diff --git a/src/api/gql/dataPlanes.ts b/src/api/gql/dataPlanes.ts index dc496e9322..84db2db9b7 100644 --- a/src/api/gql/dataPlanes.ts +++ b/src/api/gql/dataPlanes.ts @@ -1,4 +1,7 @@ -import type { DataPlanesQuery } from 'src/gql-types/graphql'; +import type { + DataPlanesQuery, + PublicDataPlanesQuery, +} from 'src/gql-types/graphql'; import type { CloudProvider } from 'src/utils/cloudRegions'; import { graphql } from 'src/gql-types'; @@ -28,6 +31,27 @@ export const DATA_PLANES_QUERY = graphql(` } `); +// Unauthenticated query for the pre-tenant onboarding flow. The +// authenticated `dataPlanes` query returns nothing until the user +// has grants, which a brand-new signup does not. +export const PUBLIC_DATA_PLANES_QUERY = graphql(` + query PublicDataPlanes($after: String) { + publicDataPlanes(first: 100, after: $after) { + edges { + node { + name + cloudProvider + region + } + } + pageInfo { + hasNextPage + endCursor + } + } + } +`); + type DataPlaneGqlNode = DataPlanesQuery['dataPlanes']['edges'][number]['node']; export interface DataPlaneNode extends Omit { @@ -44,3 +68,21 @@ export const toDataPlaneNode = (node: DataPlaneGqlNode): DataPlaneNode => { scope: node.isPublic ? 'public' : 'private', }; }; + +type PublicDataPlaneGqlNode = + PublicDataPlanesQuery['publicDataPlanes']['edges'][number]['node']; + +export interface PublicDataPlaneNode + extends Omit { + // Narrower than the schema's DataPlaneCloudProvider, which also allows LOCAL + cloudProvider: CloudProvider; +} + +export const toPublicDataPlaneNode = ( + node: PublicDataPlaneGqlNode +): PublicDataPlaneNode => { + return { + ...node, + cloudProvider: node.cloudProvider as CloudProvider, + }; +}; diff --git a/src/context/URQL.tsx b/src/context/URQL.tsx index a2b5a42271..06d6bd272b 100644 --- a/src/context/URQL.tsx +++ b/src/context/URQL.tsx @@ -64,6 +64,7 @@ function UrqlConfigProvider({ children }: BaseComponentProps) { RefreshTokenInfo: (_data) => null, StorageMapping: (data) => null, DataPlane: (data) => null, + PublicDataPlane: (data) => null, }, updates: { Mutation: { diff --git a/src/directives/BetaOnboard.tsx b/src/directives/BetaOnboard.tsx index 739bcd6f54..a8cf271fe4 100644 --- a/src/directives/BetaOnboard.tsx +++ b/src/directives/BetaOnboard.tsx @@ -12,9 +12,11 @@ import { submitDirective } from 'src/api/directives'; import RegistrationProgress from 'src/app/guards/RegistrationProgress'; import BetaWarningAndError from 'src/components/transformation/create/BetaWarningAndError'; import Actions from 'src/directives/Actions'; +import DataPlaneSelector from 'src/directives/Onboard/DataPlaneSelector'; import OrganizationNameField from 'src/directives/Onboard/OrganizationName'; import { useOnboardingStore_nameInvalid, + useOnboardingStore_requestedDataPlane, useOnboardingStore_requestedTenant, useOnboardingStore_resetState, useOnboardingStore_setNameMissing, @@ -39,13 +41,15 @@ const EVENT_NAME = 'Tenant:Create'; const submit_onboard = async ( requestedTenant: string, directive: any, - surveyResponse: any + surveyResponse: any, + requestedDataPlane: string | null ) => { return submitDirective( directiveName, directive, requestedTenant, - surveyResponse + surveyResponse, + requestedDataPlane ); }; @@ -60,6 +64,7 @@ const BetaOnboard = ({ directive, mutate, status }: DirectiveProps) => { const setNameMissing = useOnboardingStore_setNameMissing(); const setSurveyMissing = useOnboardingStore_setSurveyMissing(); const surveyResponse = useOnboardingStore_surveyResponse(); + const requestedDataPlane = useOnboardingStore_requestedDataPlane(); const resetOnboardingState = useOnboardingStore_resetState(); const setServerError = useOnboardingStore_setServerError(); @@ -100,7 +105,8 @@ const BetaOnboard = ({ directive, mutate, status }: DirectiveProps) => { const onboardingResponse = await submit_onboard( requestedTenant, directive, - surveyResponse + surveyResponse, + requestedDataPlane ); if (onboardingResponse.error) { @@ -194,6 +200,8 @@ const BetaOnboard = ({ directive, mutate, status }: DirectiveProps) => { > + + + `${option.region} (${option.name})`; + +function DataPlaneSelector() { + const intl = useIntl(); + const { dataPlanes, loading, error } = usePublicDataPlanes(); + + const selected = useOnboardingStore_requestedDataPlane(); + const setSelected = useOnboardingStore_setRequestedDataPlane(); + + // Sorted by provider first because groupBy only groups correctly when the + // list is already ordered by group; sorting on name alone worked only + // because the names happen to embed the provider. + const options = useMemo( + () => + [...dataPlanes].sort( + (a, b) => + a.cloudProvider.localeCompare(b.cloudProvider) || + a.region.localeCompare(b.region) + ), + [dataPlanes] + ); + + // Preselect the platform default so submitting without touching the + // picker still records an explicit, valid choice. + useEffect(() => { + if (!selected && options.length > 0) { + const preferred = + options.find( + (option) => option.name === DEFAULT_PUBLIC_DATA_PLANE + ) ?? options[0]; + setSelected(preferred.name); + } + }, [options, selected, setSelected]); + + // Fail safe: if the plane list can't be fetched, render nothing. The + // claim simply omits requestedDataPlane and the backend applies its + // own default instead of blocking signup on this field. + if (error || (!loading && options.length === 0)) { + return null; + } + + const currentOption = options.find((option) => option.name === selected); + + return ( + + + {intl.formatMessage({ id: 'tenant.dataPlane.label' })} + + + setSelected(value?.name ?? null)} + groupBy={(option) => option.cloudProvider} + getOptionLabel={optionLabel} + renderOption={(props, option) => { + const { key, ...rest } = props; + return ( +
  • + + + {optionLabel(option)} + +
  • + ); + }} + renderInput={(params) => ( + + )} + /> +
    + ); +} + +export default DataPlaneSelector; diff --git a/src/directives/Onboard/Store/create.ts b/src/directives/Onboard/Store/create.ts index f179b49c9b..a44f25ee64 100644 --- a/src/directives/Onboard/Store/create.ts +++ b/src/directives/Onboard/Store/create.ts @@ -17,6 +17,7 @@ const getInitialStateData = (): Pick< | 'nameProblematic' | 'nameMissing' | 'requestedTenant' + | 'requestedDataPlane' | 'surveyResponse' | 'surveyMissing' | 'serverError' @@ -25,6 +26,7 @@ const getInitialStateData = (): Pick< nameProblematic: false, nameMissing: false, requestedTenant: '', + requestedDataPlane: null, surveyResponse: { origin: '', details: '' }, surveyMissing: false, serverError: null, @@ -63,6 +65,16 @@ const getInitialState = (set: NamedSet): OnboardingState => ({ ); }, + setRequestedDataPlane: (value) => { + set( + () => ({ + requestedDataPlane: value, + }), + false, + 'setRequestedDataPlane' + ); + }, + setServerError: (value) => { set( (state) => ({ diff --git a/src/directives/Onboard/Store/hooks.ts b/src/directives/Onboard/Store/hooks.ts index 0ca47d5c56..34bd4cb59f 100644 --- a/src/directives/Onboard/Store/hooks.ts +++ b/src/directives/Onboard/Store/hooks.ts @@ -17,6 +17,20 @@ export const useOnboardingStore_setRequestedTenant = () => { >(OnboardingStoreNames.GENERAL, (state) => state.setRequestedTenant); }; +export const useOnboardingStore_requestedDataPlane = () => { + return useLocalZustandStore< + OnboardingState, + OnboardingState['requestedDataPlane'] + >(OnboardingStoreNames.GENERAL, (state) => state.requestedDataPlane); +}; + +export const useOnboardingStore_setRequestedDataPlane = () => { + return useLocalZustandStore< + OnboardingState, + OnboardingState['setRequestedDataPlane'] + >(OnboardingStoreNames.GENERAL, (state) => state.setRequestedDataPlane); +}; + export const useOnboardingStore_nameInvalid = () => { return useLocalZustandStore< OnboardingState, diff --git a/src/directives/Onboard/Store/types.ts b/src/directives/Onboard/Store/types.ts index 024164f69a..51a2889b3c 100644 --- a/src/directives/Onboard/Store/types.ts +++ b/src/directives/Onboard/Store/types.ts @@ -7,6 +7,9 @@ export interface OnboardingState { requestedTenant: string; setRequestedTenant: (value: string) => void; + requestedDataPlane: string | null; + setRequestedDataPlane: (value: string | null) => void; + nameInvalid: boolean; setNameInvalid: (value: boolean) => void; diff --git a/src/directives/shared.ts b/src/directives/shared.ts index eb0fcd5903..0e5d4ecad4 100644 --- a/src/directives/shared.ts +++ b/src/directives/shared.ts @@ -80,9 +80,17 @@ export const DIRECTIVES: Directives = { return queryBuilder; }, generateUserClaim: (args: any[]) => { + const [requestedTenant, survey, requestedDataPlane] = args; return { - requestedTenant: args[0], - survey: args.length > 1 ? args[1] : null, + requestedTenant, + survey: survey ?? null, + // Omitted (never sent as null) when unset: the agent's + // claims parser rejects unknown keys, and older agents + // don't know this one — leaving it out lets the backend + // fall back to its own default plane. + ...(hasLength(requestedDataPlane) + ? { requestedDataPlane } + : {}), }; }, calculateStatus: (appliedDirective) => { diff --git a/src/directives/types.ts b/src/directives/types.ts index 71c7b9be97..33b6f1a2e1 100644 --- a/src/directives/types.ts +++ b/src/directives/types.ts @@ -42,6 +42,7 @@ interface ClickToAcceptClaim { interface OnboardClaim { requestedTenant: string; survey: any; + requestedDataPlane?: string; } interface StorageMappingsClaim { diff --git a/src/gql-types/gql.ts b/src/gql-types/gql.ts index f3bf218a46..d6728d1315 100644 --- a/src/gql-types/gql.ts +++ b/src/gql-types/gql.ts @@ -22,6 +22,7 @@ type Documents = { "\n query ConnectorsGrid($filter: ConnectorsFilter, $after: String) {\n connectors(first: 500, after: $after, filter: $filter) {\n edges {\n cursor\n node {\n id\n imageName\n logoUrl\n title\n recommended\n detail\n defaultSpec {\n id\n imageTag\n documentationUrl\n protocol\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": typeof types.ConnectorsGridDocument, "\n query ConnectorTagData($imageName: String!, $fullImageName: String!) {\n connector(imageName: $imageName) {\n id\n imageName\n logoUrl\n title\n }\n connectorSpec(fullImageName: $fullImageName) {\n id\n imageTag\n defaultCaptureInterval\n disableBackfill\n documentationUrl\n endpointSpecSchema\n resourceSpecSchema\n protocol\n }\n }\n": typeof types.ConnectorTagDataDocument, "\n query DataPlanes($after: String) {\n dataPlanes(first: 100, after: $after) {\n edges {\n node {\n name\n cloudProvider\n region\n isPublic\n fqdn\n cidrBlocks\n awsIamUserArn\n gcpServiceAccountEmail\n azureApplicationClientId\n azureApplicationName\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": typeof types.DataPlanesDocument, + "\n query PublicDataPlanes($after: String) {\n publicDataPlanes(first: 100, after: $after) {\n edges {\n node {\n name\n cloudProvider\n region\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": typeof types.PublicDataPlanesDocument, "\n query InviteLinks($first: Int, $after: String) {\n inviteLinks(first: $first, after: $after) {\n edges {\n node {\n token\n ssoProviderId\n catalogPrefix\n capability\n singleUse\n detail\n createdAt\n }\n cursor\n }\n pageInfo {\n ...PageInfoFields\n }\n }\n }\n": typeof types.InviteLinksDocument, "\n mutation CreateInviteLink(\n $catalogPrefix: Prefix!\n $capability: Capability!\n $singleUse: Boolean!\n $detail: String\n ) {\n createInviteLink(\n catalogPrefix: $catalogPrefix\n capability: $capability\n singleUse: $singleUse\n detail: $detail\n ) {\n token\n catalogPrefix\n capability\n singleUse\n detail\n createdAt\n }\n }\n": typeof types.CreateInviteLinkDocument, "\n mutation DeleteInviteLink($token: UUID!) {\n deleteInviteLink(token: $token)\n }\n": typeof types.DeleteInviteLinkDocument, @@ -50,6 +51,7 @@ const documents: Documents = { "\n query ConnectorsGrid($filter: ConnectorsFilter, $after: String) {\n connectors(first: 500, after: $after, filter: $filter) {\n edges {\n cursor\n node {\n id\n imageName\n logoUrl\n title\n recommended\n detail\n defaultSpec {\n id\n imageTag\n documentationUrl\n protocol\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": types.ConnectorsGridDocument, "\n query ConnectorTagData($imageName: String!, $fullImageName: String!) {\n connector(imageName: $imageName) {\n id\n imageName\n logoUrl\n title\n }\n connectorSpec(fullImageName: $fullImageName) {\n id\n imageTag\n defaultCaptureInterval\n disableBackfill\n documentationUrl\n endpointSpecSchema\n resourceSpecSchema\n protocol\n }\n }\n": types.ConnectorTagDataDocument, "\n query DataPlanes($after: String) {\n dataPlanes(first: 100, after: $after) {\n edges {\n node {\n name\n cloudProvider\n region\n isPublic\n fqdn\n cidrBlocks\n awsIamUserArn\n gcpServiceAccountEmail\n azureApplicationClientId\n azureApplicationName\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": types.DataPlanesDocument, + "\n query PublicDataPlanes($after: String) {\n publicDataPlanes(first: 100, after: $after) {\n edges {\n node {\n name\n cloudProvider\n region\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": types.PublicDataPlanesDocument, "\n query InviteLinks($first: Int, $after: String) {\n inviteLinks(first: $first, after: $after) {\n edges {\n node {\n token\n ssoProviderId\n catalogPrefix\n capability\n singleUse\n detail\n createdAt\n }\n cursor\n }\n pageInfo {\n ...PageInfoFields\n }\n }\n }\n": types.InviteLinksDocument, "\n mutation CreateInviteLink(\n $catalogPrefix: Prefix!\n $capability: Capability!\n $singleUse: Boolean!\n $detail: String\n ) {\n createInviteLink(\n catalogPrefix: $catalogPrefix\n capability: $capability\n singleUse: $singleUse\n detail: $detail\n ) {\n token\n catalogPrefix\n capability\n singleUse\n detail\n createdAt\n }\n }\n": types.CreateInviteLinkDocument, "\n mutation DeleteInviteLink($token: UUID!) {\n deleteInviteLink(token: $token)\n }\n": types.DeleteInviteLinkDocument, @@ -116,6 +118,10 @@ export function graphql(source: "\n query ConnectorTagData($imageName: String * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n query DataPlanes($after: String) {\n dataPlanes(first: 100, after: $after) {\n edges {\n node {\n name\n cloudProvider\n region\n isPublic\n fqdn\n cidrBlocks\n awsIamUserArn\n gcpServiceAccountEmail\n azureApplicationClientId\n azureApplicationName\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n"): (typeof documents)["\n query DataPlanes($after: String) {\n dataPlanes(first: 100, after: $after) {\n edges {\n node {\n name\n cloudProvider\n region\n isPublic\n fqdn\n cidrBlocks\n awsIamUserArn\n gcpServiceAccountEmail\n azureApplicationClientId\n azureApplicationName\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query PublicDataPlanes($after: String) {\n publicDataPlanes(first: 100, after: $after) {\n edges {\n node {\n name\n cloudProvider\n region\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n"): (typeof documents)["\n query PublicDataPlanes($after: String) {\n publicDataPlanes(first: 100, after: $after) {\n edges {\n node {\n name\n cloudProvider\n region\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/src/gql-types/graphql.ts b/src/gql-types/graphql.ts index fc207e4b76..fa5e841770 100644 --- a/src/gql-types/graphql.ts +++ b/src/gql-types/graphql.ts @@ -2443,6 +2443,13 @@ export type DataPlanesQueryVariables = Exact<{ export type DataPlanesQuery = { __typename?: 'QueryRoot', dataPlanes: { __typename?: 'DataPlaneConnection', edges: Array<{ __typename?: 'DataPlaneEdge', node: { __typename?: 'DataPlane', name: string, cloudProvider: DataPlaneCloudProvider, region: string, isPublic: boolean, fqdn: string, cidrBlocks: Array, awsIamUserArn?: string | null, gcpServiceAccountEmail?: string | null, azureApplicationClientId?: string | null, azureApplicationName?: string | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null } } }; +export type PublicDataPlanesQueryVariables = Exact<{ + after?: InputMaybe; +}>; + + +export type PublicDataPlanesQuery = { __typename?: 'QueryRoot', publicDataPlanes: { __typename?: 'PublicDataPlaneConnection', edges: Array<{ __typename?: 'PublicDataPlaneEdge', node: { __typename?: 'PublicDataPlane', name: string, cloudProvider: DataPlaneCloudProvider, region: string } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null } } }; + export type InviteLinksQueryVariables = Exact<{ first?: InputMaybe; after?: InputMaybe; @@ -2589,6 +2596,7 @@ export const AlertTypeDocument = {"kind":"Document","definitions":[{"kind":"Oper export const ConnectorsGridDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ConnectorsGrid"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ConnectorsFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectors"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"500"}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"imageName"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"recommended"}},{"kind":"Field","name":{"kind":"Name","value":"detail"}},{"kind":"Field","name":{"kind":"Name","value":"defaultSpec"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"imageTag"}},{"kind":"Field","name":{"kind":"Name","value":"documentationUrl"}},{"kind":"Field","name":{"kind":"Name","value":"protocol"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode; export const ConnectorTagDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ConnectorTagData"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"imageName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fullImageName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connector"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"imageName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"imageName"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"imageName"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}},{"kind":"Field","name":{"kind":"Name","value":"connectorSpec"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"fullImageName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fullImageName"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"imageTag"}},{"kind":"Field","name":{"kind":"Name","value":"defaultCaptureInterval"}},{"kind":"Field","name":{"kind":"Name","value":"disableBackfill"}},{"kind":"Field","name":{"kind":"Name","value":"documentationUrl"}},{"kind":"Field","name":{"kind":"Name","value":"endpointSpecSchema"}},{"kind":"Field","name":{"kind":"Name","value":"resourceSpecSchema"}},{"kind":"Field","name":{"kind":"Name","value":"protocol"}}]}}]}}]} as unknown as DocumentNode; export const DataPlanesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DataPlanes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dataPlanes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"cloudProvider"}},{"kind":"Field","name":{"kind":"Name","value":"region"}},{"kind":"Field","name":{"kind":"Name","value":"isPublic"}},{"kind":"Field","name":{"kind":"Name","value":"fqdn"}},{"kind":"Field","name":{"kind":"Name","value":"cidrBlocks"}},{"kind":"Field","name":{"kind":"Name","value":"awsIamUserArn"}},{"kind":"Field","name":{"kind":"Name","value":"gcpServiceAccountEmail"}},{"kind":"Field","name":{"kind":"Name","value":"azureApplicationClientId"}},{"kind":"Field","name":{"kind":"Name","value":"azureApplicationName"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode; +export const PublicDataPlanesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PublicDataPlanes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publicDataPlanes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"cloudProvider"}},{"kind":"Field","name":{"kind":"Name","value":"region"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode; export const InviteLinksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"InviteLinks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inviteLinks"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"ssoProviderId"}},{"kind":"Field","name":{"kind":"Name","value":"catalogPrefix"}},{"kind":"Field","name":{"kind":"Name","value":"capability"}},{"kind":"Field","name":{"kind":"Name","value":"singleUse"}},{"kind":"Field","name":{"kind":"Name","value":"detail"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PageInfoFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PageInfoFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]} as unknown as DocumentNode; export const CreateInviteLinkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateInviteLink"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"catalogPrefix"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Prefix"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"capability"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Capability"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"singleUse"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"detail"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createInviteLink"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"catalogPrefix"},"value":{"kind":"Variable","name":{"kind":"Name","value":"catalogPrefix"}}},{"kind":"Argument","name":{"kind":"Name","value":"capability"},"value":{"kind":"Variable","name":{"kind":"Name","value":"capability"}}},{"kind":"Argument","name":{"kind":"Name","value":"singleUse"},"value":{"kind":"Variable","name":{"kind":"Name","value":"singleUse"}}},{"kind":"Argument","name":{"kind":"Name","value":"detail"},"value":{"kind":"Variable","name":{"kind":"Name","value":"detail"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"catalogPrefix"}},{"kind":"Field","name":{"kind":"Name","value":"capability"}},{"kind":"Field","name":{"kind":"Name","value":"singleUse"}},{"kind":"Field","name":{"kind":"Name","value":"detail"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const DeleteInviteLinkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteInviteLink"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteInviteLink"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}]}]}}]} as unknown as DocumentNode; diff --git a/src/hooks/dataPlanes/usePublicDataPlanes.ts b/src/hooks/dataPlanes/usePublicDataPlanes.ts new file mode 100644 index 0000000000..52a29c8256 --- /dev/null +++ b/src/hooks/dataPlanes/usePublicDataPlanes.ts @@ -0,0 +1,21 @@ +import { + PUBLIC_DATA_PLANES_QUERY, + toPublicDataPlaneNode, +} from 'src/api/gql/dataPlanes'; +import { useAllPages } from 'src/api/gql/useAllPages'; + +// Public planes for the pre-tenant onboarding flow. The authenticated +// `dataPlanes` query is useless here: a brand-new user has no grants yet, +// so it returns nothing until the tenant is provisioned. +export function usePublicDataPlanes() { + const { + data: dataPlanes, + loading, + error, + } = useAllPages(PUBLIC_DATA_PLANES_QUERY, { + getConnection: (data) => data.publicDataPlanes, + transform: toPublicDataPlaneNode, + }); + + return { dataPlanes, loading, error }; +} diff --git a/src/lang/en-US/Authentication.ts b/src/lang/en-US/Authentication.ts index 76460be753..aea3ff10cf 100644 --- a/src/lang/en-US/Authentication.ts +++ b/src/lang/en-US/Authentication.ts @@ -106,6 +106,8 @@ export const Authentication: Record = { 'tenant.input.label': `Organization Name`, 'tenant.input.placeholder': `acmeCo`, 'tenant.errorMessage.empty': `You must provide an organization name before continuing.`, + 'tenant.dataPlane.label': `Data Plane`, + 'tenant.dataPlane.helper': `Where your data is processed and stored. Pick the region closest to your data sources.`, 'tenant.errorMessage.invalid': `Your organization name is invalid.`, 'tenant.origin.errorMessage.empty': `Please let us know where you heard about us.`, 'tenant.warningMessage.problematic': `Looks like your organization name contains "test". This is generally not recommended as you cannot rename your organization later.`,