3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 import { TextMuted } from 'design-system';
21 import React, { useEffect, useState } from 'react';
22 import theme, { colors } from '../../../../app/theme';
23 import Modal from '../../../../components/controls/Modal';
24 import { Button } from '../../../../components/controls/buttons';
25 import CheckIcon from '../../../../components/icons/CheckIcon';
26 import ClearIcon from '../../../../components/icons/ClearIcon';
27 import HelpIcon from '../../../../components/icons/HelpIcon';
28 import { Alert, AlertVariant } from '../../../../components/ui/Alert';
29 import { translate, translateWithParameters } from '../../../../helpers/l10n';
30 import { useCheckGitHubConfigQuery } from '../../../../queries/identity-provider';
31 import { GitHubProvisioningStatus } from '../../../../types/provisioning';
33 const intlPrefix = 'settings.authentication.github.configuration.validation';
35 function ValidityIcon({ valid }: { valid: boolean }) {
36 const color = valid ? theme.colors.success500 : theme.colors.error500;
39 <CheckIcon fill={color} label={translate(`${intlPrefix}.details.valid_label`)} />
41 <ClearIcon fill={color} label={translate(`${intlPrefix}.details.invalid_label`)} />
46 isAutoProvisioning: boolean;
47 selectedOrganizations: string[];
50 export default function GitHubConfigurationValidity({
52 selectedOrganizations,
54 const [openDetails, setOpenDetails] = useState(false);
55 const [messages, setMessages] = useState<string[]>([]);
56 const [alertVariant, setAlertVariant] = useState<AlertVariant>('loading');
57 const { data, isFetching, refetch } = useCheckGitHubConfigQuery(true);
58 const modalHeader = translate(`${intlPrefix}.details.title`);
60 const applicationField = isAutoProvisioning ? 'autoProvisioning' : 'jit';
63 data?.application[applicationField].status === GitHubProvisioningStatus.Success;
65 const failedOrgs = selectedOrganizations.filter((o) => {
66 return !data?.installations.find((i) => i.organization === o);
71 setMessages([translate(`${intlPrefix}.loading`)]);
72 setAlertVariant('loading');
78 ? data.installations.filter(
79 (org) => org[applicationField].status === GitHubProvisioningStatus.Failed,
83 const invalidOrgsMessages = invalidOrgs.map((org) =>
84 translateWithParameters(
85 `${intlPrefix}.invalid_org`,
87 org[applicationField].errorMessage ?? '',
91 if (isValidApp && invalidOrgs.length === 0) {
93 translateWithParameters(
94 `${intlPrefix}.valid${data.installations.length === 1 ? '_one' : ''}`,
96 `settings.authentication.github.form.provisioning_with_github_short.${applicationField}`,
98 data.installations.length === 1
99 ? data.installations[0].organization
100 : data.installations.length,
103 setAlertVariant('success');
104 } else if (isValidApp && !isAutoProvisioning) {
105 setMessages([translate(`${intlPrefix}.valid.short`), ...invalidOrgsMessages]);
106 setAlertVariant('warning');
109 translateWithParameters(
110 `${intlPrefix}.invalid`,
111 data?.application[applicationField].errorMessage ?? '',
113 ...invalidOrgsMessages,
115 setAlertVariant('error');
117 }, [isFetching, isValidApp, isAutoProvisioning, applicationField, data]);
123 variant={alertVariant}
127 aria-busy={isFetching}
129 <div className="sw-flex sw-justify-between sw-items-center">
131 {messages.map((msg) => (
132 <div key={msg}>{msg}</div>
135 <div className="sw-flex">
137 onClick={() => setOpenDetails(true)}
138 disabled={isFetching}
139 className="sw-mr-2 sw-whitespace-nowrap sw-text-center"
141 {translate(`${intlPrefix}.details`)}
144 onClick={() => refetch()}
145 disabled={isFetching}
146 className="sw-whitespace-nowrap sw-text-center"
148 {translate(`${intlPrefix}.test`)}
154 <Modal size="small" contentLabel={modalHeader} onRequestClose={() => setOpenDetails(false)}>
155 <header className="modal-head">
157 {modalHeader} <ValidityIcon valid={isValidApp} />
160 <div className="modal-body modal-container">
162 <Alert variant="error">{data?.application[applicationField].errorMessage}</Alert>
164 <ul className="sw-pl-5">
165 {data?.installations.map((inst) => (
166 <li key={inst.organization}>
168 valid={inst[applicationField].status === GitHubProvisioningStatus.Success}
170 <span className="sw-ml-2">{inst.organization}</span>
171 {inst[applicationField].status === GitHubProvisioningStatus.Failed && (
172 <span> - {inst[applicationField].errorMessage}</span>
176 {failedOrgs.map((fo) => (
178 <HelpIcon fillInner={colors.gray60} fill={colors.white} role="img" />
181 text={translateWithParameters(`${intlPrefix}.details.org_not_found`, fo)}
187 <footer className="modal-foot">
188 <Button onClick={() => setOpenDetails(false)}>{translate('close')}</Button>