feat(fe2 & server): saved views foundation (list & view) + bits n bobs (#5163)

* init db migration

* WIP store view

* create service call

* WIP insertion

* insert sort of works

* moving code arounmd

* creation tests

* avoid duplicate entries

* fixes from main

* basic group retrieval works

* group filtering works

* WIP view listing

* filter by acl

* fixes + WIP single group retrieval

* wip pivot

* more pivot query fixes

* tests fixed after pivot

* views list tests

* fixing test command

* business plan only checks

* more tests for coverage

* .dts import fix

* cli fix

* anutha one

* auth policy tests for business plan access

* WIP saved views panel base

* BE listing adjustments

* WIP group rendering

* group render done

* WIP post create cache updates

* listing fine?

* my vs theirs

* auto open

* minor fixes

* click load omg

* nicely loading views

* type fix

* less spammy loading

* another type fix:

* more lint fix

* test fix

* codecov disable

* moar coverage

* fix sidebar flashin

* more test coverage

* more test cvoverage

* minor adfjustments

* adj

* saved view wipe fixes

* CSR viewer

* more improvements

* extra feature flag checks

* lint fix

* feature flags fix

* more test fixes
This commit is contained in:
Kristaps Fabians Geikins
2025-08-05 11:52:50 +03:00
committed by GitHub
parent 23e9cd31b9
commit a6287fc06d
137 changed files with 8225 additions and 1049 deletions
@@ -13,8 +13,14 @@ describe('workspacePlanHasAccessToFeature', () => {
describe.each(allPlans)('should work for %s plan', (plan) => {
it.each(allFeatures)('%s feature combination', (feature) => {
const expectedResult = WorkspacePlanConfigs[plan].features.includes(feature)
const actualResult = workspacePlanHasAccessToFeature({ plan, feature })
const expectedResult = WorkspacePlanConfigs({ featureFlags: undefined })[
plan
].features.includes(feature)
const actualResult = workspacePlanHasAccessToFeature({
plan,
feature,
featureFlags: undefined
})
expect(
actualResult,
@@ -7,6 +7,7 @@ import {
WorkspacePlans
} from './plans.js'
import type { MaybeNullOrUndefined } from '../../core/helpers/utilityTypes.js'
import { FeatureFlags } from '../../environment/index.js'
/**
* WORKSPACE FEATURES
@@ -22,7 +23,8 @@ export const WorkspacePlanFeatures = <const>{
CustomDataRegion: 'workspaceDataRegionSpecificity',
HideSpeckleBranding: 'hideSpeckleBranding',
ExclusiveMembership: 'exclusiveMembership',
EmbedPrivateProjects: 'embedPrivateProjects'
EmbedPrivateProjects: 'embedPrivateProjects',
SavedViews: 'savedViews'
}
export type WorkspacePlanFeatures =
@@ -62,6 +64,10 @@ export const WorkspacePlanFeaturesMetadata = (<const>{
[WorkspacePlanFeatures.EmbedPrivateProjects]: {
displayName: 'Embed private projects',
description: 'Embed projects with visibility set to private or workspace'
},
[WorkspacePlanFeatures.SavedViews]: {
displayName: 'Saved views',
description: 'Create and share saved views of your models'
}
}) satisfies Record<
WorkspacePlanFeatures,
@@ -100,9 +106,11 @@ const baseFeatures = [
WorkspacePlanFeatures.EmbedPrivateProjects
] as const
export const WorkspacePaidPlanConfigs: {
export const WorkspacePaidPlanConfigs: (params: {
featureFlags: Partial<FeatureFlags> | undefined
}) => {
[plan in PaidWorkspacePlans]: WorkspacePlanConfig<plan>
} = {
} = (params) => ({
[PaidWorkspacePlans.Team]: {
plan: PaidWorkspacePlans.Team,
features: [...baseFeatures],
@@ -130,7 +138,10 @@ export const WorkspacePaidPlanConfigs: {
WorkspacePlanFeatures.DomainSecurity,
WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.CustomDataRegion,
WorkspacePlanFeatures.HideSpeckleBranding
WorkspacePlanFeatures.HideSpeckleBranding,
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED
? [WorkspacePlanFeatures.SavedViews]
: [])
],
limits: {
projectCount: 10,
@@ -146,7 +157,10 @@ export const WorkspacePaidPlanConfigs: {
WorkspacePlanFeatures.DomainSecurity,
WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.CustomDataRegion,
WorkspacePlanFeatures.HideSpeckleBranding
WorkspacePlanFeatures.HideSpeckleBranding,
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED
? [WorkspacePlanFeatures.SavedViews]
: [])
],
limits: {
projectCount: null,
@@ -155,11 +169,13 @@ export const WorkspacePaidPlanConfigs: {
commentHistory: null
}
}
}
})
export const WorkspaceUnpaidPlanConfigs: {
export const WorkspaceUnpaidPlanConfigs: (params: {
featureFlags: Partial<FeatureFlags> | undefined
}) => {
[plan in UnpaidWorkspacePlans]: WorkspacePlanConfig<plan>
} = {
} = (params) => ({
[UnpaidWorkspacePlans.Enterprise]: {
plan: UnpaidWorkspacePlans.Enterprise,
features: [
@@ -168,7 +184,10 @@ export const WorkspaceUnpaidPlanConfigs: {
WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.CustomDataRegion,
WorkspacePlanFeatures.HideSpeckleBranding,
WorkspacePlanFeatures.ExclusiveMembership
WorkspacePlanFeatures.ExclusiveMembership,
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED
? [WorkspacePlanFeatures.SavedViews]
: [])
],
limits: unlimited
},
@@ -180,7 +199,10 @@ export const WorkspaceUnpaidPlanConfigs: {
WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.CustomDataRegion,
WorkspacePlanFeatures.HideSpeckleBranding,
WorkspacePlanFeatures.ExclusiveMembership
WorkspacePlanFeatures.ExclusiveMembership,
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED
? [WorkspacePlanFeatures.SavedViews]
: [])
],
limits: unlimited
},
@@ -191,16 +213,19 @@ export const WorkspaceUnpaidPlanConfigs: {
WorkspacePlanFeatures.DomainSecurity,
WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.CustomDataRegion,
WorkspacePlanFeatures.HideSpeckleBranding
WorkspacePlanFeatures.HideSpeckleBranding,
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED
? [WorkspacePlanFeatures.SavedViews]
: [])
],
limits: unlimited
},
[UnpaidWorkspacePlans.TeamUnlimitedInvoiced]: {
...WorkspacePaidPlanConfigs.teamUnlimited,
...WorkspacePaidPlanConfigs(params).teamUnlimited,
plan: UnpaidWorkspacePlans.TeamUnlimitedInvoiced
},
[UnpaidWorkspacePlans.ProUnlimitedInvoiced]: {
...WorkspacePaidPlanConfigs.proUnlimited,
...WorkspacePaidPlanConfigs(params).proUnlimited,
plan: UnpaidWorkspacePlans.ProUnlimitedInvoiced
},
[UnpaidWorkspacePlans.Free]: {
@@ -213,24 +238,28 @@ export const WorkspaceUnpaidPlanConfigs: {
commentHistory: { value: 7, unit: 'day' }
}
}
}
})
export const WorkspacePlanConfigs = {
...WorkspacePaidPlanConfigs,
...WorkspaceUnpaidPlanConfigs
}
export const WorkspacePlanConfigs = (params: {
featureFlags: Partial<FeatureFlags> | undefined
}) => ({
...WorkspacePaidPlanConfigs(params),
...WorkspaceUnpaidPlanConfigs(params)
})
/**
* Checks if a workspace exceeds its plan limits for projects and models
*/
export const workspaceExceedsPlanLimit = (
plan: MaybeNullOrUndefined<WorkspacePlans>,
projectCount: MaybeNullOrUndefined<number>,
export const workspaceExceedsPlanLimit = (params: {
plan: MaybeNullOrUndefined<WorkspacePlans>
projectCount: MaybeNullOrUndefined<number>
modelCount: MaybeNullOrUndefined<number>
): boolean => {
featureFlags: Partial<FeatureFlags> | undefined
}): boolean => {
const { plan, projectCount, modelCount, featureFlags } = params
if (!plan) return false
const planConfig = WorkspacePlanConfigs[plan]
const planConfig = WorkspacePlanConfigs({ featureFlags })[plan]
if (!planConfig) return false
const limits = planConfig.limits
@@ -243,14 +272,16 @@ export const workspaceExceedsPlanLimit = (
/**
* Checks if a workspace reached its plan limits for projects and models
*/
export const workspaceReachedPlanLimit = (
plan: MaybeNullOrUndefined<WorkspacePlans>,
projectCount: MaybeNullOrUndefined<number>,
export const workspaceReachedPlanLimit = (params: {
plan: MaybeNullOrUndefined<WorkspacePlans>
projectCount: MaybeNullOrUndefined<number>
modelCount: MaybeNullOrUndefined<number>
): boolean => {
featureFlags: Partial<FeatureFlags> | undefined
}): boolean => {
const { plan, projectCount, modelCount, featureFlags } = params
if (!plan) return false
const planConfig = WorkspacePlanConfigs[plan]
const planConfig = WorkspacePlanConfigs({ featureFlags })[plan]
if (!planConfig) return false
const limits = planConfig.limits
@@ -261,12 +292,14 @@ export const workspaceReachedPlanLimit = (
export const workspacePlanHasAccessToFeature = ({
plan,
feature
feature,
featureFlags
}: {
plan: WorkspacePlans
feature: WorkspacePlanFeatures
featureFlags: Partial<FeatureFlags> | undefined
}): boolean => {
const planConfig = WorkspacePlanConfigs[plan]
const planConfig = WorkspacePlanConfigs({ featureFlags })[plan]
const hasAccess = planConfig.features.includes(feature)
return hasAccess
}