1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-01 01:18:10 +02:00

Remove integrationsRework flag (#4770)

https://linear.app/unleash/issue/1-1359/remove-integrationsrework-flag
This commit is contained in:
Tymoteusz Czech 2023-09-20 16:27:40 +02:00 committed by GitHub
parent eeeedffa8c
commit 18f7d0f9e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 14 additions and 620 deletions

View File

@ -43,7 +43,6 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { IntegrationDelete } from './IntegrationDelete/IntegrationDelete';
import { IntegrationStateSwitch } from './IntegrationStateSwitch/IntegrationStateSwitch';
import { capitalizeFirst } from 'utils/capitalizeFirst';
import { useUiFlag } from 'hooks/useUiFlag';
import { IntegrationHowToSection } from '../IntegrationHowToSection/IntegrationHowToSection';
type IntegrationFormProps = {
@ -77,7 +76,6 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
label: event,
}));
const { uiConfig } = useUiConfig();
const integrationsRework = useUiFlag('integrationsRework');
const [formValues, setFormValues] = useState(initialValues);
const [errors, setErrors] = useState<{
containsErrors: boolean;
@ -221,14 +219,14 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
try {
if (editMode) {
await updateAddon(formValues as AddonSchema);
navigate(integrationsRework ? '/integrations' : '/addons');
navigate('/integrations');
setToastData({
type: 'success',
title: 'Integration updated successfully',
});
} else {
await createAddon(formValues as Omit<AddonSchema, 'id'>);
navigate(integrationsRework ? '/integrations' : '/addons');
navigate('/integrations');
setToastData({
type: 'success',
confetti: true,
@ -393,19 +391,12 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
/>
</div>
</StyledConfigurationSection>
<ConditionallyRender
condition={Boolean(integrationsRework && editMode)}
show={() => (
<>
<Divider />
<section>
<IntegrationDelete
id={(formValues as AddonSchema).id}
/>
</section>
</>
)}
/>
<Divider />
<section>
<IntegrationDelete
id={(formValues as AddonSchema).id}
/>
</section>
</StyledContainer>
</StyledForm>
</FormTemplate>

View File

@ -1,38 +0,0 @@
import { styled } from '@mui/material';
import { Badge } from 'component/common/Badge/Badge';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
import { AddonTypeSchema } from 'openapi';
const StyledBadge = styled(Badge)(({ theme }) => ({
cursor: 'pointer',
marginLeft: theme.spacing(1),
}));
interface IAddonNameCellProps {
provider: Pick<
AddonTypeSchema,
'displayName' | 'description' | 'deprecated'
>;
}
/**
* @deprecated Remove when integrationsRework flag is removed
*/
export const AddonNameCell = ({ provider }: IAddonNameCellProps) => (
<HighlightCell
value={provider.displayName}
subtitle={provider.description}
afterTitle={
<ConditionallyRender
condition={Boolean(provider.deprecated)}
show={
<HtmlTooltip title={provider.deprecated} arrow>
<StyledBadge color="neutral">Deprecated</StyledBadge>
</HtmlTooltip>
}
/>
}
/>
);

View File

@ -1,21 +0,0 @@
import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons';
import { AvailableAddons } from './AvailableAddons/AvailableAddons';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useAddons from 'hooks/api/getters/useAddons/useAddons';
/**
* @deprecated remove with `integrationsRework` flag
*/
export const AddonsList = () => {
const { providers, addons, loading } = useAddons();
return (
<>
<ConditionallyRender
condition={addons.length > 0}
show={<ConfiguredAddons />}
/>
<AvailableAddons loading={loading} providers={providers} />
</>
);
};

View File

@ -1,172 +0,0 @@
import { useMemo } from 'react';
import { PageContent } from 'component/common/PageContent/PageContent';
import {
Table,
SortableTableHeader,
TableBody,
TableCell,
TableRow,
TablePlaceholder,
} from 'component/common/Table';
import { useTable, useSortBy } from 'react-table';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { sortTypes } from 'utils/sortTypes';
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
import { ConfigureAddonsButton } from './ConfigureAddonButton/ConfigureAddonsButton';
import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
import { AddonNameCell } from '../AddonNameCell/AddonNameCell';
import type { AddonTypeSchema } from 'openapi';
interface IAvailableAddonsProps {
providers: AddonTypeSchema[];
loading: boolean;
}
/**
* @deprecated Remove when integrationsRework flag is removed
*/
export const AvailableAddons = ({
providers,
loading,
}: IAvailableAddonsProps) => {
const data = useMemo(() => {
if (loading) {
return Array(5).fill({
name: 'Provider name',
description: 'Provider description when loading',
});
}
return providers.map(
({ name, displayName, description, deprecated, installation }) => ({
name,
displayName,
description,
deprecated,
installation,
})
);
}, [providers, loading]);
const columns = useMemo(
() => [
{
id: 'Icon',
Cell: ({
row: {
original: { name },
},
}: any) => {
return (
<IconCell
icon={<IntegrationIcon name={name as string} />}
/>
);
},
},
{
Header: 'Name',
accessor: 'name',
width: '90%',
Cell: ({ row: { original } }: any) => (
<AddonNameCell provider={original} />
),
sortType: 'alphanumeric',
},
{
id: 'Actions',
align: 'center',
Cell: ({ row: { original } }: any) => (
<ActionCell>
<ConfigureAddonsButton provider={original} />
</ActionCell>
),
width: 150,
disableSortBy: true,
},
{
accessor: 'description',
disableSortBy: true,
},
],
[]
);
const initialState = useMemo(
() => ({
sortBy: [{ id: 'name', desc: false }],
hiddenColumns: ['description'],
}),
[]
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state: { globalFilter },
} = useTable(
{
columns: columns as any[], // TODO: fix after `react-table` v8 update
data,
initialState,
sortTypes,
autoResetGlobalFilter: false,
autoResetSortBy: false,
disableSortRemove: true,
},
useSortBy
);
return (
<PageContent
isLoading={loading}
header={<PageHeader title={`Available addons (${rows.length})`} />}
>
<Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups} />
<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>
<ConditionallyRender
condition={rows.length === 0}
show={
<ConditionallyRender
condition={globalFilter?.length > 0}
show={
<TablePlaceholder>
No providers found matching &ldquo;
{globalFilter}
&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
No providers available.
</TablePlaceholder>
}
/>
}
/>
</PageContent>
);
};

View File

@ -1,29 +0,0 @@
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { CREATE_ADDON } from 'component/providers/AccessProvider/permissions';
import type { AddonTypeSchema } from 'openapi';
import { useNavigate } from 'react-router-dom';
interface IConfigureAddonsButtonProps {
provider: AddonTypeSchema;
}
/**
* @deprecated Remove when integrationsRework flag is removed
*/
export const ConfigureAddonsButton = ({
provider,
}: IConfigureAddonsButtonProps) => {
const navigate = useNavigate();
return (
<PermissionButton
permission={CREATE_ADDON}
variant="outlined"
onClick={() => {
navigate(`/addons/create/${provider.name}`);
}}
>
Configure
</PermissionButton>
);
};

View File

@ -1,241 +0,0 @@
import { Table, TableBody, TableCell, TableRow } from 'component/common/Table';
import { useMemo, useState, useCallback } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageContent } from 'component/common/PageContent/PageContent';
import useAddons from 'hooks/api/getters/useAddons/useAddons';
import useToast from 'hooks/useToast';
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { formatUnknownError } from 'utils/formatUnknownError';
import { sortTypes } from 'utils/sortTypes';
import { useTable, useSortBy } from 'react-table';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { SortableTableHeader, TablePlaceholder } from 'component/common/Table';
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
import { ConfiguredAddonsActionsCell } from './ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell';
import { AddonNameCell } from '../AddonNameCell/AddonNameCell';
import { AddonSchema } from 'openapi';
/**
* @deprecated Remove when integrationsRework flag is removed
*/
export const ConfiguredAddons = () => {
const { refetchAddons, addons, providers, loading } = useAddons();
const { updateAddon, removeAddon } = useAddonsApi();
const { setToastData, setToastApiError } = useToast();
const [showDelete, setShowDelete] = useState(false);
const [deletedAddon, setDeletedAddon] = useState<AddonSchema>({
id: 0,
provider: '',
description: '',
enabled: false,
events: [],
parameters: {},
});
const data = useMemo(() => {
if (loading) {
return Array(5).fill({
name: 'Addon name',
description: 'Addon description when loading',
});
}
return addons.map(addon => ({
...addon,
}));
}, [addons, loading]);
const toggleAddon = useCallback(
async (addon: AddonSchema) => {
try {
await updateAddon({ ...addon, enabled: !addon.enabled });
refetchAddons();
setToastData({
type: 'success',
title: 'Success',
text: !addon.enabled
? 'Addon is now enabled'
: 'Addon is now disabled',
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
throw error; // caught by optimistic update
}
},
[setToastApiError, refetchAddons, setToastData, updateAddon]
);
const columns = useMemo(
() => [
{
accessor: 'id',
Cell: ({
row: {
original: { provider },
},
}: any) => (
<IconCell
icon={<IntegrationIcon name={provider as string} />}
/>
),
disableSortBy: true,
},
{
Header: 'Name',
accessor: 'provider',
width: '90%',
Cell: ({
row: {
original: { provider, description },
},
}: any) => (
<AddonNameCell
provider={{
...(providers.find(
({ name }) => name === provider
) || {
displayName: provider,
}),
description,
}}
/>
),
sortType: 'alphanumeric',
},
{
Header: 'Actions',
id: 'Actions',
align: 'center',
Cell: ({
row: { original },
}: {
row: { original: AddonSchema };
}) => (
<ConfiguredAddonsActionsCell
key={original.id}
setShowDelete={setShowDelete}
toggleAddon={toggleAddon}
setDeletedAddon={setDeletedAddon}
original={original}
/>
),
width: 150,
disableSortBy: true,
},
{
accessor: 'description',
disableSortBy: true,
},
],
[toggleAddon]
);
const initialState = useMemo(
() => ({
sortBy: [
{ id: 'provider', desc: false },
{ id: 'id', desc: false },
],
hiddenColumns: ['description'],
}),
[]
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state: { globalFilter },
} = useTable(
{
columns: columns as any[], // TODO: fix after `react-table` v8 update
data,
initialState,
sortTypes,
autoResetGlobalFilter: false,
autoResetSortBy: false,
disableSortRemove: true,
},
useSortBy
);
const onRemoveAddon = async (addon: AddonSchema) => {
try {
await removeAddon(addon.id);
refetchAddons();
setToastData({
type: 'success',
title: 'Success',
text: 'Deleted addon successfully',
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
return (
<PageContent
isLoading={loading}
header={<PageHeader title={`Configured addons (${rows.length})`} />}
sx={theme => ({ marginBottom: theme.spacing(2) })}
>
<Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups} />
<TableBody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row);
return (
<TableRow hover {...row.getRowProps()}>
{row.cells.map(cell => (
<TableCell
{...cell.getCellProps()}
padding="none"
>
{cell.render('Cell')}
</TableCell>
))}
</TableRow>
);
})}
</TableBody>
</Table>
<ConditionallyRender
condition={rows.length === 0}
show={
<ConditionallyRender
condition={globalFilter?.length > 0}
show={
<TablePlaceholder>
No addons found matching &ldquo;
{globalFilter}
&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
No addons configured
</TablePlaceholder>
}
/>
}
/>
<Dialogue
open={showDelete}
onClick={() => {
onRemoveAddon(deletedAddon);
setShowDelete(false);
}}
onClose={() => {
setShowDelete(false);
}}
title="Confirm deletion"
>
<div>Are you sure you want to delete this Addon?</div>
</Dialogue>
</PageContent>
);
};

View File

@ -1,76 +0,0 @@
import { Edit, Delete } from '@mui/icons-material';
import { Tooltip } from '@mui/material';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
import { useOptimisticUpdate } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/hooks/useOptimisticUpdate';
import {
UPDATE_ADDON,
DELETE_ADDON,
} from 'component/providers/AccessProvider/permissions';
import { AddonSchema } from 'openapi';
import { useNavigate } from 'react-router-dom';
interface IConfiguredAddonsActionsCellProps {
toggleAddon: (addon: AddonSchema) => Promise<void>;
original: AddonSchema;
setShowDelete: React.Dispatch<React.SetStateAction<boolean>>;
setDeletedAddon: React.Dispatch<React.SetStateAction<AddonSchema>>;
}
/**
* @deprecated Remove when integrationsRework flag is removed
*/
export const ConfiguredAddonsActionsCell = ({
toggleAddon,
setShowDelete,
setDeletedAddon,
original,
}: IConfiguredAddonsActionsCellProps) => {
const navigate = useNavigate();
const [isEnabled, setIsEnabled, rollbackIsChecked] =
useOptimisticUpdate<boolean>(original.enabled);
const onClick = () => {
setIsEnabled(!isEnabled);
toggleAddon(original).catch(rollbackIsChecked);
};
return (
<ActionCell>
<Tooltip
title={
isEnabled
? `Disable addon ${original.provider}`
: `Enable addon ${original.provider}`
}
arrow
describeChild
>
<PermissionSwitch
permission={UPDATE_ADDON}
checked={isEnabled}
onClick={onClick}
/>
</Tooltip>
<ActionCell.Divider />
<PermissionIconButton
permission={UPDATE_ADDON}
tooltipProps={{ title: 'Edit Addon' }}
onClick={() => navigate(`/addons/edit/${original.id}`)}
>
<Edit />
</PermissionIconButton>
<PermissionIconButton
permission={DELETE_ADDON}
tooltipProps={{ title: 'Remove Addon' }}
onClick={() => {
setDeletedAddon(original);
setShowDelete(true);
}}
>
<Delete />
</PermissionIconButton>
</ActionCell>
);
};

View File

@ -308,11 +308,7 @@ exports[`returns all baseRoutes 1`] = `
{
"component": [Function],
"hidden": false,
"menu": {
"advanced": true,
"mobile": true,
},
"notFlag": "integrationsRework",
"menu": {},
"path": "/addons",
"title": "Addons",
"type": "protected",
@ -343,7 +339,6 @@ exports[`returns all baseRoutes 1`] = `
},
{
"component": [Function],
"flag": "integrationsRework",
"hidden": false,
"menu": {
"advanced": true,

View File

@ -43,9 +43,9 @@ import { LazyAdmin } from 'component/admin/LazyAdmin';
import { LazyProject } from 'component/project/Project/LazyProject';
import { LoginHistory } from 'component/loginHistory/LoginHistory';
import { FeatureTypesList } from 'component/featureTypes/FeatureTypesList';
import { AddonsList } from 'component/integrations/IntegrationList/AddonsList';
import { ViewIntegration } from 'component/integrations/ViewIntegration/ViewIntegration';
import { ApplicationList } from '../application/ApplicationList/ApplicationList';
import { AddonRedirect } from 'component/integrations/AddonRedirect/AddonRedirect';
export const routes: IRoute[] = [
// Splash
@ -306,8 +306,7 @@ export const routes: IRoute[] = [
path: '/addons/create/:providerId',
parent: '/addons',
title: 'Create',
component: CreateIntegration,
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
component: AddonRedirect,
type: 'protected',
menu: {},
},
@ -315,21 +314,17 @@ export const routes: IRoute[] = [
path: '/addons/edit/:addonId',
parent: '/addons',
title: 'Edit',
component: EditIntegration,
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
component: AddonRedirect,
type: 'protected',
menu: {},
},
{
path: '/addons',
title: 'Addons',
component: AddonsList,
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
component: AddonRedirect,
hidden: false,
type: 'protected',
notFlag: 'integrationsRework',
menu: { mobile: true, advanced: true },
// TODO: remove 'addons' from menu after removing `integrationsRework` menu flag
menu: {},
},
{
path: '/integrations/create/:providerId',
@ -362,7 +357,6 @@ export const routes: IRoute[] = [
hidden: false,
type: 'protected',
menu: { mobile: true, advanced: true },
flag: 'integrationsRework',
},
// Segments

View File

@ -59,7 +59,6 @@ export type UiFlags = {
customRootRolesKillSwitch?: boolean;
strategyVariant?: boolean;
lastSeenByEnvironment?: boolean;
integrationsRework?: boolean;
multipleRoles?: boolean;
featureNamingPattern?: boolean;
doraMetrics?: boolean;

View File

@ -87,7 +87,6 @@ exports[`should create default config 1`] = `
"featuresExportImport": true,
"filterInvalidClientMetrics": false,
"googleAuthEnabled": false,
"integrationsRework": false,
"lastSeenByEnvironment": false,
"maintenanceMode": false,
"messageBanner": {
@ -127,7 +126,6 @@ exports[`should create default config 1`] = `
"featuresExportImport": true,
"filterInvalidClientMetrics": false,
"googleAuthEnabled": false,
"integrationsRework": false,
"lastSeenByEnvironment": false,
"maintenanceMode": false,
"messageBanner": {

View File

@ -24,7 +24,6 @@ export type IFlagKey =
| 'filterInvalidClientMetrics'
| 'lastSeenByEnvironment'
| 'customRootRolesKillSwitch'
| 'integrationsRework'
| 'multipleRoles'
| 'featureNamingPattern'
| 'doraMetrics'
@ -116,10 +115,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES_KILL_SWITCH,
false,
),
integrationsRework: parseEnvVarBoolean(
process.env.UNLEASH_INTEGRATIONS,
false,
),
multipleRoles: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_MULTIPLE_ROLES,
false,

View File

@ -39,7 +39,6 @@ process.nextTick(async () => {
responseTimeWithAppNameKillSwitch: false,
slackAppAddon: true,
lastSeenByEnvironment: true,
integrationsRework: true,
featureNamingPattern: true,
doraMetrics: true,
variantTypeNumber: true,