Skip to content

Commit 9df253f

Browse files
committed
Merge branch 'wip/fix-zone-wiring' into wip/platform-org-wiring
2 parents 975ad42 + c5c6a79 commit 9df253f

78 files changed

Lines changed: 420 additions & 318 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/desktop/src/main/app-routes.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ describe('settingsRoute', () => {
99
expect(settingsRoute('/o/org-1/search?q=x')).toBe('/o/org-1/settings/general')
1010
})
1111

12+
it.each(['/org', '/org/search?q=x', '/org/settings/usage'])(
13+
'stays in the organization from its Platform page %s',
14+
(lastRoute) => {
15+
expect(settingsRoute(lastRoute)).toBe('/org/settings/general')
16+
}
17+
)
18+
1219
it('keeps the desktop settings of the workspace the user was last in', () => {
1320
expect(settingsRoute('/workspace/ws-1/home')).toBe('/workspace/ws-1/settings/desktop')
1421
})
@@ -18,6 +25,7 @@ describe('settingsRoute', () => {
1825
['the app entry', '/home'],
1926
['no saved route', undefined],
2027
['a route that only starts like an organization', '/oauth/o/org-1'],
28+
['a route that only starts like the Platform', '/organizations/x'],
2129
['an unsafe route', '//evil.example/o/org-1'],
2230
])('resolves settings through the web app from %s', (_label, lastRoute) => {
2331
expect(settingsRoute(lastRoute)).toBe('/workspace?redirect=settings')

‎apps/desktop/src/main/app-routes.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ function organizationIdFromRoute(lastRoute: string | undefined): string | null {
4343
return null
4444
}
4545

46+
/** The organization's Platform pages, mounted at `/org`, which name no organization in the path. */
47+
function isOrganizationZoneRoute(lastRoute: string | undefined): boolean {
48+
return isSafeInternalPath(lastRoute) && /^\/org(?:[/?#]|$)/.test(lastRoute)
49+
}
50+
4651
/**
4752
* Route for "New Chat": the home (chat) surface of the workspace the user was
4853
* last in, falling back to the app entry when the last route carries no
@@ -63,5 +68,8 @@ export function settingsRoute(lastRoute: string | undefined): string {
6368
return `/workspace/${workspaceId}/settings/desktop`
6469
}
6570
const organizationId = organizationIdFromRoute(lastRoute)
66-
return organizationId ? `/o/${organizationId}/settings/general` : SETTINGS_ENTRY_ROUTE
71+
if (organizationId) {
72+
return `/o/${organizationId}/settings/general`
73+
}
74+
return isOrganizationZoneRoute(lastRoute) ? '/org/settings/general' : SETTINGS_ENTRY_ROUTE
6775
}

‎apps/sim/app/api/organizations/[id]/home/workspace-activity/route.ts‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
import { getOrganizationHomeWorkspaceActivityContract } from '@/lib/api/contracts/organization-home'
22
import {
3+
createInternalResourceConcealmentPolicy,
34
defineInternalJsonRoute,
5+
internalErrorResponse,
6+
internalOrchestrationErrorPolicy,
47
internalRateLimits,
58
internalSessionAuth,
69
} from '@/lib/api/server/routes'
710
import {
811
organizationHomeWorkspaceOperations,
912
readOrganizationHomeWorkspaceActivity,
1013
} from '@/lib/organizations/application/home-workspaces'
11-
import { createCredentialGroupInternalErrorPolicy } from '@/app/api/workspaces/[id]/credential-groups/error-policy'
1214

1315
export const GET = defineInternalJsonRoute({
1416
contract: getOrganizationHomeWorkspaceActivityContract,
1517
auth: internalSessionAuth,
1618
operation: organizationHomeWorkspaceOperations.readActivity,
1719
rateLimit: internalRateLimits.user({ bucketName: 'organization-home-workspace-activity' }),
18-
errorPolicy: createCredentialGroupInternalErrorPolicy(
19-
'Failed to load workspace activity',
20-
'Organization not found'
21-
),
20+
errorPolicy: createInternalResourceConcealmentPolicy({
21+
base: {
22+
project: internalOrchestrationErrorPolicy.project,
23+
unhandled: () => internalErrorResponse(500, { error: 'Failed to load workspace activity' }),
24+
},
25+
notFoundMessage: 'Organization not found',
26+
}),
2227
mapInput: ({ params }) => ({ organizationId: params.id }),
2328
useCase: readOrganizationHomeWorkspaceActivity,
2429
staticResponseHeaders: { 'Cache-Control': 'private, no-store' },

‎apps/sim/lib/core/config/env.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ export const env = createEnv({
614614
SIM_LINKS: z.boolean().optional(), // Fallback for the `sim-links` feature flag off AppConfig
615615
PEOPLE: z.boolean().optional(), // Fallback for the `people` feature flag off AppConfig
616616
PLATFORM_ORG_UI: z.boolean().optional(), // Fallback for the `platform-org-ui` feature flag off AppConfig
617-
SIM_PLATFORM_ORIGIN: z.string().url().optional(), // Internal origin of the Platform zone. Set at build and runtime: adds the /platform rewrite and arms the organization redirect
617+
SIM_PLATFORM_ORIGIN: z.string().url().optional(), // Internal origin of the Platform zone. Set at build and runtime: adds the /org rewrite and arms the organization redirect
618618
MSHIP_MODEL_SELECTOR: z.boolean().optional(),
619619
INBOX_ENABLED: z.boolean().optional(), // Enable inbox (Sim Mailer) on self-hosted (bypasses hosted requirements)
620620
SANDBOXES_ENABLED: z.boolean().optional(), // Enable custom sandboxes on self-hosted (bypasses hosted requirements)

‎apps/sim/lib/organizations/home/workspace-activity.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ export function readRunningWorkflows(
5353

5454
/**
5555
* Per workspace, one workflow waiting on a person and the newest retained run. Each is a `LIMIT 1`
56-
* probe, so a workspace with thousands of unanswered pauses costs what one does. The waiting workflow
57-
* is therefore any of them rather than the newest, since finding the newest would read them all.
56+
* probe, so many unanswered pauses cost what one does once a match exists. The waiting workflow is
57+
* therefore any of them rather than the newest. With no match, the probe reads every live pause.
5858
*/
5959
export function readReviewAndLastRun(workspaceIds: readonly string[]) {
6060
const logs = workflowExecutionLogs

‎apps/sim/lib/organizations/settings-sections.ts‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@ import {
22
ACCOUNT_SETTINGS_ITEMS,
33
type AccountSettingsSection,
44
getSettingsSectionMeta,
5-
isOrganizationSettingsSectionAvailable,
65
ORGANIZATION_SETTINGS_GROUPS,
76
ORGANIZATION_SETTINGS_ITEMS,
87
type OrganizationSettingsFeatures,
98
type OrganizationSettingsSection,
109
parseSettingsPathSection,
11-
resolveOrganizationSectionAccess,
1210
type SettingsNavigationItem,
1311
toSettingsHeaderMeta,
1412
} from '@/components/settings/navigation'
1513
import type { SettingsHeaderMeta } from '@/components/settings/settings-header'
14+
import { organizationSupportsSettingsSection } from '@/lib/settings/section-availability'
1615

1716
/**
1817
* A section on the organization surface's settings, tagged with the plane that
@@ -84,18 +83,8 @@ export function organizationSettingsNavigation(
8483
features: OrganizationSettingsFeatures,
8584
availability: { connectedAccounts: boolean; search: boolean; knowledgeGraphs?: boolean }
8685
) {
87-
return ORGANIZATION_SETTINGS_ITEMS.filter(
88-
(item) =>
89-
(item.id !== 'knowledge-graphs' || availability.knowledgeGraphs === true) &&
90-
(item.id !== 'connected-accounts' || availability.connectedAccounts) &&
91-
((item.id !== 'search-mcp' && item.id !== 'search-slack' && item.id !== 'integrations') ||
92-
availability.search) &&
93-
resolveOrganizationSectionAccess({
94-
section: item.id,
95-
isTargetOrganizationMember: true,
96-
isTargetOrganizationAdmin: isAdmin,
97-
}) !== 'unavailable' &&
98-
isOrganizationSettingsSectionAvailable(item.id, features)
86+
return ORGANIZATION_SETTINGS_ITEMS.filter((item) =>
87+
organizationSupportsSettingsSection(item.id, features, availability, isAdmin)
9988
)
10089
}
10190

‎apps/sim/lib/platform/routes.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,25 @@ describe('Platform zone mount', () => {
2222
it('hands legacy organization routes to the mount', () => {
2323
expect(mapLegacyOrganizationUrl('/o/org-1/home', 'org-1')).toBe('/org/home')
2424
})
25+
26+
it.each([
27+
'slack=token',
28+
'integration=gmail',
29+
'connectorType=gmail',
30+
'connectorId=c',
31+
'credentialId=c',
32+
'connectionMode=member',
33+
'optionId=o',
34+
'savedProviderId=jama-service-account',
35+
'provider=google',
36+
])('keeps the integrations page legacy for a link carrying %s', (query) => {
37+
expect(mapLegacyOrganizationUrl(`/o/org-1/integrations?${query}`, 'org-1')).toBeNull()
38+
})
39+
40+
it('still hands a plain or searched integrations page to the mount', () => {
41+
expect(mapLegacyOrganizationUrl('/o/org-1/integrations', 'org-1')).toBe('/org/integrations')
42+
expect(mapLegacyOrganizationUrl('/o/org-1/integrations?q=mail', 'org-1')).toBe(
43+
'/org/integrations?q=mail'
44+
)
45+
})
2546
})

‎apps/sim/lib/platform/routes.ts‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,29 @@ function mapSettingsRoute(rest: readonly string[], query: URLSearchParams): stri
6060
return target && `/settings/${target}`
6161
}
6262

63+
/**
64+
* Query keys that give the integrations page a job the zone's page cannot do yet: the Slack onboarding
65+
* handoff, a connection request, or a jump to one integration. Those links stay on the legacy page.
66+
*/
67+
const LEGACY_INTEGRATIONS_QUERY = [
68+
'slack',
69+
'integration',
70+
'connectorType',
71+
'connectorId',
72+
'credentialId',
73+
'connectionMode',
74+
'optionId',
75+
'savedProviderId',
76+
'provider',
77+
] as const
78+
6379
function mapRoute(rest: readonly string[], query: URLSearchParams): string | null {
6480
const [head, detail] = rest
6581
if (head === undefined) return '/'
6682
if (head === 'settings') return mapSettingsRoute(rest.slice(1), query)
6783
if (rest.length === 1) {
84+
if (head === 'integrations' && LEGACY_INTEGRATIONS_QUERY.some((key) => query.has(key)))
85+
return null
6886
if (head === 'home' || head === 'people' || head === 'integrations' || head === 'search')
6987
return `/${head}`
7088
if (head === 'links') return '/settings/links'
@@ -77,8 +95,7 @@ function mapRoute(rest: readonly string[], query: URLSearchParams): string | nul
7795

7896
/**
7997
* The Platform URL for a legacy URL under `/o/{organizationId}`, keeping its query, or `null` for a
80-
* route that stays legacy: Benchmark, Skills, Admin, and the Search and credential pages with
81-
* legacy management links. Null also covers another organization's URL. A fallback must lead to
98+
* route that stays legacy: Benchmark, Skills, Admin, and integrations links the zone cannot serve yet. Null also covers another organization's URL. A fallback must lead to
8299
* an unmapped route so the two surfaces cannot redirect to each other.
83100
*/
84101
export function mapLegacyOrganizationUrl(url: string, organizationId: string): string | null {

‎apps/sim/platform/app/(shell)/integrations/_components/integrations-page/live-integrations-route.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { notFound } from 'next/navigation'
12
import { IntegrationsRoute } from '@/platform/app/(shell)/integrations/_components/integrations-page/integrations-route'
23
import { legacyRoutes } from '@/platform/lib/data/organization-model'
34
import { readOrganizationSurface } from '@/platform/lib/data/surface'
@@ -8,6 +9,7 @@ export async function LiveIntegrationsRoute({ section }: { section?: string }) {
89
// The layout already rendered this outcome's login, denial or error state.
910
if (surface.status !== 'ok') return null
1011
const { organization } = surface
12+
if (!organization.searchAccess.memberScoped) notFound()
1113
return (
1214
<IntegrationsRoute
1315
organizationId={organization.organization.id}

‎apps/sim/platform/app/(shell)/people/_components/org-chart/org-chart.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ import {
2121
} from '@/platform/design-system/components'
2222
import { useScrollEdges } from '@/platform/design-system/components/scroll-area/use-scroll-edges'
2323
import {
24-
isRefusal,
2524
type SyncSource,
2625
useOrgChart,
2726
usePeoplePicker,
2827
usePeopleViewer,
2928
} from '@/platform/lib/data/people'
29+
import { isRefusal } from '@/platform/lib/data/query-view'
3030

3131
function ReportingLinesGraphic() {
3232
return (

0 commit comments

Comments
 (0)