Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/api/src/app/services/stripe.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ export async function updateEntitlements(customerId: string, entitlements: Strip
chromeExtension: false,
recordSync: false,
desktop: false,
analysisTools: false,
},
);

Expand Down
43 changes: 43 additions & 0 deletions apps/jetstream-canvas/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ const ManagePermissionsSelection = lazy(() =>
const ManagePermissionsEditor = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.ManagePermissionsEditor })),
);
const PermissionAnalysis = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.PermissionAnalysis })),
);
const PermissionAnalysisSelection = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.PermissionAnalysisSelection })),
);
const PermissionAnalysisView = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.PermissionAnalysisView })),
);

const DataAnalysis = lazy(() => import('@jetstream/feature/data-analysis').then((module) => ({ default: module.DataAnalysis })));
const DataAnalysisSelection = lazy(() =>
import('@jetstream/feature/data-analysis').then((module) => ({ default: module.DataAnalysisSelection })),
);
const FieldUsageAnalysisView = lazy(() =>
import('@jetstream/feature/data-analysis').then((module) => ({ default: module.FieldUsageAnalysisView })),
);

const DeployMetadata = lazy(() => import('@jetstream/feature/deploy').then((module) => ({ default: module.DeployMetadata })));
const DeployMetadataSelection = lazy(() =>
Expand Down Expand Up @@ -141,6 +158,8 @@ export const AppRoutes = () => {
AutomationControlEditor.preload();
} else if (location.pathname.includes('/permissions-manager')) {
ManagePermissionsEditor.preload();
} else if (location.pathname.includes('/data-analysis')) {
FieldUsageAnalysisView.preload();
} else if (location.pathname.includes('/deploy-metadata')) {
DeployMetadataDeployment.preload();
} else if (location.pathname.includes('/create-fields')) {
Expand Down Expand Up @@ -214,6 +233,30 @@ export const AppRoutes = () => {
<Route path="editor" element={<ManagePermissionsEditor />} />
<Route path="*" element={<Navigate to=".." />} />
</Route>
<Route
path={APP_ROUTES.PERMISSION_ANALYSIS.ROUTE}
element={
<OrgSelectionRequired>
<PermissionAnalysis />
</OrgSelectionRequired>
}
>
<Route index element={<PermissionAnalysisSelection />} />
<Route path="analysis" element={<PermissionAnalysisView />} />
<Route path="*" element={<Navigate to=".." />} />
</Route>
<Route
path={APP_ROUTES.DATA_ANALYSIS.ROUTE}
element={
<OrgSelectionRequired>
<DataAnalysis />
</OrgSelectionRequired>
}
>
<Route index element={<DataAnalysisSelection />} />
<Route path="analysis" element={<FieldUsageAnalysisView />} />
<Route path="*" element={<Navigate to=".." />} />
</Route>
<Route
path={APP_ROUTES.DEPLOY_METADATA.ROUTE}
element={
Expand Down
43 changes: 43 additions & 0 deletions apps/jetstream-desktop-client/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ const ManagePermissionsSelection = lazy(() =>
const ManagePermissionsEditor = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.ManagePermissionsEditor })),
);
const PermissionAnalysis = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.PermissionAnalysis })),
);
const PermissionAnalysisSelection = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.PermissionAnalysisSelection })),
);
const PermissionAnalysisView = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.PermissionAnalysisView })),
);

const DataAnalysis = lazy(() => import('@jetstream/feature/data-analysis').then((module) => ({ default: module.DataAnalysis })));
const DataAnalysisSelection = lazy(() =>
import('@jetstream/feature/data-analysis').then((module) => ({ default: module.DataAnalysisSelection })),
);
const FieldUsageAnalysisView = lazy(() =>
import('@jetstream/feature/data-analysis').then((module) => ({ default: module.FieldUsageAnalysisView })),
);

const DeployMetadata = lazy(() => import('@jetstream/feature/deploy').then((module) => ({ default: module.DeployMetadata })));
const DeployMetadataSelection = lazy(() =>
Expand Down Expand Up @@ -121,6 +138,8 @@ export const AppRoutes = () => {
AutomationControlEditor.preload();
} else if (location.pathname.includes('/permissions-manager')) {
ManagePermissionsEditor.preload();
} else if (location.pathname.includes('/data-analysis')) {
FieldUsageAnalysisView.preload();
} else if (location.pathname.includes('/deploy-metadata')) {
DeployMetadataDeployment.preload();
} else if (location.pathname.includes('/create-fields')) {
Expand Down Expand Up @@ -197,6 +216,30 @@ export const AppRoutes = () => {
<Route path="editor" element={<ManagePermissionsEditor />} />
<Route path="*" element={<Navigate to=".." />} />
</Route>
<Route
path={APP_ROUTES.PERMISSION_ANALYSIS.ROUTE}
element={
<OrgSelectionRequired>
<PermissionAnalysis />
</OrgSelectionRequired>
}
>
<Route index element={<PermissionAnalysisSelection />} />
<Route path="analysis" element={<PermissionAnalysisView />} />
<Route path="*" element={<Navigate to=".." />} />
</Route>
<Route
path={APP_ROUTES.DATA_ANALYSIS.ROUTE}
element={
<OrgSelectionRequired>
<DataAnalysis />
</OrgSelectionRequired>
}
>
<Route index element={<DataAnalysisSelection />} />
<Route path="analysis" element={<FieldUsageAnalysisView />} />
<Route path="*" element={<Navigate to=".." />} />
</Route>
<Route
path={APP_ROUTES.DEPLOY_METADATA.ROUTE}
element={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Announcement, JetstreamEventSaveSoqlQueryFormatOptionsPayload, Salesfor
import { fireToast } from '@jetstream/ui';
import { fromJetstreamEvents, useAmplitude } from '@jetstream/ui-core';
import { fromAppState } from '@jetstream/ui/app-state';
import { initDexieDb } from '@jetstream/ui/db';
import { initDexieDb, pruneAnalysisJobHistory } from '@jetstream/ui/db';
import { AxiosResponse } from 'axios';
import { useAtom, useAtomValue } from 'jotai';
import localforage from 'localforage';
Expand Down Expand Up @@ -85,9 +85,11 @@ APP VERSION ${version}
} else {
disconnectSocket();
}
initDexieDb({ recordSyncEnabled }).catch((ex) => {
logger.error('[DB] Error initializing db', ex);
});
initDexieDb({ recordSyncEnabled })
.then(() => pruneAnalysisJobHistory())
.catch((ex) => {
logger.error('[DB] Error initializing db', ex);
});
}, [appInfo.serverUrl, authInfo.accessToken, authInfo.deviceId, recordSyncEnabled]);

useEffect(() => {
Expand Down
8 changes: 8 additions & 0 deletions apps/jetstream-e2e/src/tests/app/routing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ const testCases = [
},
{ cardTitle: 'AUTOMATION', menu: 'Automation Control', items: [{ link: 'Automation Control', path: '/automation-control' }] },
{ cardTitle: 'PERMISSIONS', menu: 'Manage Permissions', items: [{ link: 'Manage Permissions', path: '/permissions-manager' }] },
{
cardTitle: 'Analysis',
menu: 'Analysis Tools',
items: [
{ link: 'Permission Analysis', path: '/permission-analysis' },
{ link: 'Data Analysis', path: '/data-analysis' },
],
},
{
cardTitle: 'DEPLOY',
menu: 'Deploy Metadata',
Expand Down
43 changes: 43 additions & 0 deletions apps/jetstream/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ const ManagePermissionsSelection = lazy(() =>
const ManagePermissionsEditor = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.ManagePermissionsEditor })),
);
const PermissionAnalysis = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.PermissionAnalysis })),
);
const PermissionAnalysisSelection = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.PermissionAnalysisSelection })),
);
const PermissionAnalysisView = lazy(() =>
import('@jetstream/feature/manage-permissions').then((module) => ({ default: module.PermissionAnalysisView })),
);

const DataAnalysis = lazy(() => import('@jetstream/feature/data-analysis').then((module) => ({ default: module.DataAnalysis })));
const DataAnalysisSelection = lazy(() =>
import('@jetstream/feature/data-analysis').then((module) => ({ default: module.DataAnalysisSelection })),
);
const FieldUsageAnalysisView = lazy(() =>
import('@jetstream/feature/data-analysis').then((module) => ({ default: module.FieldUsageAnalysisView })),
);

const DeployMetadata = lazy(() => import('@jetstream/feature/deploy').then((module) => ({ default: module.DeployMetadata })));
const DeployMetadataSelection = lazy(() =>
Expand Down Expand Up @@ -109,6 +126,8 @@ export const AppRoutes = () => {
AutomationControlEditor.preload();
} else if (location.pathname.includes('/permissions-manager')) {
ManagePermissionsEditor.preload();
} else if (location.pathname.includes('/permission-analysis')) {
PermissionAnalysisView.preload();
} else if (location.pathname.includes('/deploy-metadata')) {
DeployMetadataDeployment.preload();
} else if (location.pathname.includes('/create-fields')) {
Expand Down Expand Up @@ -189,6 +208,30 @@ export const AppRoutes = () => {
<Route path="editor" element={<ManagePermissionsEditor />} />
<Route path="*" element={<Navigate to=".." />} />
</Route>
<Route
path={APP_ROUTES.PERMISSION_ANALYSIS.ROUTE}
element={
<OrgSelectionRequired>
<PermissionAnalysis />
</OrgSelectionRequired>
}
>
<Route index element={<PermissionAnalysisSelection />} />
<Route path="analysis" element={<PermissionAnalysisView />} />
<Route path="*" element={<Navigate to=".." />} />
</Route>
<Route
path={APP_ROUTES.DATA_ANALYSIS.ROUTE}
element={
<OrgSelectionRequired>
<DataAnalysis />
</OrgSelectionRequired>
}
>
<Route index element={<DataAnalysisSelection />} />
<Route path="analysis" element={<FieldUsageAnalysisView />} />
<Route path="*" element={<Navigate to=".." />} />
</Route>
<Route
path={APP_ROUTES.DEPLOY_METADATA.ROUTE}
element={
Expand Down
10 changes: 6 additions & 4 deletions apps/jetstream/src/app/components/core/AppInitializer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { fireToast } from '@jetstream/ui';
import { fromJetstreamEvents, useAmplitude } from '@jetstream/ui-core';
import { fromAppState } from '@jetstream/ui/app-state';
import { CookieConsentBanner, useConditionalGoogleAnalytics } from '@jetstream/ui/cookie-consent-banner';
import { initDexieDb } from '@jetstream/ui/db';
import { initDexieDb, pruneAnalysisJobHistory } from '@jetstream/ui/db';
import { AxiosResponse } from 'axios';
import { useAtom, useAtomValue } from 'jotai';
import localforage from 'localforage';
Expand Down Expand Up @@ -90,9 +90,11 @@ APP VERSION ${version}
} else {
disconnectSocket();
}
initDexieDb({ recordSyncEnabled }).catch((ex) => {
logger.error('[DB] Error initializing db', ex);
});
initDexieDb({ recordSyncEnabled })
.then(() => pruneAnalysisJobHistory())
.catch((ex) => {
logger.error('[DB] Error initializing db', ex);
});
}, [appInfo.serverUrl, recordSyncEnabled]);

useEffect(() => {
Expand Down
6 changes: 5 additions & 1 deletion libs/auth/acl/src/lib/acl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type Actions = 'read' | 'update';
type Subjects = 'Billing' | 'CoreFunctionality' | 'Profile' | 'Settings';

type EntitlementActions = 'access';
type EntitlementSubjects = 'GoogleDrive' | 'ChromeExtension' | 'Desktop' | 'RecordSync';
type EntitlementSubjects = 'GoogleDrive' | 'ChromeExtension' | 'Desktop' | 'RecordSync' | 'AnalysisTools';

type TeamActions = 'read' | 'update';
type TeamSubjects = 'Team' | 'TeamMember' | { type: 'TeamMember'; role: TeamMemberRole };
Expand Down Expand Up @@ -132,6 +132,10 @@ function getAbilityRules({ isBrowserExtension, isDesktop, isCanvasApp, user }: G
if (user.entitlements.recordSync || isBrowserExtension || isDesktop) {
can('access', 'RecordSync');
}
// Analysis Tools (Field Usage + Permission Analysis) are paid-only; desktop/extension/canvas are paid tiers.
if (user.entitlements.analysisTools || isBrowserExtension || isDesktop || isCanvasApp) {
can('access', 'AnalysisTools');
}

return rules;
}
Expand Down
17 changes: 17 additions & 0 deletions libs/features/data-analysis/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const baseConfig = require('../../../eslint.config.js');

module.exports = [
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {},
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {},
},
{
files: ['**/*.js', '**/*.jsx'],
rules: {},
},
];
16 changes: 16 additions & 0 deletions libs/features/data-analysis/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "features-data-analysis",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/features/data-analysis/src",
"projectType": "library",
"tags": ["scope:browser"],
"targets": {
"test": {
"executor": "@nx/vitest:test",
"outputs": ["{options.reportsDirectory}"],
"options": {
"reportsDirectory": "{projectRoot}/../../../coverage/libs/features/data-analysis"
}
}
}
}
19 changes: 19 additions & 0 deletions libs/features/data-analysis/src/DataAnalysis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { TITLES } from '@jetstream/shared/constants';
import { useTitle } from '@jetstream/shared/ui-utils';
import { AnalysisToolsPaywall } from '@jetstream/ui-core';
import { analysisToolsAccessState } from '@jetstream/ui/app-state';
import { useAtomValue } from 'jotai';
import { FunctionComponent } from 'react';
import { Outlet } from 'react-router-dom';

/** Route shell for **Data analysis** (field usage). Paid-only — see {@link analysisToolsAccessState}. */
export const DataAnalysis: FunctionComponent = () => {
useTitle(TITLES.DATA_ANALYSIS);
const { hasAnalysisToolsAccess } = useAtomValue(analysisToolsAccessState);
if (!hasAnalysisToolsAccess) {
return <AnalysisToolsPaywall featureLabel="Field Usage Analysis" />;
}
return <Outlet />;
};

export default DataAnalysis;
Loading
Loading