Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
6cbd097
feat(ui): add disabled mode to sobject multiselect
AndyHaas Apr 30, 2026
8a556c0
feat(db): add analysis_job model and migration
AndyHaas Apr 30, 2026
7b5251f
feat(routing): add permission and data analysis routes
AndyHaas Apr 30, 2026
90fe629
feat(data-analysis): add data analysis feature library
AndyHaas Apr 30, 2026
99da4f5
feat(manage-permissions): add permission analysis workspace
AndyHaas Apr 30, 2026
c345b65
feat(api): add analysis job persistence and processor
AndyHaas Apr 30, 2026
b6ecd85
feat(api): wire analysis job HTTP routes and client helpers
AndyHaas Apr 30, 2026
80a38a7
docs(manage-permissions): add Id-based permission export SOQL templates
AndyHaas Apr 30, 2026
fedb007
feat(api): add permission export query runner and job processor
AndyHaas Apr 30, 2026
3c17bd3
refactor(manage-permissions): drop manual refresh from permission ana…
AndyHaas Apr 30, 2026
2290e2b
feat(permissions): add Profile.Name to permission set SOQL and listAn…
AndyHaas Apr 30, 2026
a510277
feat(permissions): add analysis job status display formatter
AndyHaas Apr 30, 2026
50c9460
feat(permissions): add permission scope badge Emotion styles
AndyHaas Apr 30, 2026
90e48dc
feat(permissions): add export history modal to permission analysis
AndyHaas Apr 30, 2026
874eb07
fix(permissions): scope filter popover button alignment
AndyHaas Apr 30, 2026
4fd2891
fix(permissions): style scope filter Clear as link and align row
AndyHaas Apr 30, 2026
52d6730
chore(permissions): shorten export history modal footer copy
AndyHaas Apr 30, 2026
57d7b70
feat(icon-factory): register groups and incident standard icons
AndyHaas Apr 30, 2026
31bdff9
feat(permission-export): add SOQL for assignments and permission set …
AndyHaas Apr 30, 2026
80c17b1
feat(permission-export): persist assignments and PSG data in analysis…
AndyHaas Apr 30, 2026
2e0ab33
feat(manage-permissions): tabbed permission analysis with export and …
AndyHaas Apr 30, 2026
e4560f9
fix(manage-permissions): widen export grid columns for permission hea…
AndyHaas Apr 30, 2026
dee531e
feat(manage-permissions): profiles tab and scope-based export tabs
AndyHaas Apr 30, 2026
d1d6fd8
feat(manage-permissions): improve object permission analysis UX and e…
AndyHaas May 1, 2026
35b567c
feat(constants): add permission export finding code catalog
AndyHaas May 1, 2026
d234e03
feat(api): build permission export findings during analysis jobs
AndyHaas May 1, 2026
539676c
feat(manage-permissions): aggregate findings and cell helpers for ana…
AndyHaas May 1, 2026
dc68aaf
feat(manage-permissions): show export findings in Issues tab and obje…
AndyHaas May 1, 2026
1971855
feat(manage-permissions): add helpers to map findings onto export grids
AndyHaas May 1, 2026
fce6769
refactor(manage-permissions): extract shared findings modal from obje…
AndyHaas May 1, 2026
1a46ddc
feat(manage-permissions): surface job findings on field, assignment, …
AndyHaas May 1, 2026
e0ff8c7
feat(manage-permissions): permission sets analysis tree with assignme…
AndyHaas May 1, 2026
85aaab7
fix(manage-permissions): drop created and modified from perm set tooltip
AndyHaas May 1, 2026
556ad2d
feat(manage-permissions): add user-grouped Assignments tree to permis…
AndyHaas May 1, 2026
932ad46
feat(manage-permissions): sort OLS tree and surface profiles in Objec…
AndyHaas May 1, 2026
db23a8b
feat(manage-permissions): show tab labels and readable visibility in …
AndyHaas May 1, 2026
3d071b6
feat(manage-permissions): add issues rollup drill-down and enrich fin…
AndyHaas May 1, 2026
b8ffeca
fix(manage-permissions): add visible gap between aggregated findings …
AndyHaas May 1, 2026
d7bb7f5
feat(manage-permissions): add grouped field permissions analysis tree
AndyHaas May 1, 2026
e65555e
feat(manage-permissions): move finding filters to analysis toolbar
AndyHaas May 1, 2026
d8f9ccc
feat(manage-permissions): refine analysis toolbar and Issues grouping
AndyHaas May 1, 2026
19d3384
feat(manage-permissions): polish permission analysis findings filters
AndyHaas May 1, 2026
7a4abbc
feat(manage-permissions): export scope filter and issues column picker
AndyHaas May 1, 2026
f8ab6a3
refactor(manage-permissions): show issue code counts in toolbar popover
AndyHaas May 1, 2026
876f81b
style(manage-permissions): title case Export Scope and Direct Assignm…
AndyHaas May 1, 2026
a9ad3c1
fix(manage-permissions): show Export Scope only for mixed profile/PS …
AndyHaas May 1, 2026
1741a43
style(manage-permissions): card layout and title case for Issue Codes…
AndyHaas May 1, 2026
fa19bc0
feat(manage-permissions): polish issues aggregated findings rail
AndyHaas May 1, 2026
27de6a7
refactor(manage-permissions): align permission analysis copy with Iss…
AndyHaas May 1, 2026
61e778c
feat(manage-permissions): add optional object scope to permission ana…
AndyHaas May 1, 2026
79bebd9
feat(manage-permissions): refine permission analysis toolbar layout
AndyHaas May 1, 2026
3b32680
feat(manage-permissions): speed up permission analysis workspace
AndyHaas May 1, 2026
ecb4d1b
feat(manage-permissions): consolidate issues filters and split export…
AndyHaas May 1, 2026
db8cfed
feat(api): process field_usage analysis jobs
AndyHaas May 1, 2026
4ef99c7
feat(data-analysis): field usage results UI and job history
AndyHaas May 1, 2026
ae0626f
fix(data-analysis): clarify primary action label on selection screen
AndyHaas May 1, 2026
95d11eb
fix(data-analysis): drop redundant completed badge on field usage res…
AndyHaas May 1, 2026
6ae2dc6
fix(data-analysis): remove in-progress job status badge from field us…
AndyHaas May 1, 2026
56b43d7
feat(data-analysis): group field usage by object in tree grid
AndyHaas May 1, 2026
6cb2f9a
fix(data-analysis): widen Where Used column and capitalize label
AndyHaas May 1, 2026
35daeac
feat(data-analysis): richer field types, filters, and usage grid UX
AndyHaas May 1, 2026
500d2d5
feat(data-analysis): field usage where-used grid and modal fixes
AndyHaas May 1, 2026
a56c3d2
feat(field-usage): apex where-used kind and shared custom field tooli…
AndyHaas May 1, 2026
e515d5c
feat(field-usage): layout kind, flow dedupe, counts by Kind
AndyHaas May 1, 2026
20c512b
feat(data-analysis): field usage where-used links and setup popovers
AndyHaas May 1, 2026
23d1db6
feat(data-analysis): add load-all-records field usage rescan
AndyHaas May 1, 2026
957b656
feat(data-analysis): open query results from field usage object popover
AndyHaas May 1, 2026
b1f4c60
fix(field-usage): popover trigger styles and full-scan job typing
AndyHaas May 1, 2026
93ec01d
feat(field-usage): add destructive delete from usage for unmanaged fi…
AndyHaas May 1, 2026
afa8c38
fix(query): hand off field usage open-in-new-tab via localStorage
AndyHaas May 1, 2026
0d8dfd4
fix(field-usage): open query results with router basename via useHref
AndyHaas May 1, 2026
bd291e7
style(field-usage): align copy with Salesforce capitalization
AndyHaas May 1, 2026
efe3059
fix(manage-permissions): derive container issue severity from row whe…
AndyHaas May 1, 2026
04261ea
feat(analysis): job history entry points and permission analysis UX
AndyHaas May 1, 2026
618eaa6
feat(manage-permissions): use popovers for permission analysis metadata
AndyHaas May 1, 2026
7e7f23a
chore: merge upstream/main into feature-analysis-tools
AndyHaas May 13, 2026
03483ca
chore: wip
paustint May 16, 2026
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
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
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"
}
}
}
}
12 changes: 12 additions & 0 deletions libs/features/data-analysis/src/DataAnalysis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { TITLES } from '@jetstream/shared/constants';
import { useTitle } from '@jetstream/shared/ui-utils';
import { FunctionComponent } from 'react';
import { Outlet } from 'react-router-dom';

/** Route shell for **Data analysis** (field usage). */
export const DataAnalysis: FunctionComponent = () => {
useTitle(TITLES.DATA_ANALYSIS);
return <Outlet />;
};

export default DataAnalysis;
Loading
Loading