From 744b602beebc94cba8058fc93701f29890200474 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 11:11:05 +0200 Subject: [PATCH 01/17] refactor oauth consent --- www/src/components/oidc/OAuthConsent.tsx | 39 +++++++++++---- www/src/components/oidc/queries.ts | 33 ------------ www/src/generated/graphql.ts | 64 ++++++++++++++++++++++++ www/src/graph/oauth.graphql | 19 +++++++ 4 files changed, 111 insertions(+), 44 deletions(-) diff --git a/www/src/components/oidc/OAuthConsent.tsx b/www/src/components/oidc/OAuthConsent.tsx index 525e20254..05801423b 100644 --- a/www/src/components/oidc/OAuthConsent.tsx +++ b/www/src/components/oidc/OAuthConsent.tsx @@ -1,6 +1,6 @@ import { useLocation, useNavigate } from 'react-router-dom' import { useMutation, useQuery } from '@apollo/client' -import queryString from 'query-string' +import queryString, { ParsedQuery } from 'query-string' import { ArrowRightLeftIcon, Button, IconFrame } from '@pluralsh/design-system' import { useCallback } from 'react' import { A, Flex, Span } from 'honorable' @@ -10,12 +10,13 @@ import { useTheme } from 'styled-components' import { LoginPortal } from '../users/LoginPortal' import { GqlError } from '../utils/Alert' import { PLURAL_MARK, PLURAL_MARK_WHITE } from '../constants' -import { useMeQuery } from '../../generated/graphql' +import { useMeQuery, useOidcConsentQuery } from '../../generated/graphql' import { clearLocalStorage } from '../../helpers/localStorage' import LoadingIndicator from '../utils/LoadingIndicator' -import { GET_OIDC_CONSENT, OAUTH_CONSENT } from './queries' +import { OAUTH_CONSENT } from './queries' +import { isEmpty } from 'lodash' function Icon({ icon, darkIcon }: any) { const dark = useTheme().mode !== 'light' @@ -36,12 +37,22 @@ function Icon({ icon, darkIcon }: any) { ) } +const getChallenge = (parsedQueryString: ParsedQuery): string => { + const challenge = parsedQueryString.consent_challenge + + if (Array.isArray(challenge)) { + return !isEmpty(challenge) ? challenge[0] ?? '' : '' + } + + return challenge ?? '' +} + export function OAuthConsent() { const location = useLocation() const navigate = useNavigate() const { data: userData, loading: userLoading } = useMeQuery() - const { consent_challenge: challenge } = queryString.parse(location.search) - const { data } = useQuery(GET_OIDC_CONSENT, { variables: { challenge } }) + const challenge = getChallenge(queryString.parse(location.search)) + const { data } = useOidcConsentQuery({ variables: { challenge } }) const repository = data?.oidcConsent?.repository const consent = data?.oidcConsent?.consent const [mutation, { loading, error }] = useMutation(OAUTH_CONSENT, { @@ -80,11 +91,15 @@ export function OAuthConsent() { icon={PLURAL_MARK} darkIcon={PLURAL_MARK_WHITE} /> - - + {repository && ( + <> + + + + )} - {StartCase(repository.name)} requires access + {repository?.name + ? `${StartCase(repository.name)} requires access` + : 'Access required'} ; + + +export type OidcConsentQuery = { __typename?: 'RootQueryType', oidcConsent?: { __typename?: 'OidcStepResponse', repository?: { __typename?: 'Repository', name: string, icon?: string | null, darkIcon?: string | null } | null, consent?: { __typename?: 'ConsentRequest', requestedScope?: Array | null, skip?: boolean | null } | null } | null }; + export type LimitFragment = { __typename?: 'Limit', dimension: string, quantity: number }; export type LineItemFragment = { __typename?: 'LineItem', name: string, dimension: string, cost: number, period?: string | null, type?: PlanType | null }; @@ -7085,6 +7094,13 @@ export const OAuthInfoFragmentDoc = gql` authorizeUrl } `; +export const RepositoryFragmentDoc = gql` + fragment Repository on Repository { + name + icon + darkIcon +} + `; export const SubscriptionFragmentDoc = gql` fragment Subscription on RepositorySubscription { id @@ -8802,6 +8818,52 @@ export function useCreateKeyBackupMutation(baseOptions?: Apollo.MutationHookOpti export type CreateKeyBackupMutationHookResult = ReturnType; export type CreateKeyBackupMutationResult = Apollo.MutationResult; export type CreateKeyBackupMutationOptions = Apollo.BaseMutationOptions; +export const OidcConsentDocument = gql` + query OIDCConsent($challenge: String!) { + oidcConsent(challenge: $challenge) { + repository { + ...Repository + } + consent { + requestedScope + skip + } + } +} + ${RepositoryFragmentDoc}`; + +/** + * __useOidcConsentQuery__ + * + * To run a query within a React component, call `useOidcConsentQuery` and pass it any options that fit your needs. + * When your component renders, `useOidcConsentQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useOidcConsentQuery({ + * variables: { + * challenge: // value for 'challenge' + * }, + * }); + */ +export function useOidcConsentQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(OidcConsentDocument, options); + } +export function useOidcConsentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(OidcConsentDocument, options); + } +export function useOidcConsentSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(OidcConsentDocument, options); + } +export type OidcConsentQueryHookResult = ReturnType; +export type OidcConsentLazyQueryHookResult = ReturnType; +export type OidcConsentSuspenseQueryHookResult = ReturnType; +export type OidcConsentQueryResult = Apollo.QueryResult; export const SubscriptionDocument = gql` query Subscription { account { @@ -11579,6 +11641,7 @@ export const namedOperations = { Invite: 'Invite', KeyBackups: 'KeyBackups', KeyBackup: 'KeyBackup', + OIDCConsent: 'OIDCConsent', Subscription: 'Subscription', Cards: 'Cards', Invoices: 'Invoices', @@ -11712,6 +11775,7 @@ export const namedOperations = { PageInfo: 'PageInfo', OIDCProvider: 'OIDCProvider', OAuthInfo: 'OAuthInfo', + Repository: 'Repository', Limit: 'Limit', LineItem: 'LineItem', ServiceLevel: 'ServiceLevel', diff --git a/www/src/graph/oauth.graphql b/www/src/graph/oauth.graphql index 655473da9..9644453ee 100644 --- a/www/src/graph/oauth.graphql +++ b/www/src/graph/oauth.graphql @@ -30,3 +30,22 @@ fragment OAuthInfo on OauthInfo { provider authorizeUrl } + + +fragment Repository on Repository { + name + icon + darkIcon +} + +query OIDCConsent($challenge: String!) { + oidcConsent(challenge: $challenge) { + repository { + ...Repository + } + consent { + requestedScope + skip + } + } +} From 5dc879edff065e690f5f19a7926b1d94dff10cde Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 11:14:13 +0200 Subject: [PATCH 02/17] refactor oauth consent mutation --- www/src/components/oidc/OAuthConsent.tsx | 17 ++++++---- www/src/components/oidc/queries.ts | 8 ----- www/src/generated/graphql.ts | 43 ++++++++++++++++++++++++ www/src/graph/oauth.graphql | 6 ++++ 4 files changed, 59 insertions(+), 15 deletions(-) diff --git a/www/src/components/oidc/OAuthConsent.tsx b/www/src/components/oidc/OAuthConsent.tsx index 05801423b..cd3e64975 100644 --- a/www/src/components/oidc/OAuthConsent.tsx +++ b/www/src/components/oidc/OAuthConsent.tsx @@ -10,12 +10,13 @@ import { useTheme } from 'styled-components' import { LoginPortal } from '../users/LoginPortal' import { GqlError } from '../utils/Alert' import { PLURAL_MARK, PLURAL_MARK_WHITE } from '../constants' -import { useMeQuery, useOidcConsentQuery } from '../../generated/graphql' +import { + useConsentMutation, + useMeQuery, + useOidcConsentQuery, +} from '../../generated/graphql' import { clearLocalStorage } from '../../helpers/localStorage' - import LoadingIndicator from '../utils/LoadingIndicator' - -import { OAUTH_CONSENT } from './queries' import { isEmpty } from 'lodash' function Icon({ icon, darkIcon }: any) { @@ -55,13 +56,15 @@ export function OAuthConsent() { const { data } = useOidcConsentQuery({ variables: { challenge } }) const repository = data?.oidcConsent?.repository const consent = data?.oidcConsent?.consent - const [mutation, { loading, error }] = useMutation(OAUTH_CONSENT, { + const [mutation, { loading, error }] = useConsentMutation({ variables: { challenge, scopes: consent?.requestedScope || ['profile', 'openid'], }, - onCompleted: ({ oauthConsent: { redirectTo } }) => { - window.location = redirectTo + onCompleted: ({ oauthConsent }) => { + if (oauthConsent?.redirectTo) { + window.location.href = oauthConsent.redirectTo + } }, }) diff --git a/www/src/components/oidc/queries.ts b/www/src/components/oidc/queries.ts index a44421c62..3958f3be5 100644 --- a/www/src/components/oidc/queries.ts +++ b/www/src/components/oidc/queries.ts @@ -19,11 +19,3 @@ export const UPDATE_PROVIDER = gql` } ${OIDCProvider} ` - -export const OAUTH_CONSENT = gql` - mutation Consent($challenge: String!, $scopes: [String]) { - oauthConsent(challenge: $challenge, scopes: $scopes) { - redirectTo - } - } -` diff --git a/www/src/generated/graphql.ts b/www/src/generated/graphql.ts index 07ccf4638..cf00816b1 100644 --- a/www/src/generated/graphql.ts +++ b/www/src/generated/graphql.ts @@ -5597,6 +5597,14 @@ export type OidcConsentQueryVariables = Exact<{ export type OidcConsentQuery = { __typename?: 'RootQueryType', oidcConsent?: { __typename?: 'OidcStepResponse', repository?: { __typename?: 'Repository', name: string, icon?: string | null, darkIcon?: string | null } | null, consent?: { __typename?: 'ConsentRequest', requestedScope?: Array | null, skip?: boolean | null } | null } | null }; +export type ConsentMutationVariables = Exact<{ + challenge: Scalars['String']['input']; + scopes?: InputMaybe> | InputMaybe>; +}>; + + +export type ConsentMutation = { __typename?: 'RootMutationType', oauthConsent?: { __typename?: 'OauthResponse', redirectTo: string } | null }; + export type LimitFragment = { __typename?: 'Limit', dimension: string, quantity: number }; export type LineItemFragment = { __typename?: 'LineItem', name: string, dimension: string, cost: number, period?: string | null, type?: PlanType | null }; @@ -8864,6 +8872,40 @@ export type OidcConsentQueryHookResult = ReturnType; export type OidcConsentLazyQueryHookResult = ReturnType; export type OidcConsentSuspenseQueryHookResult = ReturnType; export type OidcConsentQueryResult = Apollo.QueryResult; +export const ConsentDocument = gql` + mutation Consent($challenge: String!, $scopes: [String]) { + oauthConsent(challenge: $challenge, scopes: $scopes) { + redirectTo + } +} + `; +export type ConsentMutationFn = Apollo.MutationFunction; + +/** + * __useConsentMutation__ + * + * To run a mutation, you first call `useConsentMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useConsentMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [consentMutation, { data, loading, error }] = useConsentMutation({ + * variables: { + * challenge: // value for 'challenge' + * scopes: // value for 'scopes' + * }, + * }); + */ +export function useConsentMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(ConsentDocument, options); + } +export type ConsentMutationHookResult = ReturnType; +export type ConsentMutationResult = Apollo.MutationResult; +export type ConsentMutationOptions = Apollo.BaseMutationOptions; export const SubscriptionDocument = gql` query Subscription { account { @@ -11688,6 +11730,7 @@ export const namedOperations = { CreateInvite: 'CreateInvite', DeleteKeyBackup: 'DeleteKeyBackup', CreateKeyBackup: 'CreateKeyBackup', + Consent: 'Consent', UpdateAccountBilling: 'UpdateAccountBilling', CreatePlatformSubscription: 'CreatePlatformSubscription', DowngradeToFreePlanMutation: 'DowngradeToFreePlanMutation', diff --git a/www/src/graph/oauth.graphql b/www/src/graph/oauth.graphql index 9644453ee..fee05b6c0 100644 --- a/www/src/graph/oauth.graphql +++ b/www/src/graph/oauth.graphql @@ -49,3 +49,9 @@ query OIDCConsent($challenge: String!) { } } } + +mutation Consent($challenge: String!, $scopes: [String]) { + oauthConsent(challenge: $challenge, scopes: $scopes) { + redirectTo + } +} From 8a7f733b0d1c796b5a6c156e462c3021f073d244 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 11:19:25 +0200 Subject: [PATCH 03/17] refactor oauth consent types --- www/src/components/oidc/OAuthConsent.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/www/src/components/oidc/OAuthConsent.tsx b/www/src/components/oidc/OAuthConsent.tsx index cd3e64975..938f18b95 100644 --- a/www/src/components/oidc/OAuthConsent.tsx +++ b/www/src/components/oidc/OAuthConsent.tsx @@ -1,5 +1,4 @@ import { useLocation, useNavigate } from 'react-router-dom' -import { useMutation, useQuery } from '@apollo/client' import queryString, { ParsedQuery } from 'query-string' import { ArrowRightLeftIcon, Button, IconFrame } from '@pluralsh/design-system' import { useCallback } from 'react' @@ -19,14 +18,21 @@ import { clearLocalStorage } from '../../helpers/localStorage' import LoadingIndicator from '../utils/LoadingIndicator' import { isEmpty } from 'lodash' -function Icon({ icon, darkIcon }: any) { +function Icon({ + icon, + darkIcon, +}: { + icon: Nullable + darkIcon: Nullable +}) { const dark = useTheme().mode !== 'light' + const src = dark ? darkIcon ?? icon : icon - return ( + return src ? ( @@ -35,7 +41,7 @@ function Icon({ icon, darkIcon }: any) { height="64px" type="floating" /> - ) + ) : null } const getChallenge = (parsedQueryString: ParsedQuery): string => { From 624a327de2467276869d27f6f7001381aadd871f Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 11:23:51 +0200 Subject: [PATCH 04/17] use typed queries --- www/src/components/app/oidc/OIDC.tsx | 13 +++-- www/src/components/oidc/queries.ts | 21 ------- www/src/generated/graphql.ts | 86 ++++++++++++++++++++++++++++ www/src/graph/oauth.graphql | 12 ++++ 4 files changed, 106 insertions(+), 26 deletions(-) delete mode 100644 www/src/components/oidc/queries.ts diff --git a/www/src/components/app/oidc/OIDC.tsx b/www/src/components/app/oidc/OIDC.tsx index 36c5dc183..b5314315e 100644 --- a/www/src/components/app/oidc/OIDC.tsx +++ b/www/src/components/app/oidc/OIDC.tsx @@ -33,13 +33,16 @@ import { deepUpdate, updateCache } from '../../../utils/graphql' import InviteUserModal from '../../account/invite/InviteUserModal' import { BindingInput, fetchGroups, fetchUsers } from '../../account/Typeaheads' import { sanitize } from '../../account/utils' -import { CREATE_PROVIDER, UPDATE_PROVIDER } from '../../oidc/queries' import { AuthMethod } from '../../oidc/types' import { REPO_Q } from '../../repository/packages/queries' import { GqlError } from '../../utils/Alert' import CreateGroupModal from '../../utils/group/CreateGroupModal' import ImpersonateServiceAccount from '../../utils/ImpersonateServiceAccount' import { AppHeaderActions } from '../AppHeaderActions' +import { + useCreateProviderMutation, + useUpdateProviderMutation, +} from '../../../generated/graphql' export function UrlsInput({ uriFormat = '', urls, setUrls }: any) { const [baseScheme, basePath] = ['https://', '/oauth2/callback'] @@ -310,12 +313,12 @@ export function CreateProvider({ installation }: any) { authMethod: settings.authMethod || AuthMethod.POST, }) const [bindings, setBindings] = useState([]) - const [mutation, { loading, error }] = useMutation(CREATE_PROVIDER, { + const [mutation, { loading, error }] = useCreateProviderMutation({ variables: { id: installation.id, attributes: { ...attributes, bindings: bindings.map(sanitize) }, }, - update: (cache, { data: { createOidcProvider } }) => + update: (cache, { data }) => updateCache(cache, { query: REPO_Q, variables: { repositoryId: installation.repository.id }, @@ -323,7 +326,7 @@ export function CreateProvider({ installation }: any) { deepUpdate( prev, 'repository.installation.oidcProvider', - () => createOidcProvider + () => data?.createOidcProvider ), }), }) @@ -380,7 +383,7 @@ export function UpdateProvider({ installation }: any) { ModalSelection.None ) - const [mutation, { loading, error }] = useMutation(UPDATE_PROVIDER, { + const [mutation, { loading, error }] = useUpdateProviderMutation({ variables: { id: installation.id, attributes: { diff --git a/www/src/components/oidc/queries.ts b/www/src/components/oidc/queries.ts deleted file mode 100644 index 3958f3be5..000000000 --- a/www/src/components/oidc/queries.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { gql } from '@apollo/client' - -import { OIDCProvider } from '../../models/oauth' - -export const CREATE_PROVIDER = gql` - mutation Provider($id: ID!, $attributes: OidcAttributes!) { - createOidcProvider(installationId: $id, attributes: $attributes) { - ...OIDCProvider - } - } - ${OIDCProvider} -` - -export const UPDATE_PROVIDER = gql` - mutation Update($id: ID!, $attributes: OidcAttributes!) { - updateOidcProvider(installationId: $id, attributes: $attributes) { - ...OIDCProvider - } - } - ${OIDCProvider} -` diff --git a/www/src/generated/graphql.ts b/www/src/generated/graphql.ts index cf00816b1..aab0867d0 100644 --- a/www/src/generated/graphql.ts +++ b/www/src/generated/graphql.ts @@ -5605,6 +5605,22 @@ export type ConsentMutationVariables = Exact<{ export type ConsentMutation = { __typename?: 'RootMutationType', oauthConsent?: { __typename?: 'OauthResponse', redirectTo: string } | null }; +export type CreateProviderMutationVariables = Exact<{ + id: Scalars['ID']['input']; + attributes: OidcAttributes; +}>; + + +export type CreateProviderMutation = { __typename?: 'RootMutationType', createOidcProvider?: { __typename?: 'OidcProvider', id: string, clientId: string, authMethod: OidcAuthMethod, clientSecret: string, redirectUris?: Array | null, bindings?: Array<{ __typename?: 'OidcProviderBinding', id: string, user?: { __typename?: 'User', id: string, name: string, email: string, avatar?: string | null, provider?: Provider | null, demoed?: boolean | null, onboarding?: OnboardingState | null, emailConfirmed?: boolean | null, emailConfirmBy?: Date | null, backgroundColor?: string | null, serviceAccount?: boolean | null, hasInstallations?: boolean | null, hasShell?: boolean | null, onboardingChecklist?: { __typename?: 'OnboardingChecklist', dismissed?: boolean | null, status?: OnboardingChecklistState | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null, roles?: { __typename?: 'Roles', admin?: boolean | null } | null, groups?: Array<{ __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null> | null, impersonationPolicy?: { __typename?: 'ImpersonationPolicy', id: string, bindings?: Array<{ __typename?: 'ImpersonationPolicyBinding', id: string, group?: { __typename?: 'Group', id: string, name: string } | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null } | null> | null } | null } | null, group?: { __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null } | null> | null, configuration?: { __typename?: 'OuathConfiguration', issuer?: string | null, authorizationEndpoint?: string | null, tokenEndpoint?: string | null, jwksUri?: string | null, userinfoEndpoint?: string | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null } | null }; + +export type UpdateProviderMutationVariables = Exact<{ + id: Scalars['ID']['input']; + attributes: OidcAttributes; +}>; + + +export type UpdateProviderMutation = { __typename?: 'RootMutationType', updateOidcProvider?: { __typename?: 'OidcProvider', id: string, clientId: string, authMethod: OidcAuthMethod, clientSecret: string, redirectUris?: Array | null, bindings?: Array<{ __typename?: 'OidcProviderBinding', id: string, user?: { __typename?: 'User', id: string, name: string, email: string, avatar?: string | null, provider?: Provider | null, demoed?: boolean | null, onboarding?: OnboardingState | null, emailConfirmed?: boolean | null, emailConfirmBy?: Date | null, backgroundColor?: string | null, serviceAccount?: boolean | null, hasInstallations?: boolean | null, hasShell?: boolean | null, onboardingChecklist?: { __typename?: 'OnboardingChecklist', dismissed?: boolean | null, status?: OnboardingChecklistState | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null, roles?: { __typename?: 'Roles', admin?: boolean | null } | null, groups?: Array<{ __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null> | null, impersonationPolicy?: { __typename?: 'ImpersonationPolicy', id: string, bindings?: Array<{ __typename?: 'ImpersonationPolicyBinding', id: string, group?: { __typename?: 'Group', id: string, name: string } | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null } | null> | null } | null } | null, group?: { __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null } | null> | null, configuration?: { __typename?: 'OuathConfiguration', issuer?: string | null, authorizationEndpoint?: string | null, tokenEndpoint?: string | null, jwksUri?: string | null, userinfoEndpoint?: string | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null } | null }; + export type LimitFragment = { __typename?: 'Limit', dimension: string, quantity: number }; export type LineItemFragment = { __typename?: 'LineItem', name: string, dimension: string, cost: number, period?: string | null, type?: PlanType | null }; @@ -8906,6 +8922,74 @@ export function useConsentMutation(baseOptions?: Apollo.MutationHookOptions; export type ConsentMutationResult = Apollo.MutationResult; export type ConsentMutationOptions = Apollo.BaseMutationOptions; +export const CreateProviderDocument = gql` + mutation CreateProvider($id: ID!, $attributes: OidcAttributes!) { + createOidcProvider(installationId: $id, attributes: $attributes) { + ...OIDCProvider + } +} + ${OidcProviderFragmentDoc}`; +export type CreateProviderMutationFn = Apollo.MutationFunction; + +/** + * __useCreateProviderMutation__ + * + * To run a mutation, you first call `useCreateProviderMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateProviderMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createProviderMutation, { data, loading, error }] = useCreateProviderMutation({ + * variables: { + * id: // value for 'id' + * attributes: // value for 'attributes' + * }, + * }); + */ +export function useCreateProviderMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateProviderDocument, options); + } +export type CreateProviderMutationHookResult = ReturnType; +export type CreateProviderMutationResult = Apollo.MutationResult; +export type CreateProviderMutationOptions = Apollo.BaseMutationOptions; +export const UpdateProviderDocument = gql` + mutation UpdateProvider($id: ID!, $attributes: OidcAttributes!) { + updateOidcProvider(installationId: $id, attributes: $attributes) { + ...OIDCProvider + } +} + ${OidcProviderFragmentDoc}`; +export type UpdateProviderMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateProviderMutation__ + * + * To run a mutation, you first call `useUpdateProviderMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateProviderMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateProviderMutation, { data, loading, error }] = useUpdateProviderMutation({ + * variables: { + * id: // value for 'id' + * attributes: // value for 'attributes' + * }, + * }); + */ +export function useUpdateProviderMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateProviderDocument, options); + } +export type UpdateProviderMutationHookResult = ReturnType; +export type UpdateProviderMutationResult = Apollo.MutationResult; +export type UpdateProviderMutationOptions = Apollo.BaseMutationOptions; export const SubscriptionDocument = gql` query Subscription { account { @@ -11731,6 +11815,8 @@ export const namedOperations = { DeleteKeyBackup: 'DeleteKeyBackup', CreateKeyBackup: 'CreateKeyBackup', Consent: 'Consent', + CreateProvider: 'CreateProvider', + UpdateProvider: 'UpdateProvider', UpdateAccountBilling: 'UpdateAccountBilling', CreatePlatformSubscription: 'CreatePlatformSubscription', DowngradeToFreePlanMutation: 'DowngradeToFreePlanMutation', diff --git a/www/src/graph/oauth.graphql b/www/src/graph/oauth.graphql index fee05b6c0..2c8c5b5b3 100644 --- a/www/src/graph/oauth.graphql +++ b/www/src/graph/oauth.graphql @@ -55,3 +55,15 @@ mutation Consent($challenge: String!, $scopes: [String]) { redirectTo } } + +mutation CreateProvider($id: ID!, $attributes: OidcAttributes!) { + createOidcProvider(installationId: $id, attributes: $attributes) { + ...OIDCProvider + } +} + +mutation UpdateProvider($id: ID!, $attributes: OidcAttributes!) { + updateOidcProvider(installationId: $id, attributes: $attributes) { + ...OIDCProvider + } +} From bf0e638599a857015f7a8373f3fb25a7f66498d2 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 11:33:10 +0200 Subject: [PATCH 05/17] use typed queries --- www/src/components/shell/queries.ts | 50 --------------- www/src/components/stack/Stack.tsx | 23 ++++--- www/src/components/stack/queries.ts | 12 ---- www/src/components/stack/types.ts | 4 +- www/src/components/users/queries.ts | 96 ----------------------------- www/src/generated/graphql.ts | 34 ++++++---- www/src/graph/recipes.graphql | 22 ++++--- www/src/models/repo.ts | 29 --------- 8 files changed, 50 insertions(+), 220 deletions(-) delete mode 100644 www/src/components/stack/queries.ts diff --git a/www/src/components/shell/queries.ts b/www/src/components/shell/queries.ts index dec09ace5..552ed435b 100644 --- a/www/src/components/shell/queries.ts +++ b/www/src/components/shell/queries.ts @@ -1,8 +1,6 @@ import { gql } from '@apollo/client' import { CloudShellFragment, DemoProjectFragment } from '../../models/shell' -import { RepoFragment, StackFragment } from '../../models/repo' -import { PageInfo } from '../../models/misc' export const AUTHENTICATION_URLS_QUERY = gql` query { @@ -91,54 +89,6 @@ export const DELETE_DEMO_PROJECT_MUTATION = gql` ${DemoProjectFragment} ` -export const APPLICATIONS_QUERY = gql` - query ApplicationsQuery($cursor: String) { - repositories(after: $cursor, first: 200) { - pageInfo { - ...PageInfo - } - edges { - node { - ...RepoFragment - recipes { - id - name - provider - } - } - } - } - } - ${RepoFragment} - ${PageInfo} -` - -export const STACKS_QUERY = gql` - query Stacks($featured: Boolean) { - stacks(featured: $featured, first: 10) { - pageInfo { - ...PageInfo - } - edges { - node { - ...StackFragment - } - } - } - } - ${PageInfo} - ${StackFragment} -` - -export const STACK_QUERY = gql` - query StackQuery($name: String!, $provider: Provider!) { - stack(name: $name, provider: $provider) { - ...StackFragment - } - } - ${StackFragment} -` - export const CREATE_QUICK_STACK_MUTATION = gql` mutation QuickStacks($applicationIds: [ID], $provider: Provider!) { quickStack(repositoryIds: $applicationIds, provider: $provider) { diff --git a/www/src/components/stack/Stack.tsx b/www/src/components/stack/Stack.tsx index 55fb355d1..4e9ea7c71 100644 --- a/www/src/components/stack/Stack.tsx +++ b/www/src/components/stack/Stack.tsx @@ -1,4 +1,3 @@ -import { useQuery } from '@apollo/client' import { Outlet, useLocation, useParams } from 'react-router-dom' import { Div, Flex, P, Span } from 'honorable' import { @@ -6,8 +5,8 @@ import { Tab, TabList, TabPanel, - VerifiedIcon, useSetBreadcrumbs, + VerifiedIcon, } from '@pluralsh/design-system' import { useMemo, useRef } from 'react' @@ -22,13 +21,16 @@ import { LinkTabWrap } from '../utils/Tabs' import { ProvidersSidecar } from '../utils/recipeHelpers' -import { StackCollection } from '../../generated/graphql' +import { + Provider, + StackCollection, + StackCollectionFragment, + useGetStackQuery, +} from '../../generated/graphql' import LoadingIndicator from '../utils/LoadingIndicator' import { MARKETPLACE_CRUMB } from '../marketplace/Marketplace' - -import { STACK_QUERY } from './queries' import { StackContext } from './types' const DIRECTORY = [{ label: 'Stack applications', path: '' }] @@ -117,7 +119,9 @@ function Sidenav({ stack }: StackContext) { function StackSidecar({ stack }: StackContext) { const filteredCollections = stack?.collections?.filter( - (sC: StackCollection | null | undefined): sC is StackCollection => !!sC + ( + sC: StackCollectionFragment | null | undefined + ): sC is StackCollectionFragment => !!sC ) const recipes = filteredCollections?.map(({ provider }) => ({ description: `Installs ${stack.displayName || stack.name} on ${provider}`, @@ -145,8 +149,8 @@ function StackSidecar({ stack }: StackContext) { export default function Stack() { const { name } = useParams() - const { data } = useQuery(STACK_QUERY, { - variables: { name, provider: 'AWS' }, + const { data } = useGetStackQuery({ + variables: { name: name ?? '', provider: Provider.Aws }, }) const tabStateRef = useRef(null) const breadcrumbs = useMemo( @@ -156,9 +160,10 @@ export default function Stack() { useSetBreadcrumbs(breadcrumbs) - if (!data) return + if (!data?.stack) return const { stack } = data + const outletContext: StackContext = { stack } return ( diff --git a/www/src/components/stack/queries.ts b/www/src/components/stack/queries.ts deleted file mode 100644 index 4a3117466..000000000 --- a/www/src/components/stack/queries.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { gql } from '@apollo/client' - -import { StackFragment } from '../../models/repo' - -export const STACK_QUERY = gql` - query Stack($name: String!, $provider: Provider!) { - stack(name: $name, provider: $provider) { - ...StackFragment - } - } - ${StackFragment} -` diff --git a/www/src/components/stack/types.ts b/www/src/components/stack/types.ts index f8ee1c5c1..8d8cfbbb3 100644 --- a/www/src/components/stack/types.ts +++ b/www/src/components/stack/types.ts @@ -1,5 +1,5 @@ -import { Stack } from '../../generated/graphql' +import { StackFragment } from '../../generated/graphql' export interface StackContext { - stack: Stack + stack: StackFragment } diff --git a/www/src/components/users/queries.ts b/www/src/components/users/queries.ts index 0c575f748..96bbd2c14 100644 --- a/www/src/components/users/queries.ts +++ b/www/src/components/users/queries.ts @@ -109,33 +109,6 @@ export const TOKEN_METRICS = gql` } ` -export const WEBHOOKS_Q = gql` - query Webhooks($cursor: String) { - webhooks(first: 10, after: $cursor) { - edges { - node { - ...WebhookFragment - } - } - pageInfo { - ...PageInfo - } - } - } - ${WebhookFragment} - ${PageInfo} -` - -export const PING_WEBHOOK = gql` - mutation PingWebhook($repo: String!, $id: ID!) { - pingWebhook(repo: $repo, id: $id) { - statusCode - body - headers - } - } -` - export const REGISTER_CARD = gql` mutation RegisterCard($source: String!) { createCard(source: $source) { @@ -166,18 +139,6 @@ export const REALIZE_TOKEN = gql` } ` -export const RESET_TOKEN = gql` - query Token($id: ID!) { - resetToken(id: $id) { - type - user { - ...UserFragment - } - } - } - ${UserFragment} -` - export const LIST_KEYS = gql` query Keys($cursor: String) { publicKeys(after: $cursor, first: 20) { @@ -203,63 +164,6 @@ export const DELETE_KEY = gql` } ` -export const LOGIN_METHOD = gql` - query LoginMethod($email: String!, $host: String) { - loginMethod(email: $email, host: $host) { - loginMethod - token - authorizeUrl - } - } -` - -export const SIGNUP_MUTATION = gql` - mutation Signup( - $attributes: UserAttributes! - $account: AccountAttributes - $deviceToken: String - ) { - signup( - attributes: $attributes - account: $account - deviceToken: $deviceToken - ) { - jwt - id - onboarding - } - } -` - -export const LOGIN_MUTATION = gql` - mutation Login($email: String!, $password: String!, $deviceToken: String) { - login(email: $email, password: $password, deviceToken: $deviceToken) { - jwt - id - email - } - } -` - -export const PASSWORDLESS_LOGIN = gql` - mutation Passwordless($token: String!) { - passwordlessLogin(token: $token) { - jwt - id - email - } - } -` - -export const POLL_LOGIN_TOKEN = gql` - mutation Poll($token: String!, $deviceToken: String) { - loginToken(token: $token, deviceToken: $deviceToken) { - jwt - id - email - } - } -` export const EAB_CREDENTIALS = gql` query { eabCredentials { diff --git a/www/src/generated/graphql.ts b/www/src/generated/graphql.ts index aab0867d0..edce3038f 100644 --- a/www/src/generated/graphql.ts +++ b/www/src/generated/graphql.ts @@ -5765,6 +5765,8 @@ export type RecipeConfigurationFragment = { __typename?: 'RecipeConfiguration', export type StackFragment = { __typename?: 'Stack', id: string, name: string, displayName?: string | null, description?: string | null, featured?: boolean | null, creator?: { __typename?: 'User', id: string, name: string } | null, collections?: Array<{ __typename?: 'StackCollection', id: string, provider: Provider, bundles?: Array<{ __typename?: 'StackRecipe', recipe: { __typename?: 'Recipe', repository?: { __typename?: 'Repository', id: string, name: string, notes?: string | null, description?: string | null, documentation?: string | null, icon?: string | null, darkIcon?: string | null, private?: boolean | null, trending?: boolean | null, verified?: boolean | null, category?: Category | null, tags?: Array<{ __typename?: 'Tag', tag: string } | null> | null, docs?: Array<{ __typename?: 'FileContent', content: string, path: string } | null> | null, oauthSettings?: { __typename?: 'OauthSettings', uriFormat: string, authMethod: OidcAuthMethod } | null, publisher?: { __typename?: 'Publisher', id?: string | null, name: string, phone?: string | null, avatar?: string | null, description?: string | null, backgroundColor?: string | null, owner?: { __typename?: 'User', id: string, name: string, email: string, avatar?: string | null, provider?: Provider | null, demoed?: boolean | null, onboarding?: OnboardingState | null, emailConfirmed?: boolean | null, emailConfirmBy?: Date | null, backgroundColor?: string | null, serviceAccount?: boolean | null, hasInstallations?: boolean | null, hasShell?: boolean | null, onboardingChecklist?: { __typename?: 'OnboardingChecklist', dismissed?: boolean | null, status?: OnboardingChecklistState | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null, roles?: { __typename?: 'Roles', admin?: boolean | null } | null, groups?: Array<{ __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null> | null, impersonationPolicy?: { __typename?: 'ImpersonationPolicy', id: string, bindings?: Array<{ __typename?: 'ImpersonationPolicyBinding', id: string, group?: { __typename?: 'Group', id: string, name: string } | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null } | null> | null } | null } | null, address?: { __typename?: 'Address', line1?: string | null, line2?: string | null, city?: string | null, country?: string | null, state?: string | null, zip?: string | null } | null } | null, recipes?: Array<{ __typename?: 'Recipe', name: string, provider?: Provider | null, description?: string | null } | null> | null } | null } } | null> | null } | null> | null }; +export type StackCollectionFragment = { __typename?: 'StackCollection', id: string, provider: Provider, bundles?: Array<{ __typename?: 'StackRecipe', recipe: { __typename?: 'Recipe', repository?: { __typename?: 'Repository', id: string, name: string, notes?: string | null, description?: string | null, documentation?: string | null, icon?: string | null, darkIcon?: string | null, private?: boolean | null, trending?: boolean | null, verified?: boolean | null, category?: Category | null, tags?: Array<{ __typename?: 'Tag', tag: string } | null> | null, docs?: Array<{ __typename?: 'FileContent', content: string, path: string } | null> | null, oauthSettings?: { __typename?: 'OauthSettings', uriFormat: string, authMethod: OidcAuthMethod } | null, publisher?: { __typename?: 'Publisher', id?: string | null, name: string, phone?: string | null, avatar?: string | null, description?: string | null, backgroundColor?: string | null, owner?: { __typename?: 'User', id: string, name: string, email: string, avatar?: string | null, provider?: Provider | null, demoed?: boolean | null, onboarding?: OnboardingState | null, emailConfirmed?: boolean | null, emailConfirmBy?: Date | null, backgroundColor?: string | null, serviceAccount?: boolean | null, hasInstallations?: boolean | null, hasShell?: boolean | null, onboardingChecklist?: { __typename?: 'OnboardingChecklist', dismissed?: boolean | null, status?: OnboardingChecklistState | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null, roles?: { __typename?: 'Roles', admin?: boolean | null } | null, groups?: Array<{ __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null> | null, impersonationPolicy?: { __typename?: 'ImpersonationPolicy', id: string, bindings?: Array<{ __typename?: 'ImpersonationPolicyBinding', id: string, group?: { __typename?: 'Group', id: string, name: string } | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null } | null> | null } | null } | null, address?: { __typename?: 'Address', line1?: string | null, line2?: string | null, city?: string | null, country?: string | null, state?: string | null, zip?: string | null } | null } | null, recipes?: Array<{ __typename?: 'Recipe', name: string, provider?: Provider | null, description?: string | null } | null> | null } | null } } | null> | null }; + export type GetRecipeQueryVariables = Exact<{ repo?: InputMaybe; name?: InputMaybe; @@ -7409,6 +7411,22 @@ export const RecipeFragmentDoc = gql` } } ${RecipeSectionFragmentDoc}`; +export const StackCollectionFragmentDoc = gql` + fragment StackCollection on StackCollection { + id + provider + bundles { + recipe { + repository { + ...Repo + tags { + tag + } + } + } + } +} + ${RepoFragmentDoc}`; export const StackFragmentDoc = gql` fragment Stack on Stack { id @@ -7421,21 +7439,10 @@ export const StackFragmentDoc = gql` name } collections { - id - provider - bundles { - recipe { - repository { - ...Repo - tags { - tag - } - } - } - } + ...StackCollection } } - ${RepoFragmentDoc}`; + ${StackCollectionFragmentDoc}`; export const ApplyLockFragmentDoc = gql` fragment ApplyLock on ApplyLock { id @@ -11925,6 +11932,7 @@ export const namedOperations = { RecipeSection: 'RecipeSection', RecipeConfiguration: 'RecipeConfiguration', Stack: 'Stack', + StackCollection: 'StackCollection', ApplyLock: 'ApplyLock', Category: 'Category', FileContent: 'FileContent', diff --git a/www/src/graph/recipes.graphql b/www/src/graph/recipes.graphql index be4cf20a7..c5332da5c 100644 --- a/www/src/graph/recipes.graphql +++ b/www/src/graph/recipes.graphql @@ -90,15 +90,19 @@ fragment Stack on Stack { name } collections { - id - provider - bundles { - recipe { - repository { - ...Repo - tags { - tag - } + ...StackCollection + } +} + +fragment StackCollection on StackCollection { + id + provider + bundles { + recipe { + repository { + ...Repo + tags { + tag } } } diff --git a/www/src/models/repo.ts b/www/src/models/repo.ts index 719138b61..e05c90652 100644 --- a/www/src/models/repo.ts +++ b/www/src/models/repo.ts @@ -54,35 +54,6 @@ export const InstallationRepoFragment = gql` } ` -export const StackFragment = gql` - fragment StackFragment on Stack { - id - name - displayName - description - featured - creator { - id - name - } - collections { - id - provider - bundles { - recipe { - repository { - ...RepoFragment - tags { - tag - } - } - } - } - } - } - ${RepoFragment} -` - export const InstallationFragment = gql` fragment InstallationFragment on Installation { id From fbc1910155ccd95f671c7d35551d2bf11e52896b Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 12:05:09 +0200 Subject: [PATCH 06/17] use typed queries --- www/src/components/account/queries.ts | 44 --------------- www/src/components/app/queries.ts | 31 ----------- www/src/components/audits/queries.ts | 17 ------ .../marketplace/MarketplaceStacks.tsx | 41 ++++++++------ www/src/components/marketplace/queries.ts | 53 +------------------ www/src/generated/graphql.ts | 6 ++- www/src/graph/recipes.graphql | 4 +- 7 files changed, 31 insertions(+), 165 deletions(-) delete mode 100644 www/src/components/app/queries.ts diff --git a/www/src/components/account/queries.ts b/www/src/components/account/queries.ts index f19d19601..42ff0bb17 100644 --- a/www/src/components/account/queries.ts +++ b/www/src/components/account/queries.ts @@ -82,15 +82,6 @@ export const SEARCH_GROUPS = gql` ${GroupFragment} ` -export const EDIT_USER = gql` - mutation UpdateUser($id: ID, $attributes: UserAttributes!) { - updateUser(id: $id, attributes: $attributes) { - ...UserFragment - } - } - ${UserFragment} -` - export const CREATE_INVITE = gql` mutation CreateInvite($attributes: InviteAttributes!) { createInvite(attributes: $attributes) { @@ -187,41 +178,6 @@ export const AUDITS_Q = gql` ${AuditFragment} ` -export const LOGINS_Q = gql` - query Logins($cursor: String) { - oidcLogins(first: 50, after: $cursor) { - pageInfo { - ...PageInfo - } - edges { - node { - ...OidcLoginFragment - } - } - } - } - ${PageInfo} - ${OidcLoginFragment} -` - -export const AUDIT_METRICS = gql` - query { - auditMetrics { - country - count - } - } -` - -export const LOGIN_METRICS = gql` - query { - loginMetrics { - country - count - } - } -` - export const IMPERSONATE_SERVICE_ACCOUNT = gql` mutation Impersonate($id: ID) { impersonateServiceAccount(id: $id) { diff --git a/www/src/components/app/queries.ts b/www/src/components/app/queries.ts deleted file mode 100644 index 454041072..000000000 --- a/www/src/components/app/queries.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { gql } from '@apollo/client' - -import { - FileContentFragment, - InstallationFragment, - RepoFragment, -} from '../../models/repo' -import { OIDCProvider } from '../../models/oauth' - -export const REPO_Q = gql` - query Repo($name: String!) { - repository(name: $name) { - ...RepoFragment - docs { - ...FileContentFragment - } - editable - upgradeChannels - installation { - ...InstallationFragment - oidcProvider { - ...OIDCProvider - } - } - } - } - ${RepoFragment} - ${FileContentFragment} - ${InstallationFragment} - ${OIDCProvider} -` diff --git a/www/src/components/audits/queries.ts b/www/src/components/audits/queries.ts index cb3931688..03adc1aeb 100644 --- a/www/src/components/audits/queries.ts +++ b/www/src/components/audits/queries.ts @@ -3,23 +3,6 @@ import { gql } from '@apollo/client' import { AuditFragment, OidcLoginFragment } from '../../models/account' import { PageInfo } from '../../models/misc' -export const AUDITS_Q = gql` - query Audits($cursor: String) { - audits(first: 50, after: $cursor) { - pageInfo { - ...PageInfo - } - edges { - node { - ...AuditFragment - } - } - } - } - ${PageInfo} - ${AuditFragment} -` - export const LOGINS_Q = gql` query Logins($cursor: String) { oidcLogins(first: 50, after: $cursor) { diff --git a/www/src/components/marketplace/MarketplaceStacks.tsx b/www/src/components/marketplace/MarketplaceStacks.tsx index abc7ce463..d7234dfdf 100644 --- a/www/src/components/marketplace/MarketplaceStacks.tsx +++ b/www/src/components/marketplace/MarketplaceStacks.tsx @@ -7,28 +7,35 @@ import { useTheme } from 'styled-components' import { getRepoIcon } from '../repository/misc' -import { STACKS_QUERY } from './queries' import { CardGrid } from './CardGrid' +import { StackFragment, useListStacksQuery } from '../../generated/graphql' +import { mapExistingNodes } from '../../utils/graphql' +import { useMemo } from 'react' const hues = ['blue', 'green', 'yellow', 'red'] as const export default function MarketplaceStacks() { const theme = useTheme() const navigate = useNavigate() - const { data } = useQuery(STACKS_QUERY, { variables: { featured: true } }) - - if (isEmpty(data?.stacks?.edges)) return null - - const { - stacks: { edges }, - } = data - const apps = ({ collections: c }) => - c?.length > 0 - ? c[0].bundles?.map(({ recipe: { repository } }) => ({ - name: repository.name, - imageUrl: getRepoIcon(repository, theme.mode), - })) - : [] + const { data } = useListStacksQuery({ + variables: { first: 10, featured: true }, + }) + + const stacks = useMemo(() => mapExistingNodes(data?.stacks), [data?.stacks]) + + if (!stacks || isEmpty(stacks)) return null + + const apps = (stack: StackFragment) => { + const c = stack.collections + + if (!c || c.length === 0) return [] + + return c[0]?.bundles?.map((bundle) => ({ + name: bundle?.recipe?.repository?.name, + imageUrl: getRepoIcon(bundle?.recipe?.repository, theme.mode), + })) + } + const hue = (i) => hues[i % hues.length] return ( @@ -38,11 +45,11 @@ export default function MarketplaceStacks() { marginBottom="xlarge" marginTop="medium" > - {edges.map(({ node: stack }, i) => ( + {stacks.map((stack, i) => ( navigate(`/stack/${stack.name}`)} diff --git a/www/src/components/marketplace/queries.ts b/www/src/components/marketplace/queries.ts index f454a355e..c053de396 100644 --- a/www/src/components/marketplace/queries.ts +++ b/www/src/components/marketplace/queries.ts @@ -1,59 +1,8 @@ import { gql } from '@apollo/client' -import { - CategoryFragment, - InstallationFragment, - RepoFragment, - StackFragment, -} from '../../models/repo' +import { CategoryFragment } from '../../models/repo' import { PageInfo } from '../../models/misc' -export const MARKETPLACE_QUERY = gql` - query Repos($publisherId: ID, $tag: String, $cursor: String) { - repositories( - publisherId: $publisherId - tag: $tag - after: $cursor - first: 200 - ) { - pageInfo { - ...PageInfo - } - edges { - node { - ...RepoFragment - installation { - ...InstallationFragment - } - tags { - tag - } - } - } - } - } - ${PageInfo} - ${RepoFragment} - ${InstallationFragment} -` - -export const STACKS_QUERY = gql` - query Stacks($featured: Boolean) { - stacks(featured: $featured, first: 10) { - pageInfo { - ...PageInfo - } - edges { - node { - ...StackFragment - } - } - } - } - ${PageInfo} - ${StackFragment} -` - export const CATEGORIES_QUERY = gql` query { categories { diff --git a/www/src/generated/graphql.ts b/www/src/generated/graphql.ts index edce3038f..e5c242cb5 100644 --- a/www/src/generated/graphql.ts +++ b/www/src/generated/graphql.ts @@ -5814,6 +5814,7 @@ export type GetStackQueryVariables = Exact<{ export type GetStackQuery = { __typename?: 'RootQueryType', stack?: { __typename?: 'Stack', id: string, name: string, displayName?: string | null, description?: string | null, featured?: boolean | null, creator?: { __typename?: 'User', id: string, name: string } | null, collections?: Array<{ __typename?: 'StackCollection', id: string, provider: Provider, bundles?: Array<{ __typename?: 'StackRecipe', recipe: { __typename?: 'Recipe', repository?: { __typename?: 'Repository', id: string, name: string, notes?: string | null, description?: string | null, documentation?: string | null, icon?: string | null, darkIcon?: string | null, private?: boolean | null, trending?: boolean | null, verified?: boolean | null, category?: Category | null, tags?: Array<{ __typename?: 'Tag', tag: string } | null> | null, docs?: Array<{ __typename?: 'FileContent', content: string, path: string } | null> | null, oauthSettings?: { __typename?: 'OauthSettings', uriFormat: string, authMethod: OidcAuthMethod } | null, publisher?: { __typename?: 'Publisher', id?: string | null, name: string, phone?: string | null, avatar?: string | null, description?: string | null, backgroundColor?: string | null, owner?: { __typename?: 'User', id: string, name: string, email: string, avatar?: string | null, provider?: Provider | null, demoed?: boolean | null, onboarding?: OnboardingState | null, emailConfirmed?: boolean | null, emailConfirmBy?: Date | null, backgroundColor?: string | null, serviceAccount?: boolean | null, hasInstallations?: boolean | null, hasShell?: boolean | null, onboardingChecklist?: { __typename?: 'OnboardingChecklist', dismissed?: boolean | null, status?: OnboardingChecklistState | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null, roles?: { __typename?: 'Roles', admin?: boolean | null } | null, groups?: Array<{ __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null> | null, impersonationPolicy?: { __typename?: 'ImpersonationPolicy', id: string, bindings?: Array<{ __typename?: 'ImpersonationPolicyBinding', id: string, group?: { __typename?: 'Group', id: string, name: string } | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null } | null> | null } | null } | null, address?: { __typename?: 'Address', line1?: string | null, line2?: string | null, city?: string | null, country?: string | null, state?: string | null, zip?: string | null } | null } | null, recipes?: Array<{ __typename?: 'Recipe', name: string, provider?: Provider | null, description?: string | null } | null> | null } | null } } | null> | null } | null> | null } | null }; export type ListStacksQueryVariables = Exact<{ + first?: InputMaybe; featured?: InputMaybe; cursor?: InputMaybe; }>; @@ -9789,8 +9790,8 @@ export type GetStackLazyQueryHookResult = ReturnType; export type GetStackQueryResult = Apollo.QueryResult; export const ListStacksDocument = gql` - query ListStacks($featured: Boolean, $cursor: String) { - stacks(first: 100, after: $cursor, featured: $featured) { + query ListStacks($first: Int = 100, $featured: Boolean, $cursor: String) { + stacks(first: $first, after: $cursor, featured: $featured) { edges { node { ...Stack @@ -9812,6 +9813,7 @@ export const ListStacksDocument = gql` * @example * const { data, loading, error } = useListStacksQuery({ * variables: { + * first: // value for 'first' * featured: // value for 'featured' * cursor: // value for 'cursor' * }, diff --git a/www/src/graph/recipes.graphql b/www/src/graph/recipes.graphql index c5332da5c..ed6506a09 100644 --- a/www/src/graph/recipes.graphql +++ b/www/src/graph/recipes.graphql @@ -152,8 +152,8 @@ query GetStack($name: String!, $provider: Provider!) { } } -query ListStacks($featured: Boolean, $cursor: String) { - stacks(first: 100, after: $cursor, featured: $featured) { +query ListStacks($first: Int = 100, $featured: Boolean, $cursor: String) { + stacks(first: $first, after: $cursor, featured: $featured) { edges { node { ...Stack From 3e78cdbc25486731b6c2e4cce5fd64de9d857cd1 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 12:45:02 +0200 Subject: [PATCH 07/17] use typed queries --- www/src/components/account/billing/queries.ts | 55 ------- .../components/layout/WithNotifications.tsx | 46 +++--- www/src/components/layout/queries.ts | 21 --- www/src/components/publisher/queries.ts | 70 --------- .../components/utils/tableFetchHelpers.tsx | 135 ++++++++++++++++++ .../utils/useFetchPaginatedData.tsx | 132 +++++++++++++++++ www/src/generated/graphql.ts | 93 ++++++++++++ www/src/graph/notifications.graphql | 41 ++++++ www/src/models/incidents.ts | 28 ---- www/src/utils/graphql.ts | 22 +++ 10 files changed, 444 insertions(+), 199 deletions(-) delete mode 100644 www/src/components/layout/queries.ts create mode 100644 www/src/components/utils/tableFetchHelpers.tsx create mode 100644 www/src/components/utils/useFetchPaginatedData.tsx create mode 100644 www/src/graph/notifications.graphql diff --git a/www/src/components/account/billing/queries.ts b/www/src/components/account/billing/queries.ts index 211abb56e..84f44c9bc 100644 --- a/www/src/components/account/billing/queries.ts +++ b/www/src/components/account/billing/queries.ts @@ -23,61 +23,6 @@ export const PLATFORM_PLANS_QUERY = gql` } ` -export const SUBSCRIPTION_QUERY = gql` - query Subscription { - account { - billingCustomerId - grandfatheredUntil - delinquentAt - userCount - clusterCount - trialed - availableFeatures { - userManagement - audit - } - subscription { - id - trialUntil - plan { - id - period - lineItems { - dimension - cost - } - name - } - } - billingAddress { - name - line1 - line2 - zip - state - city - country - } - } - } -` - -export const UPDATE_ACCOUNT_BILLING_MUTATION = gql` - mutation UpdateAccountBilling($attributes: AccountAttributes!) { - updateAccount(attributes: $attributes) { - id - } - } -` - -export const UPGRADE_TO_PROFESSIONAL_PLAN_MUTATION = gql` - mutation UpgradeToProfessionalPlan($planId: ID!) { - createPlatformSubscription(planId: $planId) { - id - } - } -` - export const DOWNGRADE_TO_FREE_PLAN_MUTATION = gql` mutation DowngradeToFreePlanMutation { deletePlatformSubscription { diff --git a/www/src/components/layout/WithNotifications.tsx b/www/src/components/layout/WithNotifications.tsx index 380d1b127..09462dfc6 100644 --- a/www/src/components/layout/WithNotifications.tsx +++ b/www/src/components/layout/WithNotifications.tsx @@ -2,7 +2,7 @@ import { useMutation, useQuery } from '@apollo/client' import { Div, Flex, P } from 'honorable' import moment from 'moment' import { AppIcon, Button, Card, Markdown } from '@pluralsh/design-system' -import { useCallback, useContext } from 'react' +import { useCallback, useContext, useMemo } from 'react' import { Link } from 'react-router-dom' import { useTheme } from 'styled-components' @@ -13,42 +13,38 @@ import { RootMutationType, RootMutationTypeUpdateUserArgs, RootQueryType, + useNotificationsQuery, } from '../../generated/graphql' import { OnboardingChecklistContext } from '../../contexts/OnboardingChecklistContext' import usePaginatedQuery from '../../hooks/usePaginatedQuery' import { UPDATE_USER } from '../users/queries' import InfiniteScroller from '../utils/InfiniteScroller' import CurrentUserContext from '../../contexts/CurrentUserContext' -import { updateUserFragment } from '../../utils/graphql' +import { mapExistingNodes, updateUserFragment } from '../../utils/graphql' import { clearOnboardingChecklistState, isOnboardingChecklistHidden, } from '../../helpers/localStorage' - -import { NOTIFICATIONS_QUERY } from './queries' +import { useFetchPaginatedData } from '../utils/useFetchPaginatedData' export function useNotificationsCount() { - const { data } = useQuery<{ notifications: RootQueryType['notifications'] }>( - NOTIFICATIONS_QUERY, - { - variables: { first: 100 }, - pollInterval: 10000, - } - ) + const { data } = useNotificationsQuery({ + variables: { first: 100 }, + pollInterval: 10000, + }) return data?.notifications?.edges?.length ?? undefined } export function NotificationsPanel({ closePanel }: any) { - const [ - notifications, - loadingNotifications, - hasMoreNotifications, - fetchMoreNotifications, - ] = usePaginatedQuery( - NOTIFICATIONS_QUERY, - { variables: {} }, - (data) => data.notifications + const { data, loading, pageInfo, fetchNextPage } = useFetchPaginatedData( + { queryHook: useNotificationsQuery, keyPath: ['notifications'] }, + {} + ) + + const notifications = useMemo( + () => mapExistingNodes(data?.notifications), + [data?.notifications] ) const me = useContext(CurrentUserContext) @@ -59,11 +55,11 @@ export function NotificationsPanel({ closePanel }: any) { [me] ) - if (showOnboardingNotification() && !notifications.length) { + if (showOnboardingNotification() && !notifications?.length) { return } - if (!notifications.length) { + if (!notifications?.length) { return

You do not have any notifications yet.

} @@ -76,9 +72,9 @@ export function NotificationsPanel({ closePanel }: any) { )} | undefined + after?: InputMaybe | undefined + }, +>( + queryResult: QueryResult, + { + interval = POLL_INTERVAL, + ...fetchSliceOpts + }: { interval?: number } & FetchSliceOptions +) { + const { data, loading, refetch: originalRefetch } = queryResult + const edges = useMemo(() => { + const queryKey = fetchSliceOpts.keyPath[fetchSliceOpts.keyPath.length - 1] + + return data?.[queryKey]?.edges + }, [data, fetchSliceOpts.keyPath]) + + const fetchSlice = useFetchSlice(queryResult, fetchSliceOpts) + const refetch = !fetchSliceOpts?.virtualSlice?.start?.index + ? originalRefetch + : fetchSlice + + useEffect(() => { + if (!edges) { + return + } + let intervalId + + if (!loading) { + intervalId = setInterval(() => { + refetch() + }, interval) + } + + return () => { + if (intervalId) { + clearInterval(intervalId) + } + } + }, [edges, interval, loading, refetch]) + + return useMemo( + () => ({ + refetch, + }), + [refetch] + ) +} + +export function useFetchSlice< + QData, + QVariables extends { + first?: InputMaybe | undefined + after?: InputMaybe | undefined + }, +>(queryResult: QueryResult, options: FetchSliceOptions) { + const { virtualSlice, pageSize, keyPath } = options + const queryKey = useMemo(() => keyPath[keyPath.length - 1], [keyPath]) + const [endCursors, setEndCursors] = useState< + { index: number; cursor: string }[] + >([]) + const endCursor = queryResult?.data?.[queryKey]?.pageInfo.endCursor + const endCursorIndex = (queryResult?.data?.[queryKey]?.edges?.length ?? 0) - 1 + const prevEndCursor = usePrevious(endCursor) + + useEffect(() => { + if (endCursor && endCursor !== prevEndCursor && endCursorIndex >= 0) { + setEndCursors((prev) => + [ + ...(virtualSlice?.start?.index !== 0 ? prev : []), + { index: endCursorIndex, cursor: endCursor }, + ].sort((a, b) => b.index - a.index) + ) + } + }, [endCursor, endCursorIndex, prevEndCursor, virtualSlice?.start?.index]) + + const { first, after } = useMemo(() => { + const startIndex = virtualSlice?.start?.index ?? 0 + const endIndex = virtualSlice?.end?.index ?? 0 + const cursor = endCursors.find((c) => c.index < startIndex) + + return { + first: + Math.max(pageSize, endIndex - (cursor?.index || 0) + 1) || + queryResult.variables?.first, + after: cursor?.cursor || queryResult.variables?.after, + } + }, [ + endCursors, + pageSize, + queryResult.variables?.after, + queryResult.variables?.first, + virtualSlice?.end?.index, + virtualSlice?.start?.index, + ]) + + const { fetchMore } = queryResult + + return useCallback(() => { + fetchMore({ + variables: { after, first }, + updateQuery: (prev, { fetchMoreResult }) => { + const newConnection = extendConnection( + reduceNestedData(keyPath, prev), + reduceNestedData(keyPath, fetchMoreResult)[queryKey], + queryKey + ) + + return updateNestedConnection(keyPath, prev, newConnection) + }, + }) + }, [fetchMore, after, first, keyPath, queryKey]) +} + +export const reduceNestedData = (path: string[], data: any) => + path.slice(0, -1).reduce((acc, key) => acc?.[key], data) diff --git a/www/src/components/utils/useFetchPaginatedData.tsx b/www/src/components/utils/useFetchPaginatedData.tsx new file mode 100644 index 000000000..1fb584b9f --- /dev/null +++ b/www/src/components/utils/useFetchPaginatedData.tsx @@ -0,0 +1,132 @@ +import { ComponentProps, useCallback, useMemo, useState } from 'react' +import { VirtualItem } from '@tanstack/react-virtual' +import { extendConnection, updateNestedConnection } from 'utils/graphql' +import { + reduceNestedData, + useSlicePolling, +} from 'components/utils/tableFetchHelpers' +import { + ErrorPolicy, + OperationVariables, + QueryHookOptions, + QueryResult, +} from '@apollo/client' +import { Table } from '@pluralsh/design-system' + +export const DEFAULT_REACT_VIRTUAL_OPTIONS: ComponentProps< + typeof Table +>['reactVirtualOptions'] = { overscan: 10 } + +export const DEFAULT_PAGE_SIZE = 100 +export const POLL_INTERVAL = 10_000 + +type GenericQueryHook = ( + baseOptions: QueryHookOptions +) => QueryResult & { + fetchMore: (options: any) => Promise +} + +type FetchDataOptions = { + queryHook: GenericQueryHook + pageSize?: number + keyPath: string[] + pollInterval?: number + errorPolicy?: ErrorPolicy +} + +export type FetchPaginatedDataResult = { + data: TQueryType | undefined + loading: boolean + error: any + refetch: () => void + pageInfo: any + fetchNextPage: () => void + setVirtualSlice: (slice: { + start: VirtualItem | undefined + end: VirtualItem | undefined + }) => void +} + +export function useFetchPaginatedData< + TQueryType extends Partial>, + TVariables extends OperationVariables, +>( + options: FetchDataOptions, + variables: TVariables = {} as TVariables +): FetchPaginatedDataResult { + const [virtualSlice, setVirtualSlice] = useState< + | { + start: VirtualItem | undefined + end: VirtualItem | undefined + } + | undefined + >() + + const queryKey = useMemo( + () => options.keyPath[options.keyPath.length - 1], + [options.keyPath] + ) + + const queryResult = options.queryHook({ + variables: { + ...variables, + first: options.pageSize ?? DEFAULT_PAGE_SIZE, + }, + errorPolicy: options.errorPolicy, + fetchPolicy: 'cache-and-network', + // Important so loading will be updated on fetchMore to send to Table + notifyOnNetworkStatusChange: true, + }) + + const { + data: currentData, + previousData, + loading, + error, + fetchMore, + } = queryResult + + const data = currentData || previousData + const { pageInfo, reducedQueryResult } = useMemo(() => { + const reducedData = reduceNestedData(options.keyPath, currentData) + + return { + pageInfo: reducedData?.[queryKey]?.pageInfo, + reducedQueryResult: { ...queryResult, data: reducedData as TQueryType }, + } + }, [currentData, options.keyPath, queryKey, queryResult]) + + const { refetch } = useSlicePolling(reducedQueryResult, { + virtualSlice, + pageSize: options.pageSize ?? DEFAULT_PAGE_SIZE, + interval: options.pollInterval || POLL_INTERVAL, + keyPath: options.keyPath, + }) + + const fetchNextPage = useCallback(() => { + if (pageInfo?.endCursor) { + fetchMore({ + variables: { after: pageInfo.endCursor }, + updateQuery: (prev, { fetchMoreResult }) => { + const newConnection = extendConnection( + reduceNestedData(options.keyPath, prev), + reduceNestedData(options.keyPath, fetchMoreResult)[queryKey], + queryKey + ) + + return updateNestedConnection(options.keyPath, prev, newConnection) + }, + }) + } + }, [pageInfo, fetchMore, options.keyPath, queryKey]) + + return { + data, + loading, + error, + refetch, + pageInfo, + fetchNextPage, + setVirtualSlice, + } +} diff --git a/www/src/generated/graphql.ts b/www/src/generated/graphql.ts index e5c242cb5..8b9f27b6a 100644 --- a/www/src/generated/graphql.ts +++ b/www/src/generated/graphql.ts @@ -5584,6 +5584,17 @@ export type MetricFragment = { __typename?: 'Metric', name: string, tags?: Array export type PageInfoFragment = { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean }; +export type NotificationFragmentFragment = { __typename?: 'Notification', id: string, type: NotificationType, msg?: string | null, insertedAt?: Date | null, actor: { __typename?: 'User', id: string, name: string, email: string, avatar?: string | null, provider?: Provider | null, demoed?: boolean | null, onboarding?: OnboardingState | null, emailConfirmed?: boolean | null, emailConfirmBy?: Date | null, backgroundColor?: string | null, serviceAccount?: boolean | null, hasInstallations?: boolean | null, hasShell?: boolean | null, onboardingChecklist?: { __typename?: 'OnboardingChecklist', dismissed?: boolean | null, status?: OnboardingChecklistState | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null, roles?: { __typename?: 'Roles', admin?: boolean | null } | null, groups?: Array<{ __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null> | null, impersonationPolicy?: { __typename?: 'ImpersonationPolicy', id: string, bindings?: Array<{ __typename?: 'ImpersonationPolicyBinding', id: string, group?: { __typename?: 'Group', id: string, name: string } | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null } | null> | null } | null }, incident?: { __typename?: 'Incident', id: string, title: string, repository: { __typename?: 'Repository', id: string, name: string, icon?: string | null, darkIcon?: string | null } } | null, message?: { __typename?: 'IncidentMessage', text: string } | null, repository?: { __typename?: 'Repository', id: string, name: string, icon?: string | null, darkIcon?: string | null } | null }; + +export type NotificationsQueryVariables = Exact<{ + incidentId?: InputMaybe; + first?: InputMaybe; + cursor?: InputMaybe; +}>; + + +export type NotificationsQuery = { __typename?: 'RootQueryType', notifications?: { __typename?: 'NotificationConnection', pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean }, edges?: Array<{ __typename?: 'NotificationEdge', node?: { __typename?: 'Notification', id: string, type: NotificationType, msg?: string | null, insertedAt?: Date | null, actor: { __typename?: 'User', id: string, name: string, email: string, avatar?: string | null, provider?: Provider | null, demoed?: boolean | null, onboarding?: OnboardingState | null, emailConfirmed?: boolean | null, emailConfirmBy?: Date | null, backgroundColor?: string | null, serviceAccount?: boolean | null, hasInstallations?: boolean | null, hasShell?: boolean | null, onboardingChecklist?: { __typename?: 'OnboardingChecklist', dismissed?: boolean | null, status?: OnboardingChecklistState | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null, roles?: { __typename?: 'Roles', admin?: boolean | null } | null, groups?: Array<{ __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null> | null, impersonationPolicy?: { __typename?: 'ImpersonationPolicy', id: string, bindings?: Array<{ __typename?: 'ImpersonationPolicyBinding', id: string, group?: { __typename?: 'Group', id: string, name: string } | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null } | null> | null } | null }, incident?: { __typename?: 'Incident', id: string, title: string, repository: { __typename?: 'Repository', id: string, name: string, icon?: string | null, darkIcon?: string | null } } | null, message?: { __typename?: 'IncidentMessage', text: string } | null, repository?: { __typename?: 'Repository', id: string, name: string, icon?: string | null, darkIcon?: string | null } | null } | null } | null> | null } | null }; + export type OidcProviderFragment = { __typename?: 'OidcProvider', id: string, clientId: string, authMethod: OidcAuthMethod, clientSecret: string, redirectUris?: Array | null, bindings?: Array<{ __typename?: 'OidcProviderBinding', id: string, user?: { __typename?: 'User', id: string, name: string, email: string, avatar?: string | null, provider?: Provider | null, demoed?: boolean | null, onboarding?: OnboardingState | null, emailConfirmed?: boolean | null, emailConfirmBy?: Date | null, backgroundColor?: string | null, serviceAccount?: boolean | null, hasInstallations?: boolean | null, hasShell?: boolean | null, onboardingChecklist?: { __typename?: 'OnboardingChecklist', dismissed?: boolean | null, status?: OnboardingChecklistState | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null, roles?: { __typename?: 'Roles', admin?: boolean | null } | null, groups?: Array<{ __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null> | null, impersonationPolicy?: { __typename?: 'ImpersonationPolicy', id: string, bindings?: Array<{ __typename?: 'ImpersonationPolicyBinding', id: string, group?: { __typename?: 'Group', id: string, name: string } | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null } | null> | null } | null } | null, group?: { __typename?: 'Group', id: string, name: string, global?: boolean | null, description?: string | null } | null } | null> | null, configuration?: { __typename?: 'OuathConfiguration', issuer?: string | null, authorizationEndpoint?: string | null, tokenEndpoint?: string | null, jwksUri?: string | null, userinfoEndpoint?: string | null } | null, invites?: Array<{ __typename?: 'Invite', id: string, email?: string | null } | null> | null }; export type OAuthInfoFragment = { __typename?: 'OauthInfo', provider: OauthProvider, authorizeUrl: string }; @@ -7115,6 +7126,36 @@ export const PageInfoFragmentDoc = gql` hasNextPage } `; +export const NotificationFragmentFragmentDoc = gql` + fragment NotificationFragment on Notification { + id + type + msg + actor { + ...User + } + incident { + id + title + repository { + id + name + icon + darkIcon + } + } + message { + text + } + repository { + id + name + icon + darkIcon + } + insertedAt +} + ${UserFragmentDoc}`; export const OAuthInfoFragmentDoc = gql` fragment OAuthInfo on OauthInfo { provider @@ -8850,6 +8891,56 @@ export function useCreateKeyBackupMutation(baseOptions?: Apollo.MutationHookOpti export type CreateKeyBackupMutationHookResult = ReturnType; export type CreateKeyBackupMutationResult = Apollo.MutationResult; export type CreateKeyBackupMutationOptions = Apollo.BaseMutationOptions; +export const NotificationsDocument = gql` + query Notifications($incidentId: ID, $first: Int = 50, $cursor: String) { + notifications(incidentId: $incidentId, first: $first, after: $cursor) { + pageInfo { + ...PageInfo + } + edges { + node { + ...NotificationFragment + } + } + } +} + ${PageInfoFragmentDoc} +${NotificationFragmentFragmentDoc}`; + +/** + * __useNotificationsQuery__ + * + * To run a query within a React component, call `useNotificationsQuery` and pass it any options that fit your needs. + * When your component renders, `useNotificationsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useNotificationsQuery({ + * variables: { + * incidentId: // value for 'incidentId' + * first: // value for 'first' + * cursor: // value for 'cursor' + * }, + * }); + */ +export function useNotificationsQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(NotificationsDocument, options); + } +export function useNotificationsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(NotificationsDocument, options); + } +export function useNotificationsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(NotificationsDocument, options); + } +export type NotificationsQueryHookResult = ReturnType; +export type NotificationsLazyQueryHookResult = ReturnType; +export type NotificationsSuspenseQueryHookResult = ReturnType; +export type NotificationsQueryResult = Apollo.QueryResult; export const OidcConsentDocument = gql` query OIDCConsent($challenge: String!) { oidcConsent(challenge: $challenge) { @@ -11776,6 +11867,7 @@ export const namedOperations = { Invite: 'Invite', KeyBackups: 'KeyBackups', KeyBackup: 'KeyBackup', + Notifications: 'Notifications', OIDCConsent: 'OIDCConsent', Subscription: 'Subscription', Cards: 'Cards', @@ -11911,6 +12003,7 @@ export const namedOperations = { KeyBackup: 'KeyBackup', Metric: 'Metric', PageInfo: 'PageInfo', + NotificationFragment: 'NotificationFragment', OIDCProvider: 'OIDCProvider', OAuthInfo: 'OAuthInfo', Repository: 'Repository', diff --git a/www/src/graph/notifications.graphql b/www/src/graph/notifications.graphql new file mode 100644 index 000000000..bc02f9a4b --- /dev/null +++ b/www/src/graph/notifications.graphql @@ -0,0 +1,41 @@ +fragment NotificationFragment on Notification { + id + type + msg + actor { + ...User + } + incident { + id + title + repository { + id + name + icon + darkIcon + } + } + message { + text + } + repository { + id + name + icon + darkIcon + } + insertedAt +} + +query Notifications($incidentId: ID, $first: Int = 50, $cursor: String) { + notifications(incidentId: $incidentId, first: $first, after: $cursor) { + pageInfo { + ...PageInfo + } + edges { + node { + ...NotificationFragment + } + } + } +} \ No newline at end of file diff --git a/www/src/models/incidents.ts b/www/src/models/incidents.ts index 91bf65867..3dcb51a81 100644 --- a/www/src/models/incidents.ts +++ b/www/src/models/incidents.ts @@ -152,33 +152,5 @@ export const IncidentMessageFragment = gql` ` export const NotificationFragment = gql` - fragment NotificationFragment on Notification { - id - type - msg - actor { - ...UserFragment - } - incident { - id - title - repository { - id - name - icon - darkIcon - } - } - message { - text - } - repository { - id - name - icon - darkIcon - } - insertedAt - } ${UserFragment} ` diff --git a/www/src/utils/graphql.ts b/www/src/utils/graphql.ts index 3eea3ed4e..5dad842cb 100644 --- a/www/src/utils/graphql.ts +++ b/www/src/utils/graphql.ts @@ -39,6 +39,28 @@ export function extendConnection(prev, next, key) { } } +export function updateNestedConnection( + keyPath: string[], + fullQuery: TData, + newConnection: any +): TData { + if (keyPath.length < 2) return newConnection + + const res = { ...fullQuery } + let cur = res + + for (let i = 0; i < keyPath.length - 2; i++) { + const key = keyPath[i] + + if (!cur[key]) cur[key] = {} + cur = cur[key] + } + + cur[keyPath[keyPath.length - 2]] = newConnection + + return res +} + export function deepUpdate(prev, path, update) { if (isString(path)) return deepUpdate(prev, path.split('.'), update) From 1a33d4f1e50c292264d849e78fbb22eac13b4d58 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 12:52:12 +0200 Subject: [PATCH 08/17] left-align roles --- www/src/components/account/Roles.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/www/src/components/account/Roles.tsx b/www/src/components/account/Roles.tsx index 758c4101b..b8e01212e 100644 --- a/www/src/components/account/Roles.tsx +++ b/www/src/components/account/Roles.tsx @@ -66,7 +66,6 @@ function Role({ role, q }: any) { Date: Tue, 8 Oct 2024 12:53:49 +0200 Subject: [PATCH 09/17] left-align service account names --- www/src/components/account/User.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/src/components/account/User.tsx b/www/src/components/account/User.tsx index 580391ade..9701784da 100644 --- a/www/src/components/account/User.tsx +++ b/www/src/components/account/User.tsx @@ -45,7 +45,7 @@ export function UserInfo({ size="xsmall" hue={hue} /> - + {name} Date: Tue, 8 Oct 2024 13:27:22 +0200 Subject: [PATCH 10/17] use default chip size in both cluster lists --- .../components/overview/clusters/SelfHostedTableCols.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/www/src/components/overview/clusters/SelfHostedTableCols.tsx b/www/src/components/overview/clusters/SelfHostedTableCols.tsx index 87500ac23..e62ea3351 100644 --- a/www/src/components/overview/clusters/SelfHostedTableCols.tsx +++ b/www/src/components/overview/clusters/SelfHostedTableCols.tsx @@ -88,12 +88,7 @@ export const ColHealth = columnHelper.accessor((row) => row.pingedAt, { row: { original: { pingedAt }, }, - }) => ( - - ), + }) => , header: 'Health', }) From cc7bd36dc3a3ca2bf14e107d03dc2d061a6d761d Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 13:29:16 +0200 Subject: [PATCH 11/17] use the same text color in go to console buttons --- .../components/overview/clusters/SelfHostedTableCols.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/www/src/components/overview/clusters/SelfHostedTableCols.tsx b/www/src/components/overview/clusters/SelfHostedTableCols.tsx index e62ea3351..35c3784d4 100644 --- a/www/src/components/overview/clusters/SelfHostedTableCols.tsx +++ b/www/src/components/overview/clusters/SelfHostedTableCols.tsx @@ -145,13 +145,7 @@ export const ColActions = columnHelper.accessor((row) => row.consoleUrl, { target="_blank" rel="noopener noreferrer" > - - Go to Console - + Go to Console )} From 094e5a6ae2f4f1d034f2d6a45faddec7592958a0 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 13:53:57 +0200 Subject: [PATCH 12/17] fix type issue --- www/src/components/stack/misc.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/www/src/components/stack/misc.tsx b/www/src/components/stack/misc.tsx index a512a983d..3ada2cbd1 100644 --- a/www/src/components/stack/misc.tsx +++ b/www/src/components/stack/misc.tsx @@ -1,4 +1,7 @@ -import { StackCollection } from '../../generated/graphql' +import { + StackCollection, + StackCollectionFragment, +} from '../../generated/graphql' import InstallAppButton from '../utils/InstallAppButton' import { RecipeSubset } from '../utils/recipeHelpers' @@ -9,7 +12,9 @@ export function StackActions({ recipes, }: StackContext & { recipes?: RecipeSubset[] }) { const filteredCollections = stack?.collections?.filter( - (sC: StackCollection | null | undefined): sC is StackCollection => !!sC + ( + sC: StackCollectionFragment | null | undefined + ): sC is StackCollectionFragment => !!sC ) const apps = filteredCollections?.[0].bundles From 57f0342d242ffaf240373eaa6c6ebbe71f4631e2 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 13:56:41 +0200 Subject: [PATCH 13/17] yarn fix --- www/src/components/account/queries.ts | 1 - www/src/components/app/oidc/OIDC.tsx | 1 - www/src/components/audits/queries.ts | 2 +- www/src/components/layout/WithNotifications.tsx | 2 -- www/src/components/marketplace/MarketplaceStacks.tsx | 7 ++++--- www/src/components/oidc/OAuthConsent.tsx | 3 ++- www/src/components/publisher/queries.ts | 1 - www/src/components/stack/Stack.tsx | 4 ++-- www/src/components/stack/misc.tsx | 5 +---- www/src/components/users/queries.ts | 1 - www/src/components/utils/tableFetchHelpers.tsx | 1 + www/src/graph/notifications.graphql | 2 +- www/src/graph/oauth.graphql | 1 - www/src/graph/recipes.graphql | 2 +- 14 files changed, 13 insertions(+), 20 deletions(-) diff --git a/www/src/components/account/queries.ts b/www/src/components/account/queries.ts index 42ff0bb17..0aaa27b75 100644 --- a/www/src/components/account/queries.ts +++ b/www/src/components/account/queries.ts @@ -5,7 +5,6 @@ import { DnsDomainFragment, DnsRecordFragment, InviteFragment, - OidcLoginFragment, } from '../../models/account' import { PageInfo } from '../../models/misc' import { diff --git a/www/src/components/app/oidc/OIDC.tsx b/www/src/components/app/oidc/OIDC.tsx index b5314315e..3cd42dd40 100644 --- a/www/src/components/app/oidc/OIDC.tsx +++ b/www/src/components/app/oidc/OIDC.tsx @@ -1,4 +1,3 @@ -import { useMutation } from '@apollo/client' import { Card, CheckIcon, diff --git a/www/src/components/audits/queries.ts b/www/src/components/audits/queries.ts index 03adc1aeb..0c7e1996c 100644 --- a/www/src/components/audits/queries.ts +++ b/www/src/components/audits/queries.ts @@ -1,6 +1,6 @@ import { gql } from '@apollo/client' -import { AuditFragment, OidcLoginFragment } from '../../models/account' +import { OidcLoginFragment } from '../../models/account' import { PageInfo } from '../../models/misc' export const LOGINS_Q = gql` diff --git a/www/src/components/layout/WithNotifications.tsx b/www/src/components/layout/WithNotifications.tsx index 09462dfc6..f754ce716 100644 --- a/www/src/components/layout/WithNotifications.tsx +++ b/www/src/components/layout/WithNotifications.tsx @@ -12,11 +12,9 @@ import { OnboardingChecklistState, RootMutationType, RootMutationTypeUpdateUserArgs, - RootQueryType, useNotificationsQuery, } from '../../generated/graphql' import { OnboardingChecklistContext } from '../../contexts/OnboardingChecklistContext' -import usePaginatedQuery from '../../hooks/usePaginatedQuery' import { UPDATE_USER } from '../users/queries' import InfiniteScroller from '../utils/InfiniteScroller' import CurrentUserContext from '../../contexts/CurrentUserContext' diff --git a/www/src/components/marketplace/MarketplaceStacks.tsx b/www/src/components/marketplace/MarketplaceStacks.tsx index d7234dfdf..366d1a6d0 100644 --- a/www/src/components/marketplace/MarketplaceStacks.tsx +++ b/www/src/components/marketplace/MarketplaceStacks.tsx @@ -1,16 +1,17 @@ -import { useQuery } from '@apollo/client' import { H1 } from 'honorable' import { Divider, StackCard } from '@pluralsh/design-system' import { useNavigate } from 'react-router-dom' import isEmpty from 'lodash/isEmpty' import { useTheme } from 'styled-components' +import { useMemo } from 'react' + import { getRepoIcon } from '../repository/misc' -import { CardGrid } from './CardGrid' import { StackFragment, useListStacksQuery } from '../../generated/graphql' import { mapExistingNodes } from '../../utils/graphql' -import { useMemo } from 'react' + +import { CardGrid } from './CardGrid' const hues = ['blue', 'green', 'yellow', 'red'] as const diff --git a/www/src/components/oidc/OAuthConsent.tsx b/www/src/components/oidc/OAuthConsent.tsx index 938f18b95..43d94e288 100644 --- a/www/src/components/oidc/OAuthConsent.tsx +++ b/www/src/components/oidc/OAuthConsent.tsx @@ -6,6 +6,8 @@ import { A, Flex, Span } from 'honorable' import StartCase from 'lodash/startCase' import { useTheme } from 'styled-components' +import { isEmpty } from 'lodash' + import { LoginPortal } from '../users/LoginPortal' import { GqlError } from '../utils/Alert' import { PLURAL_MARK, PLURAL_MARK_WHITE } from '../constants' @@ -16,7 +18,6 @@ import { } from '../../generated/graphql' import { clearLocalStorage } from '../../helpers/localStorage' import LoadingIndicator from '../utils/LoadingIndicator' -import { isEmpty } from 'lodash' function Icon({ icon, diff --git a/www/src/components/publisher/queries.ts b/www/src/components/publisher/queries.ts index a3660b7bb..9f799d035 100644 --- a/www/src/components/publisher/queries.ts +++ b/www/src/components/publisher/queries.ts @@ -1,6 +1,5 @@ import { gql } from '@apollo/client' -import { PageInfo } from '../../models/misc' import { PublisherFragment } from '../../models/user' export const PUBLISHER_QUERY = gql` diff --git a/www/src/components/stack/Stack.tsx b/www/src/components/stack/Stack.tsx index 4e9ea7c71..d68f37482 100644 --- a/www/src/components/stack/Stack.tsx +++ b/www/src/components/stack/Stack.tsx @@ -5,8 +5,8 @@ import { Tab, TabList, TabPanel, - useSetBreadcrumbs, VerifiedIcon, + useSetBreadcrumbs, } from '@pluralsh/design-system' import { useMemo, useRef } from 'react' @@ -23,7 +23,6 @@ import { ProvidersSidecar } from '../utils/recipeHelpers' import { Provider, - StackCollection, StackCollectionFragment, useGetStackQuery, } from '../../generated/graphql' @@ -31,6 +30,7 @@ import { import LoadingIndicator from '../utils/LoadingIndicator' import { MARKETPLACE_CRUMB } from '../marketplace/Marketplace' + import { StackContext } from './types' const DIRECTORY = [{ label: 'Stack applications', path: '' }] diff --git a/www/src/components/stack/misc.tsx b/www/src/components/stack/misc.tsx index 3ada2cbd1..4e4f5d996 100644 --- a/www/src/components/stack/misc.tsx +++ b/www/src/components/stack/misc.tsx @@ -1,7 +1,4 @@ -import { - StackCollection, - StackCollectionFragment, -} from '../../generated/graphql' +import { StackCollectionFragment } from '../../generated/graphql' import InstallAppButton from '../utils/InstallAppButton' import { RecipeSubset } from '../utils/recipeHelpers' diff --git a/www/src/components/users/queries.ts b/www/src/components/users/queries.ts index 96bbd2c14..6005268e9 100644 --- a/www/src/components/users/queries.ts +++ b/www/src/components/users/queries.ts @@ -7,7 +7,6 @@ import { TokenAuditFragment, TokenFragment, UserFragment, - WebhookFragment, } from '../../models/user' import { CardFragment } from '../../models/payments' import { PageInfo } from '../../models/misc' diff --git a/www/src/components/utils/tableFetchHelpers.tsx b/www/src/components/utils/tableFetchHelpers.tsx index d122d3b8d..5188a678c 100644 --- a/www/src/components/utils/tableFetchHelpers.tsx +++ b/www/src/components/utils/tableFetchHelpers.tsx @@ -4,6 +4,7 @@ import { InputMaybe } from 'generated/graphql' import { usePrevious } from 'honorable' import { useCallback, useEffect, useMemo, useState } from 'react' import { extendConnection, updateNestedConnection } from 'utils/graphql' + import { POLL_INTERVAL } from './useFetchPaginatedData' type FetchSliceOptions = { diff --git a/www/src/graph/notifications.graphql b/www/src/graph/notifications.graphql index bc02f9a4b..852172848 100644 --- a/www/src/graph/notifications.graphql +++ b/www/src/graph/notifications.graphql @@ -38,4 +38,4 @@ query Notifications($incidentId: ID, $first: Int = 50, $cursor: String) { } } } -} \ No newline at end of file +} diff --git a/www/src/graph/oauth.graphql b/www/src/graph/oauth.graphql index 2c8c5b5b3..5958feb3c 100644 --- a/www/src/graph/oauth.graphql +++ b/www/src/graph/oauth.graphql @@ -31,7 +31,6 @@ fragment OAuthInfo on OauthInfo { authorizeUrl } - fragment Repository on Repository { name icon diff --git a/www/src/graph/recipes.graphql b/www/src/graph/recipes.graphql index ed6506a09..acb301f5e 100644 --- a/www/src/graph/recipes.graphql +++ b/www/src/graph/recipes.graphql @@ -90,7 +90,7 @@ fragment Stack on Stack { name } collections { - ...StackCollection + ...StackCollection } } From 840316e6fddbf61755399f515157b267b2830fb6 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 13:57:48 +0200 Subject: [PATCH 14/17] yarn fix --- www/src/components/layout/WithNotifications.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/src/components/layout/WithNotifications.tsx b/www/src/components/layout/WithNotifications.tsx index f754ce716..7513d7a15 100644 --- a/www/src/components/layout/WithNotifications.tsx +++ b/www/src/components/layout/WithNotifications.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from '@apollo/client' +import { useMutation } from '@apollo/client' import { Div, Flex, P } from 'honorable' import moment from 'moment' import { AppIcon, Button, Card, Markdown } from '@pluralsh/design-system' From 73f997a3a713702d86264b19c94106a019ceaab6 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 14:01:58 +0200 Subject: [PATCH 15/17] fix sidenavs --- www/src/components/profile/MyProfile.tsx | 1 + www/src/components/repository/RepositorySideNav.tsx | 1 + www/src/components/repository/packages/Chart.tsx | 1 + www/src/components/repository/packages/Docker.tsx | 1 + www/src/components/repository/packages/Terraform.tsx | 1 + www/src/components/roadmap/Roadmap.tsx | 1 + www/src/components/stack/Stack.tsx | 1 + 7 files changed, 7 insertions(+) diff --git a/www/src/components/profile/MyProfile.tsx b/www/src/components/profile/MyProfile.tsx index a6de72de9..654ddb4cc 100644 --- a/www/src/components/profile/MyProfile.tsx +++ b/www/src/components/profile/MyProfile.tsx @@ -66,6 +66,7 @@ export function MyProfile() { > {DIRECTORY.map(({ label, path }) => ( {filteredDirectory.map(({ label, path }) => ( {filteredDirectory.map(({ label, textValue, path }) => ( {DIRECTORY.map(({ label, textValue, path }: any) => ( {filteredDirectory.map(({ label, textValue, path }) => ( {TABS.map(({ label, path }) => ( {DIRECTORY.map(({ label, path }) => ( Date: Tue, 8 Oct 2024 14:30:29 +0200 Subject: [PATCH 16/17] update design system --- www/package.json | 2 +- www/yarn.lock | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/www/package.json b/www/package.json index 66fd70d85..201338e37 100644 --- a/www/package.json +++ b/www/package.json @@ -44,7 +44,7 @@ "@nivo/geo": "0.83.0", "@nivo/line": "0.83.0", "@octokit/core": "4.2.1", - "@pluralsh/design-system": "3.74.2", + "@pluralsh/design-system": "3.74.3", "@react-spring/web": "9.7.3", "@stripe/react-stripe-js": "2.1.0", "@stripe/stripe-js": "1.54.0", diff --git a/www/yarn.lock b/www/yarn.lock index a0f90286c..bd55c2079 100644 --- a/www/yarn.lock +++ b/www/yarn.lock @@ -4025,9 +4025,9 @@ __metadata: languageName: node linkType: hard -"@pluralsh/design-system@npm:3.74.2": - version: 3.74.2 - resolution: "@pluralsh/design-system@npm:3.74.2" +"@pluralsh/design-system@npm:3.74.3": + version: 3.74.3 + resolution: "@pluralsh/design-system@npm:3.74.3" dependencies: "@floating-ui/react-dom-interactions": 0.13.3 "@loomhq/loom-embed": 1.5.0 @@ -4073,7 +4073,7 @@ __metadata: react-dom: ">=18.3.1" react-transition-group: ">=4.4.5" styled-components: ">=6.1.13" - checksum: fa35462b29827cc4fb60cd4b5b253b2f27f9014bff4d3bb73f2622204ccd0c78378ba7c2ac09161c80c76fed4e86ad25b2e1d01651ea29d7bb8002569facea8c + checksum: 8d9246768e5f2cb42011aff03b4e54d25d0eb9962f084e849219502567a491d2f051dd7cd8869ac4dc4531f9956cb7e0daf767f42bd4843797d68523733a937d languageName: node linkType: hard @@ -8752,7 +8752,7 @@ __metadata: languageName: node linkType: hard -"confbox@npm:^0.1.7": +"confbox@npm:^0.1.8": version: 0.1.8 resolution: "confbox@npm:0.1.8" checksum: 5c7718ab22cf9e35a31c21ef124156076ae8c9dc65e6463d54961caf5a1d529284485a0fdf83fd23b27329f3b75b0c8c07d2e36c699f5151a2efe903343f976a @@ -12671,15 +12671,15 @@ __metadata: linkType: hard "iterator.prototype@npm:^1.1.2": - version: 1.1.2 - resolution: "iterator.prototype@npm:1.1.2" + version: 1.1.3 + resolution: "iterator.prototype@npm:1.1.3" dependencies: define-properties: ^1.2.1 get-intrinsic: ^1.2.1 has-symbols: ^1.0.3 reflect.getprototypeof: ^1.0.4 set-function-name: ^2.0.1 - checksum: d8a507e2ccdc2ce762e8a1d3f4438c5669160ac72b88b648e59a688eec6bc4e64b22338e74000518418d9e693faf2a092d2af21b9ec7dbf7763b037a54701168 + checksum: 7d2a1f8bcbba7b76f72e956faaf7b25405f4de54430c9d099992e6fb9d571717c3044604e8cdfb8e624cb881337d648030ee8b1541d544af8b338835e3f47ebe languageName: node linkType: hard @@ -14286,7 +14286,7 @@ __metadata: languageName: node linkType: hard -"mlly@npm:^1.4.2, mlly@npm:^1.7.1": +"mlly@npm:^1.4.2, mlly@npm:^1.7.2": version: 1.7.2 resolution: "mlly@npm:1.7.2" dependencies: @@ -15169,13 +15169,13 @@ __metadata: linkType: hard "pkg-types@npm:^1.0.3, pkg-types@npm:^1.2.0": - version: 1.2.0 - resolution: "pkg-types@npm:1.2.0" + version: 1.2.1 + resolution: "pkg-types@npm:1.2.1" dependencies: - confbox: ^0.1.7 - mlly: ^1.7.1 + confbox: ^0.1.8 + mlly: ^1.7.2 pathe: ^1.1.2 - checksum: c9ea31be8c7bf0b760c075d5e39f71d90fcebee316e49688345e9095d520ed766f3bfd560227e3f3c28639399a0641a27193eef60c4802d89cb414e21240bbb5 + checksum: d2e3ad7aef36cc92b17403e61c04db521bf0beb175ccb4d432c284239f00ec32ff37feb072a260613e9ff727911cff1127a083fd52f91b9bec6b62970f385702 languageName: node linkType: hard @@ -20092,7 +20092,7 @@ __metadata: "@nivo/geo": 0.83.0 "@nivo/line": 0.83.0 "@octokit/core": 4.2.1 - "@pluralsh/design-system": 3.74.2 + "@pluralsh/design-system": 3.74.3 "@pluralsh/eslint-config-typescript": 2.5.150 "@pluralsh/stylelint-config": 2.0.10 "@react-spring/web": 9.7.3 From cb726753eda0ca798cc0a26e535875532557752a Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Tue, 8 Oct 2024 16:40:05 +0200 Subject: [PATCH 17/17] disable notifications --- www/src/components/layout/Sidebar.tsx | 124 ++++++++++++-------------- 1 file changed, 57 insertions(+), 67 deletions(-) diff --git a/www/src/components/layout/Sidebar.tsx b/www/src/components/layout/Sidebar.tsx index b71ffc71d..3200c28a6 100644 --- a/www/src/components/layout/Sidebar.tsx +++ b/www/src/components/layout/Sidebar.tsx @@ -10,7 +10,6 @@ import { Link, useLocation } from 'react-router-dom' import { Avatar, Menu, MenuItem } from 'honorable' import { ArrowTopRightIcon, - BellIcon, BrowseAppsIcon, ClusterIcon, CookieIcon, @@ -39,15 +38,6 @@ import { clearLocalStorage } from '../../helpers/localStorage' import Cookiebot from '../../utils/cookiebot' -import { useReadNotificationsMutation } from '../../generated/graphql' - -import { useNotificationsCount } from './WithNotifications' -import { NotificationsPanelOverlay } from './NotificationsPanelOverlay' - -export const SIDEBAR_ICON_HEIGHT = '40px' -export const SIDEBAR_WIDTH = '224px' -export const SMALL_WIDTH = '60px' - type MenuItem = { text: string icon: ReactElement @@ -153,29 +143,29 @@ const SidebarSC = styled(DSSidebar)((_) => ({ overflow: 'auto', })) -const NotificationsCountSC = styled.div(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - color: theme.colors['text-always-white'], - backgroundColor: theme.colors['icon-danger-critical'], - borderRadius: '50%', - fontSize: 10, - height: 15, - width: 15, - position: 'absolute', - left: 16, - top: 2, - userSelect: 'none', -})) +// const NotificationsCountSC = styled.div(({ theme }) => ({ +// display: 'flex', +// alignItems: 'center', +// justifyContent: 'center', +// color: theme.colors['text-always-white'], +// backgroundColor: theme.colors['icon-danger-critical'], +// borderRadius: '50%', +// fontSize: 10, +// height: 15, +// width: 15, +// position: 'absolute', +// left: 16, +// top: 2, +// userSelect: 'none', +// })) function Sidebar(props: Omit, '$variant'>) { const menuItemRef = useRef(null) const menuRef = useRef(null) const [isMenuOpen, setIsMenuOpened] = useState(false) const [collapsed, _setCollapsed] = useState(true) - const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = - useState(false) + // const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = + // useState(false) const [isCreatePublisherModalOpen, setIsCreatePublisherModalOpen] = useState(false) const sidebarWidth = collapsed ? 65 : 256 - 32 // 64 + 1px border @@ -191,20 +181,20 @@ function Sidebar(props: Omit, '$variant'>) { isActiveMenuItem(menuItem, pathname), [pathname] ) - const [readNotifications] = useReadNotificationsMutation() - const notificationsCount = useNotificationsCount() ?? 0 - const notificationsLabel = `${ - notificationsCount > 0 ? `${notificationsCount} ` : '' - } Notifications` - const toggleNotificationPanel = useCallback( - (open) => { - setIsNotificationsPanelOpen(open) - if (!open) { - readNotifications() - } - }, - [setIsNotificationsPanelOpen, readNotifications] - ) + // const [readNotifications] = useReadNotificationsMutation() + // const notificationsCount = useNotificationsCount() ?? 0 + // const notificationsLabel = `${ + // notificationsCount > 0 ? `${notificationsCount} ` : '' + // } Notifications` + // const toggleNotificationPanel = useCallback( + // (open) => { + // setIsNotificationsPanelOpen(open) + // if (!open) { + // readNotifications() + // } + // }, + // [setIsNotificationsPanelOpen, readNotifications] + // ) useClickOutside(menuRef, (event) => { if (!menuItemRef.current?.contains(event.target as any)) { @@ -274,27 +264,27 @@ function Sidebar(props: Omit, '$variant'>) { {/* --- NOTIFICATIONS BELL --- */} - { - event.stopPropagation() - toggleNotificationPanel(!isNotificationsPanelOpen) - }} - active={isNotificationsPanelOpen} - css={{ - position: 'relative', - }} - > - - {notificationsCount > 0 && ( - - {notificationsCount > 99 ? '!' : notificationsCount} - - )} - + {/* { */} + {/* event.stopPropagation() */} + {/* toggleNotificationPanel(!isNotificationsPanelOpen) */} + {/* }} */} + {/* active={isNotificationsPanelOpen} */} + {/* css={{ */} + {/* position: 'relative', */} + {/* }} */} + {/* > */} + {/* */} + {/* {notificationsCount > 0 && ( */} + {/* */} + {/* {notificationsCount > 99 ? '!' : notificationsCount} */} + {/* */} + {/* )} */} + {/* */} {/* --- USER -- */} , '$variant'>) { )} {/* --- NOTIFICATIONS PANEL --- */} - + {/* */} {/* --- CREATE PUBLISHER MODAL --- */}