From 1280c19e75d4bd97a9663db7788f60a7620f2300 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Mon, 31 Jul 2023 20:04:28 +1000 Subject: [PATCH] update already installed states --- .../Integrations/OrganizationPicker.tsx | 130 +++++++++++++ .../interfaces/Integrations/ProjectLinker.tsx | 9 +- .../IntegrationsLayout/Integrations.utils.ts | 54 ++++++ studio/components/ui/OrganizationPicker.tsx | 115 ------------ studio/pages/integrations/github/install.tsx | 175 ++++++----------- studio/pages/integrations/vercel/install.tsx | 177 +++--------------- .../state/github-integration-installation.ts | 15 -- 7 files changed, 263 insertions(+), 412 deletions(-) create mode 100644 studio/components/interfaces/Integrations/OrganizationPicker.tsx create mode 100644 studio/components/layouts/IntegrationsLayout/Integrations.utils.ts delete mode 100644 studio/components/ui/OrganizationPicker.tsx delete mode 100644 studio/state/github-integration-installation.ts diff --git a/studio/components/interfaces/Integrations/OrganizationPicker.tsx b/studio/components/interfaces/Integrations/OrganizationPicker.tsx new file mode 100644 index 00000000000..5b6f3fe4d7c --- /dev/null +++ b/studio/components/interfaces/Integrations/OrganizationPicker.tsx @@ -0,0 +1,130 @@ +import { getHasInstalledObject } from 'components/layouts/IntegrationsLayout/Integrations.utils' +import { useIntegrationsQuery } from 'data/integrations/integrations-query' +import { IntegrationName } from 'data/integrations/integrations.types' +import { useOrganizationsQuery } from 'data/organizations/organizations-query' +import { useMemo, useRef, useState } from 'react' + +import { Organization } from 'types' +import { + Badge, + Button, + CommandEmpty_Shadcn_, + CommandGroup_Shadcn_, + CommandInput_Shadcn_, + CommandItem_Shadcn_, + CommandList_Shadcn_, + Command_Shadcn_, + IconChevronDown, + IconHexagon, + PopoverContent_Shadcn_, + PopoverTrigger_Shadcn_, + Popover_Shadcn_, +} from 'ui' + +export interface OrganizationPickerProps { + integrationName: IntegrationName + configurationId?: string + selectedOrg: Organization | null + onSelectedOrgChange: (organization: Organization) => void +} + +const OrganizationPicker = ({ + integrationName, + configurationId, + selectedOrg, + onSelectedOrgChange, +}: OrganizationPickerProps) => { + const [open, setOpen] = useState(false) + const ref = useRef(null) + + const { data: integrationData } = useIntegrationsQuery() + const { data: organizationsData, isLoading: isLoadingOrganization } = useOrganizationsQuery() + + const installed = useMemo( + () => + integrationData && organizationsData + ? getHasInstalledObject({ + integrationName, + integrationData, + organizationsData, + installationId: configurationId, + }) + : {}, + [configurationId, integrationData, integrationName, organizationsData] + ) + + return ( + <> + + + + + + + + + No results found. + + {organizationsData?.map((org) => { + return ( + { + const org = organizationsData?.find( + (org) => org.slug.toLowerCase() === slug.toLowerCase() + ) + if (org) { + onSelectedOrgChange(org) + } + + setOpen(false) + }} + > + + {org.name}{' '} + {configurationId && installed[org.slug] && ( + + Integration Installed + + )} + + ) + })} + + + + + + + ) +} + +export default OrganizationPicker diff --git a/studio/components/interfaces/Integrations/ProjectLinker.tsx b/studio/components/interfaces/Integrations/ProjectLinker.tsx index 8aefbcc265e..ac592a45df6 100644 --- a/studio/components/interfaces/Integrations/ProjectLinker.tsx +++ b/studio/components/interfaces/Integrations/ProjectLinker.tsx @@ -205,14 +205,7 @@ const ProjectLinker = ({
- - - + {integrationIcon}
[ + org.slug, + Boolean( + integrationData.find( + (integration) => + integration.organization.slug === org.slug && + integration.integration.name === 'Vercel' + ) + ), + ]) + .filter(([, v]) => Boolean(v)) + ) + } + + if (integrationName === 'GitHub') { + return Object.fromEntries( + organizationsData + .map((org) => [ + org.slug, + Boolean( + integrationData.find( + (integration) => + integration.organization.slug === org.slug && + integration.integration.name === 'GitHub' && + integration.metadata !== undefined && + 'installation_id' in integration.metadata && + String(integration.metadata.installation_id) === String(installationId) + ) + ), + ]) + .filter(([, v]) => Boolean(v)) + ) + } + + return {} +} diff --git a/studio/components/ui/OrganizationPicker.tsx b/studio/components/ui/OrganizationPicker.tsx deleted file mode 100644 index c6308ef4a6b..00000000000 --- a/studio/components/ui/OrganizationPicker.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { useIntegrationsQuery } from 'data/integrations/integrations-query' -import { IntegrationName } from 'data/integrations/integrations.types' -import { useOrganizationsQuery } from 'data/organizations/organizations-query' -import { useState } from 'react' -import { Organization } from 'types' -import { Badge, IconHexagon, Listbox } from 'ui' - -export interface OrganizationPickerProps { - label?: string - onSelectedOrgChange?: (org: Organization) => void - integrationName: IntegrationName -} - -const OrganizationPicker = ({ - label = 'Choose an organization', - onSelectedOrgChange, - integrationName, -}: OrganizationPickerProps) => { - const [selectedOrg, setSelectedOrg] = useState(null) - - /** - * array of integrations installed on all - */ - const { data: integrationData, isLoading: integrationDataLoading } = useIntegrationsQuery() - - /** - * filter integrations to match integrationName - */ - const vercelIntegrationsInstalled = integrationData?.filter( - (integration) => integration.integration.name === 'Vercel' - ) - - const { data, isLoading } = useOrganizationsQuery({ - onSuccess(organizations) { - const firstOrg = organizations?.[0] - if (firstOrg && selectedOrg === null) { - setSelectedOrg(firstOrg) - onSelectedOrgChange?.(firstOrg) - } - }, - }) - - function _onSelectedOrgChange(slug: string) { - const org = data?.find((org) => org.slug === slug) - - if (org) { - setSelectedOrg(org) - onSelectedOrgChange?.(org) - } - } - - if (isLoading || integrationDataLoading) { - return ( - - - Loading... - - - ) - } - - /** - * Organization type with `installationInstalled` added - */ - interface OrganizationsResponseWithInstalledData extends Organization { - installationInstalled?: boolean - } - - /** - * A flat array of org slugs that have integration installed - */ - const flatInstalledConnectionsIds = - integrationData && integrationData.length > 0 - ? integrationData?.map((x) => x.organization.slug) - : [] - - /** - * Organizations with extra `installationInstalled` attribute - * Used to show label/badge and allow/disallow installing - */ - const organizationsWithInstalledData: OrganizationsResponseWithInstalledData[] = data - ? data.map((org) => { - return { - ...org, - installationInstalled: !flatInstalledConnectionsIds.includes(org.slug) ? true : false, - } - }) - : [] - - return ( - - {organizationsWithInstalledData?.map((org) => { - const label = ( -
- {org.name} - {org.installationInstalled && Integration Installed} -
- ) - return ( - } - > - {label} - - ) - })} -
- ) -} - -export default OrganizationPicker diff --git a/studio/pages/integrations/github/install.tsx b/studio/pages/integrations/github/install.tsx index d781df31966..a3453862232 100644 --- a/studio/pages/integrations/github/install.tsx +++ b/studio/pages/integrations/github/install.tsx @@ -1,18 +1,28 @@ import { useRouter } from 'next/router' -import { useState } from 'react' +import { useMemo, useState } from 'react' import { useParams } from 'common' +import OrganizationPicker from 'components/interfaces/Integrations/OrganizationPicker' import { Markdown } from 'components/interfaces/Markdown' import GitHubIntegrationWindowLayout from 'components/layouts/IntegrationsLayout/GitHubIntegrationWindowLayout' +import { getHasInstalledObject } from 'components/layouts/IntegrationsLayout/Integrations.utils' import { ScaffoldColumn, ScaffoldContainer, ScaffoldDivider } from 'components/layouts/Scaffold' import { useGitHubIntegrationCreateMutation } from 'data/integrations/github-integration-create-mutation' import { useIntegrationsQuery } from 'data/integrations/integrations-query' -import { IntegrationName } from 'data/integrations/integrations.types' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useStore } from 'hooks' -import { useGitHubIntegrationInstallationState } from 'state/github-integration-installation' import { NextPageWithLayout, Organization } from 'types' -import { Alert, Badge, Button, IconBook, IconHexagon, IconLifeBuoy, Listbox, LoadingLine } from 'ui' +import { + Alert, + AlertDescription_Shadcn_, + AlertTitle_Shadcn_, + Alert_Shadcn_, + Button, + IconAlertTriangle, + IconBook, + IconLifeBuoy, + LoadingLine, +} from 'ui' /** * Organization type with `installationInstalled` added @@ -28,8 +38,6 @@ const GitHubIntegration: NextPageWithLayout = () => { const [selectedOrg, setSelectedOrg] = useState(null) const [organizationIntegrationId, setOrganizationIntegrationId] = useState(null) - const snapshot = useGitHubIntegrationInstallationState() - /** * Fetch the list of organization based integration installations for GitHub. * @@ -37,44 +45,15 @@ const GitHubIntegration: NextPageWithLayout = () => { */ const { data: integrationData } = useIntegrationsQuery() - const { data: organizationsData, isLoading: isLoadingOrganizationsQuery } = useOrganizationsQuery( - { - onSuccess(organizations) { - const firstOrg = organizations?.[0] - if (firstOrg && selectedOrg === null) { - setSelectedOrg(firstOrg) - snapshot.setSelectedOrganizationSlug(firstOrg.slug) - router.query.organizationSlug = firstOrg.slug - } - }, - } - ) - - /** - * Flat array of org slugs that have integration installed - * - */ - const flatInstalledConnectionsIds: string[] | [] = - integrationData && integrationData.length > 0 - ? integrationData - .filter((x) => x.integration.name === 'Vercel') - .map((x) => x.organization.slug) - : [] - - /** - * Organizations with extra `installationInstalled` attribute - * - * Used to show label/badge and allow/disallow installing - * - */ - const organizationsWithInstalledData: OrganizationsResponseWithInstalledData[] = organizationsData - ? organizationsData.map((org) => { - return { - ...org, - installationInstalled: flatInstalledConnectionsIds.includes(org.slug) ? true : false, - } - }) - : [] + const { data: organizationsData } = useOrganizationsQuery({ + onSuccess(organizations) { + const firstOrg = organizations?.[0] + if (firstOrg && selectedOrg === null) { + setSelectedOrg(firstOrg) + router.query.organizationSlug = firstOrg.slug + } + }, + }) const { mutate, isLoading: isLoadingGitHubIntegrationCreateMutation } = useGitHubIntegrationCreateMutation({ @@ -94,6 +73,19 @@ const GitHubIntegration: NextPageWithLayout = () => { }, }) + const installed = useMemo( + () => + integrationData && organizationsData + ? getHasInstalledObject({ + integrationName: 'GitHub', + integrationData, + organizationsData, + installationId, + }) + : {}, + [installationId, integrationData, organizationsData] + ) + function onInstall() { const orgSlug = selectedOrg?.slug @@ -131,7 +123,7 @@ const GitHubIntegration: NextPageWithLayout = () => { } } - const dataLoading = isLoadingGitHubIntegrationCreateMutation || isLoadingOrganizationsQuery + const disableInstallationForm = Boolean(selectedOrg && installed[selectedOrg.slug]) return ( <> @@ -146,18 +138,29 @@ const GitHubIntegration: NextPageWithLayout = () => { { - router.query.organizationSlug = e.slug - setSelectedOrg(e) + selectedOrg={selectedOrg} + onSelectedOrgChange={(org) => { + router.query.organizationSlug = org.slug + setSelectedOrg(org) }} - dataLoading={dataLoading} + configurationId={installationId} /> + {disableInstallationForm && ( + + + + GitHub Integration is already installed. + + + You will need to choose another organization to install the integration. + + + )}
- - - - - - No results found. - - {organizationsWithInstalledData?.map((org) => { - return ( - { - if (slug) onSelectedOrgChange(slug) - setOpen(false) - }} - > - - {org.name}{' '} - {org?.installationInstalled && ( - - Integration Installed - - )} - - ) - })} - - - - - - - ) -} - export default VercelIntegration diff --git a/studio/state/github-integration-installation.ts b/studio/state/github-integration-installation.ts deleted file mode 100644 index 8d134d2b168..00000000000 --- a/studio/state/github-integration-installation.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { proxy, snapshot, useSnapshot } from 'valtio' - -export const githubIntegrationInstallationState = proxy({ - selectedOrganizationSlug: '' as string, - setSelectedOrganizationSlug: (selectedOrganizationSlug: string) => { - githubIntegrationInstallationState.selectedOrganizationSlug = selectedOrganizationSlug - }, -}) - -export const getGitHubIntegrationInstallationState = () => - snapshot(githubIntegrationInstallationState) - -export const useGitHubIntegrationInstallationState = ( - options?: Parameters[1] -) => useSnapshot(githubIntegrationInstallationState, options)