2022-11-11 11:24:56 +01:00
|
|
|
|
import { useMemo, useState } from 'react';
|
2022-05-02 12:52:33 +02:00
|
|
|
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
2021-09-30 10:24:16 +02:00
|
|
|
|
import { useStyles } from './ProjectEnvironment.styles';
|
2022-05-09 14:38:12 +02:00
|
|
|
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
|
|
|
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
2022-03-28 10:49:59 +02:00
|
|
|
|
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
|
|
|
|
import ApiError from 'component/common/ApiError/ApiError';
|
|
|
|
|
import useToast from 'hooks/useToast';
|
|
|
|
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
2022-08-04 13:57:25 +02:00
|
|
|
|
import useProject, {
|
|
|
|
|
useProjectNameOrId,
|
|
|
|
|
} from 'hooks/api/getters/useProject/useProject';
|
2022-11-11 11:24:56 +01:00
|
|
|
|
import { Alert, styled, TableBody, TableRow } from '@mui/material';
|
2022-03-28 10:49:59 +02:00
|
|
|
|
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
2021-10-07 12:44:46 +02:00
|
|
|
|
import { Link } from 'react-router-dom';
|
2022-03-28 10:49:59 +02:00
|
|
|
|
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
|
|
|
|
|
import { IProjectEnvironment } from 'interfaces/environments';
|
2021-11-04 14:24:36 +01:00
|
|
|
|
import { getEnabledEnvs } from './helpers';
|
2022-06-21 09:08:37 +02:00
|
|
|
|
import { usePageTitle } from 'hooks/usePageTitle';
|
2022-08-04 13:57:25 +02:00
|
|
|
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
2022-11-11 11:24:56 +01:00
|
|
|
|
import { useTable, useGlobalFilter } from 'react-table';
|
|
|
|
|
import {
|
|
|
|
|
SortableTableHeader,
|
|
|
|
|
Table,
|
|
|
|
|
TableCell,
|
|
|
|
|
TablePlaceholder,
|
|
|
|
|
} from 'component/common/Table';
|
|
|
|
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
|
|
|
|
import { Search } from 'component/common/Search/Search';
|
|
|
|
|
import { EnvironmentNameCell } from 'component/environments/EnvironmentTable/EnvironmentNameCell/EnvironmentNameCell';
|
|
|
|
|
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
|
|
|
|
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
|
|
|
|
import { EnvironmentHideDialog } from './EnvironmentHideDialog/EnvironmentHideDialog';
|
|
|
|
|
import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments';
|
|
|
|
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
|
|
|
|
|
|
|
|
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
|
|
|
|
marginBottom: theme.spacing(4),
|
|
|
|
|
}));
|
2021-09-30 10:24:16 +02:00
|
|
|
|
|
2022-08-04 13:57:25 +02:00
|
|
|
|
const ProjectEnvironmentList = () => {
|
|
|
|
|
const projectId = useRequiredPathParam('projectId');
|
|
|
|
|
const projectName = useProjectNameOrId(projectId);
|
2022-06-21 09:08:37 +02:00
|
|
|
|
usePageTitle(`Project environments – ${projectName}`);
|
2022-08-04 13:57:25 +02:00
|
|
|
|
|
2021-09-30 10:24:16 +02:00
|
|
|
|
// api state
|
2022-01-14 15:50:02 +01:00
|
|
|
|
const { setToastData, setToastApiError } = useToast();
|
2021-09-30 10:24:16 +02:00
|
|
|
|
const { uiConfig } = useUiConfig();
|
2022-03-23 12:55:00 +01:00
|
|
|
|
const { environments, loading, error, refetchEnvironments } =
|
2022-11-11 11:24:56 +01:00
|
|
|
|
useProjectEnvironments(projectId);
|
2021-10-01 12:15:02 +02:00
|
|
|
|
const { project, refetch: refetchProject } = useProject(projectId);
|
2021-10-08 11:23:29 +02:00
|
|
|
|
const { removeEnvironmentFromProject, addEnvironmentToProject } =
|
|
|
|
|
useProjectApi();
|
|
|
|
|
|
2021-09-30 10:24:16 +02:00
|
|
|
|
// local state
|
2022-11-11 11:24:56 +01:00
|
|
|
|
const [selectedEnvironment, setSelectedEnvironment] =
|
|
|
|
|
useState<IProjectEnvironment>();
|
|
|
|
|
const [hideDialog, setHideDialog] = useState(false);
|
2022-05-02 15:52:41 +02:00
|
|
|
|
const { classes: styles } = useStyles();
|
2022-05-12 09:41:36 +02:00
|
|
|
|
const { isOss } = useUiConfig();
|
2021-09-30 10:24:16 +02:00
|
|
|
|
|
2022-11-11 11:24:56 +01:00
|
|
|
|
const projectEnvironments = useMemo<IProjectEnvironment[]>(
|
|
|
|
|
() =>
|
|
|
|
|
environments.map(environment => ({
|
|
|
|
|
...environment,
|
|
|
|
|
projectVisible: project?.environments.includes(
|
|
|
|
|
environment.name
|
|
|
|
|
),
|
|
|
|
|
})),
|
|
|
|
|
[environments, project?.environments]
|
|
|
|
|
);
|
2021-11-04 14:24:36 +01:00
|
|
|
|
|
2021-09-30 10:24:16 +02:00
|
|
|
|
const refetch = () => {
|
2022-03-23 12:55:00 +01:00
|
|
|
|
refetchEnvironments();
|
2021-09-30 10:24:16 +02:00
|
|
|
|
refetchProject();
|
2021-10-08 11:23:29 +02:00
|
|
|
|
};
|
2021-09-30 10:24:16 +02:00
|
|
|
|
|
|
|
|
|
const renderError = () => {
|
|
|
|
|
return (
|
|
|
|
|
<ApiError
|
|
|
|
|
onClick={refetch}
|
|
|
|
|
className={styles.apiError}
|
|
|
|
|
text="Error fetching environments"
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const errorMsg = (enable: boolean): string => {
|
2022-11-11 11:24:56 +01:00
|
|
|
|
return `Got an API error when trying to set the environment as ${
|
|
|
|
|
enable ? 'visible' : 'hidden'
|
|
|
|
|
}`;
|
2021-10-08 11:23:29 +02:00
|
|
|
|
};
|
2021-09-30 10:24:16 +02:00
|
|
|
|
|
2022-02-25 10:55:39 +01:00
|
|
|
|
const toggleEnv = async (env: IProjectEnvironment) => {
|
2022-11-11 11:24:56 +01:00
|
|
|
|
if (env.projectVisible) {
|
|
|
|
|
const enabledEnvs = getEnabledEnvs(projectEnvironments);
|
2021-11-04 14:24:36 +01:00
|
|
|
|
|
|
|
|
|
if (enabledEnvs > 1) {
|
2022-11-11 11:24:56 +01:00
|
|
|
|
setSelectedEnvironment(env);
|
|
|
|
|
setHideDialog(true);
|
2021-11-04 14:24:36 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setToastData({
|
2022-11-11 11:24:56 +01:00
|
|
|
|
title: 'One environment must be visible',
|
|
|
|
|
text: 'You must always have at least one visible environment per project',
|
2021-11-04 14:24:36 +01:00
|
|
|
|
type: 'error',
|
|
|
|
|
});
|
2021-09-30 10:24:16 +02:00
|
|
|
|
} else {
|
|
|
|
|
try {
|
2021-10-01 12:15:02 +02:00
|
|
|
|
await addEnvironmentToProject(projectId, env.name);
|
2022-11-11 11:24:56 +01:00
|
|
|
|
refetch();
|
2021-10-08 11:23:29 +02:00
|
|
|
|
setToastData({
|
2022-11-11 11:24:56 +01:00
|
|
|
|
title: 'Environment set as visible',
|
|
|
|
|
text: 'Environment successfully set as visible.',
|
2021-10-08 11:23:29 +02:00
|
|
|
|
type: 'success',
|
|
|
|
|
});
|
2021-09-30 10:24:16 +02:00
|
|
|
|
} catch (error) {
|
2022-01-14 15:50:02 +01:00
|
|
|
|
setToastApiError(errorMsg(true));
|
2021-09-30 10:24:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-08 11:23:29 +02:00
|
|
|
|
};
|
2021-09-30 10:24:16 +02:00
|
|
|
|
|
2022-11-11 11:24:56 +01:00
|
|
|
|
const onHideConfirm = async () => {
|
|
|
|
|
if (selectedEnvironment) {
|
2021-09-30 10:24:16 +02:00
|
|
|
|
try {
|
2022-11-11 11:24:56 +01:00
|
|
|
|
await removeEnvironmentFromProject(
|
|
|
|
|
projectId,
|
|
|
|
|
selectedEnvironment.name
|
|
|
|
|
);
|
|
|
|
|
refetch();
|
2021-10-08 11:23:29 +02:00
|
|
|
|
setToastData({
|
2022-11-11 11:24:56 +01:00
|
|
|
|
title: 'Environment set as hidden',
|
|
|
|
|
text: 'Environment successfully set as hidden.',
|
2021-10-08 11:23:29 +02:00
|
|
|
|
type: 'success',
|
|
|
|
|
});
|
2021-09-30 10:24:16 +02:00
|
|
|
|
} catch (e) {
|
2022-01-14 15:50:02 +01:00
|
|
|
|
setToastApiError(errorMsg(false));
|
2022-11-11 11:24:56 +01:00
|
|
|
|
} finally {
|
|
|
|
|
setHideDialog(false);
|
2021-09-30 10:24:16 +02:00
|
|
|
|
}
|
2021-10-08 11:23:29 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
2021-09-30 10:24:16 +02:00
|
|
|
|
|
2022-05-12 09:41:36 +02:00
|
|
|
|
const envIsDisabled = (projectName: string) => {
|
|
|
|
|
return isOss() && projectName === 'default';
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-11 11:24:56 +01:00
|
|
|
|
const COLUMNS = useMemo(
|
|
|
|
|
() => [
|
|
|
|
|
{
|
|
|
|
|
Header: 'Name',
|
|
|
|
|
accessor: 'name',
|
|
|
|
|
Cell: ({ row: { original } }: any) => (
|
|
|
|
|
<EnvironmentNameCell environment={original} />
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Header: 'Type',
|
|
|
|
|
accessor: 'type',
|
|
|
|
|
Cell: HighlightCell,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Header: 'Project API tokens',
|
|
|
|
|
accessor: (row: IProjectEnvironment) =>
|
|
|
|
|
row.projectApiTokenCount === 1
|
|
|
|
|
? '1 token'
|
|
|
|
|
: `${row.projectApiTokenCount} tokens`,
|
|
|
|
|
Cell: TextCell,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Header: 'Visible in project',
|
|
|
|
|
accessor: 'enabled',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 1,
|
|
|
|
|
Cell: ({ row: { original } }: any) => (
|
|
|
|
|
<ActionCell>
|
|
|
|
|
<PermissionSwitch
|
|
|
|
|
tooltip={
|
|
|
|
|
original.projectVisible
|
|
|
|
|
? 'Hide environment and disable feature toggles'
|
|
|
|
|
: 'Make it visible'
|
|
|
|
|
}
|
|
|
|
|
size="medium"
|
|
|
|
|
disabled={envIsDisabled(original.name)}
|
|
|
|
|
projectId={projectId}
|
|
|
|
|
permission={UPDATE_PROJECT}
|
|
|
|
|
checked={original.projectVisible}
|
|
|
|
|
onChange={() => toggleEnv(original)}
|
|
|
|
|
/>
|
|
|
|
|
</ActionCell>
|
|
|
|
|
),
|
|
|
|
|
disableGlobalFilter: true,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
[projectEnvironments]
|
|
|
|
|
);
|
2021-09-30 10:24:16 +02:00
|
|
|
|
|
2022-11-11 11:24:56 +01:00
|
|
|
|
const {
|
|
|
|
|
getTableProps,
|
|
|
|
|
getTableBodyProps,
|
|
|
|
|
headerGroups,
|
|
|
|
|
rows,
|
|
|
|
|
prepareRow,
|
|
|
|
|
state: { globalFilter },
|
|
|
|
|
setGlobalFilter,
|
|
|
|
|
} = useTable(
|
|
|
|
|
{
|
|
|
|
|
columns: COLUMNS as any,
|
|
|
|
|
data: projectEnvironments,
|
|
|
|
|
disableSortBy: true,
|
|
|
|
|
},
|
|
|
|
|
useGlobalFilter
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const header = (
|
|
|
|
|
<PageHeader
|
|
|
|
|
title={`Environments (${rows.length})`}
|
|
|
|
|
actions={
|
|
|
|
|
<Search
|
|
|
|
|
initialValue={globalFilter}
|
|
|
|
|
onChange={setGlobalFilter}
|
2021-10-19 13:08:25 +02:00
|
|
|
|
/>
|
2022-05-18 11:26:38 +02:00
|
|
|
|
}
|
2022-11-11 11:24:56 +01:00
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<PageContent header={header} isLoading={loading}>
|
2022-05-18 11:26:38 +02:00
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={uiConfig.flags.E}
|
|
|
|
|
show={
|
|
|
|
|
<div className={styles.container}>
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={Boolean(error)}
|
|
|
|
|
show={renderError()}
|
|
|
|
|
/>
|
2022-11-11 11:24:56 +01:00
|
|
|
|
<StyledAlert severity="info">
|
|
|
|
|
<strong>Important!</strong> In order for your
|
|
|
|
|
application to retrieve configured activation
|
|
|
|
|
strategies for a specific environment, the
|
|
|
|
|
application must use an environment specific API
|
|
|
|
|
token. You can look up the environment-specific{' '}
|
|
|
|
|
<Link to="/admin/api">API tokens here</Link>.
|
2022-05-18 11:26:38 +02:00
|
|
|
|
<br />
|
|
|
|
|
<br />
|
|
|
|
|
Your administrator can configure an
|
2022-11-11 11:24:56 +01:00
|
|
|
|
environment-specific API token to be used in the
|
|
|
|
|
SDK. If you are an administrator you can{' '}
|
|
|
|
|
<Link to="/admin/api">
|
|
|
|
|
create a new API token here
|
|
|
|
|
</Link>
|
|
|
|
|
.
|
|
|
|
|
</StyledAlert>
|
|
|
|
|
<SearchHighlightProvider value={globalFilter}>
|
|
|
|
|
<Table {...getTableProps()} rowHeight="compact">
|
|
|
|
|
<SortableTableHeader
|
|
|
|
|
headerGroups={headerGroups as any}
|
|
|
|
|
/>
|
|
|
|
|
<TableBody {...getTableBodyProps()}>
|
|
|
|
|
{rows.map(row => {
|
|
|
|
|
prepareRow(row);
|
|
|
|
|
return (
|
|
|
|
|
<TableRow
|
|
|
|
|
hover
|
|
|
|
|
{...row.getRowProps()}
|
|
|
|
|
>
|
|
|
|
|
{row.cells.map(cell => (
|
|
|
|
|
<TableCell
|
|
|
|
|
{...cell.getCellProps()}
|
|
|
|
|
>
|
|
|
|
|
{cell.render('Cell')}
|
|
|
|
|
</TableCell>
|
|
|
|
|
))}
|
|
|
|
|
</TableRow>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
|
|
|
|
</SearchHighlightProvider>
|
2022-05-18 11:26:38 +02:00
|
|
|
|
<ConditionallyRender
|
2022-11-11 11:24:56 +01:00
|
|
|
|
condition={rows.length === 0}
|
|
|
|
|
show={
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={globalFilter?.length > 0}
|
|
|
|
|
show={
|
|
|
|
|
<TablePlaceholder>
|
|
|
|
|
No environments found matching
|
|
|
|
|
“
|
|
|
|
|
{globalFilter}
|
|
|
|
|
”
|
|
|
|
|
</TablePlaceholder>
|
|
|
|
|
}
|
|
|
|
|
elseShow={
|
|
|
|
|
<TablePlaceholder>
|
|
|
|
|
No environments available. Get
|
|
|
|
|
started by adding one.
|
|
|
|
|
</TablePlaceholder>
|
|
|
|
|
}
|
|
|
|
|
/>
|
2022-05-18 11:26:38 +02:00
|
|
|
|
}
|
2022-11-11 11:24:56 +01:00
|
|
|
|
/>
|
|
|
|
|
<EnvironmentHideDialog
|
|
|
|
|
environment={selectedEnvironment}
|
|
|
|
|
open={hideDialog}
|
|
|
|
|
setOpen={setHideDialog}
|
|
|
|
|
onConfirm={onHideConfirm}
|
2022-05-18 11:26:38 +02:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
elseShow={
|
|
|
|
|
<Alert security="success">
|
|
|
|
|
This feature has not been Unleashed for you yet.
|
|
|
|
|
</Alert>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</PageContent>
|
2021-09-30 10:24:16 +02:00
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default ProjectEnvironmentList;
|