mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
feat: native composition report (#7007)
This commit is contained in:
parent
cefd6f24db
commit
b65576f8a0
10 changed files with 294 additions and 103 deletions
|
|
@ -34,8 +34,8 @@ const config: CodegenConfig = {
|
|||
contextType: 'GraphQLModules.ModuleContext',
|
||||
enumValues: {
|
||||
ProjectType: '../shared/entities#ProjectType',
|
||||
NativeFederationCompatibilityStatus:
|
||||
'../shared/entities#NativeFederationCompatibilityStatus',
|
||||
NativeFederationCompatibilityStatusType:
|
||||
'../shared/entities#NativeFederationCompatibilityStatusType',
|
||||
TargetAccessScope: '../modules/auth/providers/scopes#TargetAccessScope',
|
||||
ProjectAccessScope: '../modules/auth/providers/scopes#ProjectAccessScope',
|
||||
OrganizationAccessScope: '../modules/auth/providers/scopes#OrganizationAccessScope',
|
||||
|
|
|
|||
|
|
@ -281,12 +281,13 @@ function isActionMatch(actionContainingWildcard: string, action: string) {
|
|||
if (actionContainingWildcard === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// exact match
|
||||
if (actionContainingWildcard === action) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const [actionScope] = action.split(':');
|
||||
const [actionScope, actionId] = action.split(':');
|
||||
const [userSpecifiedActionScope, userSpecifiedActionId] = actionContainingWildcard.split(':');
|
||||
|
||||
// wildcard match "scope:*"
|
||||
|
|
@ -294,6 +295,11 @@ function isActionMatch(actionContainingWildcard: string, action: string) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// wildcard match "*:scope"
|
||||
if (userSpecifiedActionScope === '*' && userSpecifiedActionId === actionId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -500,7 +506,7 @@ type ActionDefinitionMap = {
|
|||
[key: `${string}:${string}`]: (args: any) => Array<string>;
|
||||
};
|
||||
|
||||
const actionDefinitions = {
|
||||
export const actionDefinitions = {
|
||||
...objectFromEntries(permissionsByLevel['organization'].map(t => [t.value, defaultOrgIdentity])),
|
||||
...objectFromEntries(permissionsByLevel['project'].map(t => [t.value, defaultProjectIdentity])),
|
||||
...objectFromEntries(permissionsByLevel['target'].map(t => [t.value, defaultTargetIdentity])),
|
||||
|
|
@ -514,7 +520,7 @@ const actionDefinitions = {
|
|||
|
||||
type Actions = keyof typeof actionDefinitions;
|
||||
|
||||
type ActionStrings = Actions | '*';
|
||||
type ActionStrings = Actions | '*' | '*:describe';
|
||||
|
||||
/** Unauthenticated session that is returned by default. */
|
||||
class UnauthenticatedSession extends Session {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,18 @@ export class SuperTokensCookieBasedSession extends Session {
|
|||
organizationId,
|
||||
);
|
||||
|
||||
// Allow admins to use all describe actions within foreign organizations
|
||||
// This makes it much more pleasant to debug.
|
||||
if (user.isAdmin) {
|
||||
return [
|
||||
{
|
||||
action: '*:describe',
|
||||
effect: 'allow',
|
||||
resource: `hrn:${organizationId}:organization/${organizationId}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ export class ProjectManager {
|
|||
projectId: selector.projectId,
|
||||
},
|
||||
});
|
||||
|
||||
return this.storage.getProject(selector);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,14 +114,40 @@ export default gql`
|
|||
externalSchemaComposition: ExternalSchemaComposition
|
||||
schemaVersionsCount(period: DateRangeInput): Int!
|
||||
isNativeFederationEnabled: Boolean!
|
||||
nativeFederationCompatibility: NativeFederationCompatibilityStatus!
|
||||
"""
|
||||
Get the status of the native federation compatability for the project.
|
||||
"""
|
||||
nativeFederationCompatibility: NativeCompositionCompatibility!
|
||||
}
|
||||
|
||||
extend type Target {
|
||||
schemaVersionsCount(period: DateRangeInput): Int!
|
||||
}
|
||||
|
||||
enum NativeFederationCompatibilityStatus {
|
||||
type NativeCompositionVersionStatus {
|
||||
"""
|
||||
The schema version we check against.
|
||||
"""
|
||||
schemaVersion: SchemaVersion!
|
||||
"""
|
||||
The native composition result. The supergraphSdl is sorted and normalized.
|
||||
"""
|
||||
nativeCompositionResult: SchemaCompositionResult!
|
||||
"""
|
||||
The supergraph of the latest valid schema version (sorted and normalized).
|
||||
"""
|
||||
currentSupergraphSdl: String!
|
||||
}
|
||||
|
||||
type NativeCompositionCompatibility {
|
||||
"""
|
||||
Whether the schema version is compatible.
|
||||
"""
|
||||
status: NativeFederationCompatibilityStatusType!
|
||||
results: [NativeCompositionVersionStatus]!
|
||||
}
|
||||
|
||||
enum NativeFederationCompatibilityStatusType {
|
||||
COMPATIBLE
|
||||
INCOMPATIBLE
|
||||
UNKNOWN
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { SchemaChecksFilter } from '../../../__generated__/types';
|
|||
import * as GraphQLSchema from '../../../__generated__/types';
|
||||
import {
|
||||
DateRange,
|
||||
NativeFederationCompatibilityStatus,
|
||||
NativeFederationCompatibilityStatusType,
|
||||
Organization,
|
||||
Project,
|
||||
ProjectType,
|
||||
|
|
@ -1123,7 +1123,18 @@ export class SchemaManager {
|
|||
return true;
|
||||
}
|
||||
|
||||
async getNativeFederationCompatibilityStatus(project: Project) {
|
||||
async getNativeFederationCompatibilityStatus(project: Project): Promise<{
|
||||
status: NativeFederationCompatibilityStatusType;
|
||||
results: Array<null | {
|
||||
schemaVersion: SchemaVersion;
|
||||
target: Target;
|
||||
nativeCompositionResult: {
|
||||
supergraphSdl: string | null;
|
||||
errors: Array<{ message: string }> | null;
|
||||
};
|
||||
currentSupergraphSdl: string;
|
||||
}>;
|
||||
}> {
|
||||
this.logger.debug(
|
||||
'Get native Federation compatibility status (organization=%s, project=%s)',
|
||||
project.orgId,
|
||||
|
|
@ -1131,7 +1142,10 @@ export class SchemaManager {
|
|||
);
|
||||
|
||||
if (project.type !== ProjectType.FEDERATION) {
|
||||
return NativeFederationCompatibilityStatus.NOT_APPLICABLE;
|
||||
return {
|
||||
status: NativeFederationCompatibilityStatusType.NOT_APPLICABLE,
|
||||
results: [],
|
||||
};
|
||||
}
|
||||
|
||||
const targets = await this.targetManager.getTargets({
|
||||
|
|
@ -1139,48 +1153,39 @@ export class SchemaManager {
|
|||
projectId: project.id,
|
||||
});
|
||||
|
||||
const possibleVersions = await Promise.all(
|
||||
targets.map(target => this.getMaybeLatestValidVersion(target)),
|
||||
);
|
||||
const results = await Promise.all(
|
||||
targets.map(async target => {
|
||||
const schemaVersion = await this.getMaybeLatestValidVersion(target);
|
||||
|
||||
const versions = possibleVersions.filter((v): v is SchemaVersion => !!v);
|
||||
if (schemaVersion === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.logger.debug('Found %s targets and %s versions', targets.length, versions.length);
|
||||
const currentSupergraphSdl = print(
|
||||
removeDescriptions(
|
||||
sortSDL(
|
||||
parseGraphQLSource(
|
||||
schemaVersion.supergraphSDL!,
|
||||
'parsing native supergraph in getNativeFederationCompatibilityStatus',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// If there are no composable versions available, we can't determine the compatibility status.
|
||||
if (
|
||||
versions.length === 0 ||
|
||||
!versions.every(
|
||||
version => version && version.isComposable && typeof version.supergraphSDL === 'string',
|
||||
)
|
||||
) {
|
||||
this.logger.debug('No composable versions available (status: unknown)');
|
||||
return NativeFederationCompatibilityStatus.UNKNOWN;
|
||||
}
|
||||
const schemas = await this.getSchemasOfVersion({
|
||||
organizationId: target.orgId,
|
||||
projectId: target.projectId,
|
||||
targetId: target.id,
|
||||
versionId: schemaVersion.id,
|
||||
});
|
||||
|
||||
const schemasPerVersion = await Promise.all(
|
||||
versions.map(async version =>
|
||||
this.getSchemasOfVersion({
|
||||
organizationId: version.organizationId,
|
||||
projectId: version.projectId,
|
||||
targetId: version.targetId,
|
||||
versionId: version.id,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
this.logger.debug('Checking compatibility of %s versions', versions.length);
|
||||
|
||||
const compatibilityResults = await Promise.all(
|
||||
versions.map(async (version, i) => {
|
||||
if (schemasPerVersion[i].length === 0) {
|
||||
this.logger.debug('No schemas (version=%s)', version.id);
|
||||
return NativeFederationCompatibilityStatus.UNKNOWN;
|
||||
if (schemas.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const compositionResult = await this.compositionOrchestrator.composeAndValidate(
|
||||
'federation',
|
||||
ensureCompositeSchemas(schemasPerVersion[i]).map(s =>
|
||||
ensureCompositeSchemas(schemas).map(s =>
|
||||
this.schemaHelper.createSchemaObject({
|
||||
sdl: s.sdl,
|
||||
service_name: s.service_name,
|
||||
|
|
@ -1196,53 +1201,52 @@ export class SchemaManager {
|
|||
},
|
||||
);
|
||||
|
||||
if (compositionResult.supergraph) {
|
||||
const sortedExistingSupergraph = print(
|
||||
removeDescriptions(
|
||||
sortSDL(
|
||||
parseGraphQLSource(
|
||||
compositionResult.supergraph,
|
||||
'parsing existing supergraph in getNativeFederationCompatibilityStatus',
|
||||
const supergraphSdl = compositionResult.supergraph
|
||||
? print(
|
||||
removeDescriptions(
|
||||
sortSDL(
|
||||
parseGraphQLSource(
|
||||
compositionResult.supergraph,
|
||||
'parsing native supergraph in getNativeFederationCompatibilityStatus',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
const sortedNativeSupergraph = print(
|
||||
removeDescriptions(
|
||||
sortSDL(
|
||||
parseGraphQLSource(
|
||||
version.supergraphSDL!,
|
||||
'parsing native supergraph in getNativeFederationCompatibilityStatus',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
)
|
||||
: null;
|
||||
|
||||
if (sortedNativeSupergraph === sortedExistingSupergraph) {
|
||||
return NativeFederationCompatibilityStatus.COMPATIBLE;
|
||||
}
|
||||
|
||||
this.logger.debug('Produced different supergraph (version=%s)', version.id);
|
||||
} else {
|
||||
this.logger.debug('Failed to produce supergraph (version=%s)', version.id);
|
||||
}
|
||||
|
||||
return NativeFederationCompatibilityStatus.INCOMPATIBLE;
|
||||
return {
|
||||
target,
|
||||
schemaVersion,
|
||||
currentSupergraphSdl,
|
||||
nativeCompositionResult: {
|
||||
supergraphSdl,
|
||||
errors: compositionResult.errors,
|
||||
},
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
if (compatibilityResults.includes(NativeFederationCompatibilityStatus.UNKNOWN)) {
|
||||
this.logger.debug('One of the versions seems empty (status: unknown)');
|
||||
return NativeFederationCompatibilityStatus.UNKNOWN;
|
||||
}
|
||||
let status = NativeFederationCompatibilityStatusType.INCOMPATIBLE;
|
||||
|
||||
if (compatibilityResults.every(r => r === NativeFederationCompatibilityStatus.COMPATIBLE)) {
|
||||
if (results.every(result => result === null)) {
|
||||
this.logger.debug('No composable versions available (status: unknown)');
|
||||
status = NativeFederationCompatibilityStatusType.UNKNOWN;
|
||||
} else if (
|
||||
results.every(
|
||||
result =>
|
||||
result === null ||
|
||||
(result.nativeCompositionResult &&
|
||||
result.currentSupergraphSdl === result.nativeCompositionResult.supergraphSdl),
|
||||
)
|
||||
) {
|
||||
this.logger.debug('All versions are compatible (status: compatible)');
|
||||
return NativeFederationCompatibilityStatus.COMPATIBLE;
|
||||
status = NativeFederationCompatibilityStatusType.COMPATIBLE;
|
||||
}
|
||||
|
||||
this.logger.debug('Some versions are incompatible (status: incompatible)');
|
||||
return NativeFederationCompatibilityStatus.INCOMPATIBLE;
|
||||
return {
|
||||
status,
|
||||
results,
|
||||
};
|
||||
}
|
||||
|
||||
async getGitHubMetadata(schemaVersion: SchemaVersion): Promise<null | {
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ export enum ProjectType {
|
|||
SINGLE = 'SINGLE',
|
||||
}
|
||||
|
||||
export enum NativeFederationCompatibilityStatus {
|
||||
export enum NativeFederationCompatibilityStatusType {
|
||||
COMPATIBLE = 'COMPATIBLE',
|
||||
INCOMPATIBLE = 'INCOMPATIBLE',
|
||||
UNKNOWN = 'UNKNOWN',
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { Switch } from '@/components/ui/switch';
|
|||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { FragmentType, graphql, useFragment } from '@/gql';
|
||||
import { NativeFederationCompatibilityStatus } from '@/gql/graphql';
|
||||
import { NativeFederationCompatibilityStatusType } from '@/gql/graphql';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const IncrementalNativeCompositionSwitch_TargetFragment = graphql(`
|
||||
|
|
@ -121,7 +121,9 @@ const NativeCompositionSettings_ProjectQuery = graphql(`
|
|||
query NativeCompositionSettings_ProjectQuery($selector: ProjectSelectorInput!) {
|
||||
project(reference: { bySelector: $selector }) {
|
||||
id
|
||||
nativeFederationCompatibility
|
||||
nativeFederationCompatibility {
|
||||
status
|
||||
}
|
||||
experimental_nativeCompositionPerTarget
|
||||
}
|
||||
}
|
||||
|
|
@ -290,37 +292,37 @@ export function NativeCompositionSettings(props: {
|
|||
<CardContent>
|
||||
<div className="flex flex-row items-center gap-x-4">
|
||||
<div>
|
||||
{projectQuery.data.project.nativeFederationCompatibility ===
|
||||
NativeFederationCompatibilityStatus.Compatible ? (
|
||||
{projectQuery.data.project.nativeFederationCompatibility.status ===
|
||||
NativeFederationCompatibilityStatusType.Compatible ? (
|
||||
<PartyPopperIcon className="size-10 text-emerald-500" />
|
||||
) : null}
|
||||
{projectQuery.data.project.nativeFederationCompatibility ===
|
||||
NativeFederationCompatibilityStatus.Incompatible ? (
|
||||
{projectQuery.data.project.nativeFederationCompatibility.status ===
|
||||
NativeFederationCompatibilityStatusType.Incompatible ? (
|
||||
<HeartCrackIcon className="size-10 text-red-500" />
|
||||
) : null}
|
||||
{projectQuery.data.project.nativeFederationCompatibility ===
|
||||
NativeFederationCompatibilityStatus.Unknown ? (
|
||||
{projectQuery.data.project.nativeFederationCompatibility.status ===
|
||||
NativeFederationCompatibilityStatusType.Unknown ? (
|
||||
<FlaskConicalIcon className="size-10 text-orange-500" />
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-base font-semibold">
|
||||
{projectQuery.data.project.nativeFederationCompatibility ===
|
||||
NativeFederationCompatibilityStatus.Compatible
|
||||
{projectQuery.data.project.nativeFederationCompatibility.status ===
|
||||
NativeFederationCompatibilityStatusType.Compatible
|
||||
? 'Your project is compatible'
|
||||
: null}
|
||||
{projectQuery.data.project.nativeFederationCompatibility ===
|
||||
NativeFederationCompatibilityStatus.Incompatible
|
||||
{projectQuery.data.project.nativeFederationCompatibility.status ===
|
||||
NativeFederationCompatibilityStatusType.Incompatible
|
||||
? 'Your project is not yet supported'
|
||||
: null}
|
||||
{projectQuery.data.project.nativeFederationCompatibility ===
|
||||
NativeFederationCompatibilityStatus.Unknown
|
||||
{projectQuery.data.project.nativeFederationCompatibility.status ===
|
||||
NativeFederationCompatibilityStatusType.Unknown
|
||||
? 'Unclear whether your project is compatible'
|
||||
: null}
|
||||
</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{projectQuery.data.project.nativeFederationCompatibility ===
|
||||
NativeFederationCompatibilityStatus.Compatible ? (
|
||||
{projectQuery.data.project.nativeFederationCompatibility.status ===
|
||||
NativeFederationCompatibilityStatusType.Compatible ? (
|
||||
<>
|
||||
Subgraphs of this project are composed and validated correctly by our{' '}
|
||||
<a
|
||||
|
|
@ -332,8 +334,8 @@ export function NativeCompositionSettings(props: {
|
|||
for Apollo Federation.
|
||||
</>
|
||||
) : null}
|
||||
{projectQuery.data.project.nativeFederationCompatibility ===
|
||||
NativeFederationCompatibilityStatus.Incompatible ? (
|
||||
{projectQuery.data.project.nativeFederationCompatibility.status ===
|
||||
NativeFederationCompatibilityStatusType.Incompatible ? (
|
||||
<>
|
||||
Our{' '}
|
||||
<a
|
||||
|
|
@ -344,11 +346,17 @@ export function NativeCompositionSettings(props: {
|
|||
</a>{' '}
|
||||
is not yet compatible with subgraphs of your project. We're working on it!
|
||||
<br />
|
||||
Please reach out to us to explore solutions for addressing this issue.
|
||||
Please reach out to us to explore solutions for addressing this issue and share
|
||||
this report with us:
|
||||
<a
|
||||
href={`/native-composition-compatibility-report/${projectQuery.data.project.id}`}
|
||||
>
|
||||
View full report
|
||||
</a>
|
||||
</>
|
||||
) : null}
|
||||
{projectQuery.data.project.nativeFederationCompatibility ===
|
||||
NativeFederationCompatibilityStatus.Unknown ? (
|
||||
{projectQuery.data.project.nativeFederationCompatibility.status ===
|
||||
NativeFederationCompatibilityStatusType.Unknown ? (
|
||||
<>
|
||||
Your project appears to lack any subgraphs at the moment, making it impossible
|
||||
for us to assess compatibility with our{' '}
|
||||
|
|
|
|||
123
packages/web/app/src/pages/native-composition-diff.tsx
Normal file
123
packages/web/app/src/pages/native-composition-diff.tsx
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { useQuery } from 'urql';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { DiffEditor } from '@/components/v2';
|
||||
import { graphql } from '@/gql';
|
||||
import { useClipboard } from '@/lib/hooks';
|
||||
|
||||
type NativeCompositionDiffProps = {
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
const NativeCompositionDiff_NativeCompositionDiffQuery = graphql(/* GraphQL */ `
|
||||
query NativeCompositionDiff_NativeCompositionDiffQuery($projectId: ID!) {
|
||||
project(reference: { byId: $projectId }) {
|
||||
id
|
||||
nativeFederationCompatibility {
|
||||
status
|
||||
results {
|
||||
currentSupergraphSdl
|
||||
nativeCompositionResult {
|
||||
supergraphSdl
|
||||
errors {
|
||||
edges {
|
||||
node {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
schemaVersion {
|
||||
id
|
||||
schemas {
|
||||
edges {
|
||||
node {
|
||||
... on CompositeSchema {
|
||||
id
|
||||
service
|
||||
source
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export function NativeCompositionDiff(props: NativeCompositionDiffProps): ReactNode | null {
|
||||
const [result] = useQuery({
|
||||
query: NativeCompositionDiff_NativeCompositionDiffQuery,
|
||||
variables: {
|
||||
projectId: props.projectId,
|
||||
},
|
||||
});
|
||||
|
||||
const clipboard = useClipboard();
|
||||
|
||||
if (!result.data?.project?.nativeFederationCompatibility) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { nativeFederationCompatibility } = result.data.project;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Native Composition Report Project {result.data.project.id}</h1>
|
||||
<h2>Status: {nativeFederationCompatibility.status}</h2>
|
||||
<Tabs>
|
||||
<TabsList>
|
||||
{nativeFederationCompatibility.results.map((result, index) =>
|
||||
result ? <TabsTrigger value={String(index)}>Target {index}</TabsTrigger> : null,
|
||||
)}
|
||||
</TabsList>
|
||||
{nativeFederationCompatibility.results.map((result, index) =>
|
||||
result ? (
|
||||
<TabsContent value={String(index)}>
|
||||
<div>
|
||||
<div>Supergraph Diff</div>
|
||||
<div>
|
||||
<DiffEditor
|
||||
before={result.currentSupergraphSdl}
|
||||
after={result.nativeCompositionResult.supergraphSdl ?? null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>Composition Errors:</div>
|
||||
<div>
|
||||
{result.nativeCompositionResult.errors?.edges?.length ? (
|
||||
<ul>
|
||||
{result.nativeCompositionResult.errors.edges.map(edge => (
|
||||
<li>{edge.node.message}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
'None'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-10">
|
||||
<Button
|
||||
onClick={() => {
|
||||
const services = result.schemaVersion?.schemas.edges.map(edge => ({
|
||||
sdl: (edge.node as any).source,
|
||||
name: (edge.node as any).service,
|
||||
}));
|
||||
|
||||
void clipboard(JSON.stringify(services, null, 2));
|
||||
}}
|
||||
>
|
||||
Copy services to clipboard
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
) : null,
|
||||
)}
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ import { DevPage } from './pages/dev';
|
|||
import { IndexPage } from './pages/index';
|
||||
import { LogoutPage } from './pages/logout';
|
||||
import { ManagePage } from './pages/manage';
|
||||
import { NativeCompositionDiff } from './pages/native-composition-diff';
|
||||
import { OrganizationIndexRouteSearch, OrganizationPage } from './pages/organization';
|
||||
import { JoinOrganizationPage } from './pages/organization-join';
|
||||
import { OrganizationMembersPage } from './pages/organization-members';
|
||||
|
|
@ -301,6 +302,15 @@ const devRoute = createRoute({
|
|||
component: DevPage,
|
||||
});
|
||||
|
||||
const nativeCompositionDiffRoute = createRoute({
|
||||
getParentRoute: () => authenticatedRoute,
|
||||
path: 'native-composition-compatibility-report/$projectId',
|
||||
component: function NativeCompositionDiffRoute() {
|
||||
const { projectId } = nativeCompositionDiffRoute.useParams();
|
||||
return <NativeCompositionDiff projectId={projectId} />;
|
||||
},
|
||||
});
|
||||
|
||||
const newOrgPage = createRoute({
|
||||
getParentRoute: () => authenticatedRoute,
|
||||
path: 'org/new',
|
||||
|
|
@ -836,6 +846,7 @@ const routeTree = root.addChildren([
|
|||
]),
|
||||
authenticatedRoute.addChildren([
|
||||
indexRoute,
|
||||
nativeCompositionDiffRoute,
|
||||
devRoute,
|
||||
newOrgPage,
|
||||
manageRoute,
|
||||
|
|
|
|||
Loading…
Reference in a new issue