mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: new variants table (#1025)
* fix: cleanup * fix: text * fix: stable references * refactor: fix VARIANT_WEIGH test id * refactor: fix variant element selection in e2e test * fix: update variants table * fix: refactor action cell Co-authored-by: olav <mail@olav.io>
This commit is contained in:
parent
f4d02e37b7
commit
7c52f0fcc8
@ -6,6 +6,8 @@ const ENTERPRISE = Boolean(Cypress.env('ENTERPRISE'));
|
||||
const randomId = String(Math.random()).split('.')[1];
|
||||
const featureToggleName = `unleash-e2e-${randomId}`;
|
||||
const baseUrl = Cypress.config().baseUrl;
|
||||
const variant1 = 'variant1';
|
||||
const variant2 = 'variant2';
|
||||
let strategyId = '';
|
||||
|
||||
// Disable the prod guard modal by marking it as seen.
|
||||
@ -233,9 +235,6 @@ describe('feature', () => {
|
||||
});
|
||||
|
||||
it('can add two variant to the feature', () => {
|
||||
const variantName = 'my-new-variant';
|
||||
const secondVariantName = 'my-second-variant';
|
||||
|
||||
cy.visit(`/projects/default/features/${featureToggleName}/variants`);
|
||||
|
||||
cy.intercept(
|
||||
@ -245,26 +244,26 @@ describe('feature', () => {
|
||||
if (req.body.length === 1) {
|
||||
expect(req.body[0].op).to.equal('add');
|
||||
expect(req.body[0].path).to.match(/\//);
|
||||
expect(req.body[0].value.name).to.equal(variantName);
|
||||
expect(req.body[0].value.name).to.equal(variant1);
|
||||
} else if (req.body.length === 2) {
|
||||
expect(req.body[0].op).to.equal('replace');
|
||||
expect(req.body[0].path).to.match(/weight/);
|
||||
expect(req.body[0].value).to.equal(500);
|
||||
expect(req.body[1].op).to.equal('add');
|
||||
expect(req.body[1].path).to.match(/\//);
|
||||
expect(req.body[1].value.name).to.equal(secondVariantName);
|
||||
expect(req.body[1].value.name).to.equal(variant2);
|
||||
}
|
||||
}
|
||||
).as('variantCreation');
|
||||
|
||||
cy.get('[data-testid=ADD_VARIANT_BUTTON]').click();
|
||||
cy.wait(1000);
|
||||
cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variantName);
|
||||
cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variant1);
|
||||
cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
|
||||
cy.wait('@variantCreation');
|
||||
cy.get('[data-testid=ADD_VARIANT_BUTTON]').click();
|
||||
cy.wait(1000);
|
||||
cy.get('[data-testid=VARIANT_NAME_INPUT]').type(secondVariantName);
|
||||
cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variant2);
|
||||
cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
|
||||
cy.wait('@variantCreation');
|
||||
});
|
||||
@ -272,7 +271,7 @@ describe('feature', () => {
|
||||
it('can set weight to fixed value for one of the variants', () => {
|
||||
cy.visit(`/projects/default/features/${featureToggleName}/variants`);
|
||||
|
||||
cy.get('[data-testid=VARIANT_EDIT_BUTTON]').first().click();
|
||||
cy.get(`[data-testid=VARIANT_EDIT_BUTTON_${variant1}]`).click();
|
||||
cy.wait(1000);
|
||||
cy.get('[data-testid=VARIANT_NAME_INPUT]')
|
||||
.children()
|
||||
@ -299,9 +298,10 @@ describe('feature', () => {
|
||||
|
||||
cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
|
||||
cy.wait('@variantUpdate');
|
||||
cy.get('[data-testid=VARIANT_WEIGHT]')
|
||||
.first()
|
||||
.should('have.text', '15 %');
|
||||
cy.get(`[data-testid=VARIANT_WEIGHT_${variant1}]`).should(
|
||||
'have.text',
|
||||
'15 %'
|
||||
);
|
||||
});
|
||||
|
||||
it('can delete variant', () => {
|
||||
|
@ -5,18 +5,22 @@ import { useStyles } from './TextCell.styles';
|
||||
interface ITextCellProps {
|
||||
value?: string | null;
|
||||
lineClamp?: number;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
export const TextCell: FC<ITextCellProps> = ({
|
||||
value,
|
||||
children,
|
||||
lineClamp,
|
||||
'data-testid': testid,
|
||||
}) => {
|
||||
const { classes } = useStyles({ lineClamp });
|
||||
|
||||
return (
|
||||
<Box className={classes.wrapper}>
|
||||
<span data-loading>{children ?? value}</span>
|
||||
<span data-loading="true" data-testid={testid}>
|
||||
{children ?? value}
|
||||
</span>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,17 +1,10 @@
|
||||
import { useStyles } from './FeatureVariants.styles';
|
||||
import FeatureOverviewVariants from './FeatureVariantsList/FeatureVariantsList';
|
||||
import { FeatureVariantsList } from './FeatureVariantsList/FeatureVariantsList';
|
||||
import { usePageTitle } from 'hooks/usePageTitle';
|
||||
|
||||
const FeatureVariants = () => {
|
||||
usePageTitle('Variants');
|
||||
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<FeatureOverviewVariants />
|
||||
</div>
|
||||
);
|
||||
return <FeatureVariantsList />;
|
||||
};
|
||||
|
||||
export default FeatureVariants;
|
||||
|
@ -1,21 +1,19 @@
|
||||
import classnames from 'classnames';
|
||||
import * as jsonpatch from 'fast-json-patch';
|
||||
|
||||
import styles from './variants.module.scss';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import { AddVariant } from './AddFeatureVariant/AddFeatureVariant';
|
||||
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { useContext, useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import FeatureVariantListItem from './FeatureVariantsListItem/FeatureVariantsListItem';
|
||||
import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||
@ -25,26 +23,43 @@ import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { updateWeight } from 'component/common/util';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup';
|
||||
import useDeleteVariantMarkup from './useDeleteVariantMarkup';
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { Edit, Delete } from '@mui/icons-material';
|
||||
import { useTable, useSortBy, useGlobalFilter } from 'react-table';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { SortableTableHeader, TablePlaceholder } from 'component/common/Table';
|
||||
import { sortTypes } from 'utils/sortTypes';
|
||||
import { PayloadOverridesCell } from './PayloadOverridesCell/PayloadOverridesCell';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import theme from 'themes/theme';
|
||||
import { VariantsActionCell } from './VariantsActionsCell/VariantsActionsCell';
|
||||
|
||||
const FeatureOverviewVariants = () => {
|
||||
export const FeatureVariantsList = () => {
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { feature, refetchFeature } = useFeature(projectId, featureId);
|
||||
const { feature, refetchFeature, loading } = useFeature(
|
||||
projectId,
|
||||
featureId
|
||||
);
|
||||
const [variants, setVariants] = useState<IFeatureVariant[]>([]);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const { context } = useUnleashContext();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { patchFeatureVariants } = useFeatureApi();
|
||||
const [editVariant, setEditVariant] = useState({});
|
||||
const [variantToEdit, setVariantToEdit] = useState({});
|
||||
const [showAddVariant, setShowAddVariant] = useState(false);
|
||||
const [stickinessOptions, setStickinessOptions] = useState<string[]>([]);
|
||||
const [delDialog, setDelDialog] = useState({ name: '', show: false });
|
||||
|
||||
const isMediumScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const isLargeScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
||||
|
||||
useEffect(() => {
|
||||
if (feature) {
|
||||
setClonedVariants(feature.variants);
|
||||
@ -63,6 +78,146 @@ const FeatureOverviewVariants = () => {
|
||||
|
||||
const editable = hasAccess(UPDATE_FEATURE_VARIANTS, projectId);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (loading) {
|
||||
return Array(5).fill({
|
||||
name: 'Context name',
|
||||
description: 'Context description when loading',
|
||||
});
|
||||
}
|
||||
|
||||
return feature.variants;
|
||||
}, [feature.variants, loading]);
|
||||
|
||||
const editVariant = useCallback(
|
||||
(name: string) => {
|
||||
const variant = {
|
||||
...variants.find(variant => variant.name === name),
|
||||
};
|
||||
setVariantToEdit(variant);
|
||||
setEditing(true);
|
||||
setShowAddVariant(true);
|
||||
},
|
||||
[variants, setVariantToEdit, setEditing, setShowAddVariant]
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: 'name',
|
||||
width: '25%',
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { name },
|
||||
},
|
||||
}: any) => {
|
||||
return <TextCell data-loading>{name}</TextCell>;
|
||||
},
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
{
|
||||
Header: 'Payload/Overrides',
|
||||
accessor: 'data',
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { overrides, payload },
|
||||
},
|
||||
}: any) => {
|
||||
return (
|
||||
<PayloadOverridesCell
|
||||
overrides={overrides}
|
||||
payload={payload}
|
||||
/>
|
||||
);
|
||||
},
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: 'Weight',
|
||||
accessor: 'weight',
|
||||
width: '20%',
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { name, weight },
|
||||
},
|
||||
}: any) => {
|
||||
return (
|
||||
<TextCell data-testid={`VARIANT_WEIGHT_${name}`}>
|
||||
{weight / 10.0} %
|
||||
</TextCell>
|
||||
);
|
||||
},
|
||||
sortType: 'number',
|
||||
},
|
||||
{
|
||||
Header: 'Type',
|
||||
accessor: 'weightType',
|
||||
width: '20%',
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { weightType },
|
||||
},
|
||||
}: any) => {
|
||||
return <TextCell>{weightType}</TextCell>;
|
||||
},
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
{
|
||||
Header: 'Actions',
|
||||
id: 'Actions',
|
||||
align: 'right',
|
||||
Cell: ({ row: { original } }: any) => (
|
||||
<VariantsActionCell
|
||||
editVariant={editVariant}
|
||||
setDelDialog={setDelDialog}
|
||||
variant={original as IFeatureVariant}
|
||||
projectId={projectId}
|
||||
/>
|
||||
),
|
||||
width: 150,
|
||||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[projectId, editVariant]
|
||||
);
|
||||
|
||||
const initialState = useMemo(
|
||||
() => ({
|
||||
sortBy: [{ id: 'name', desc: false }],
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
setHiddenColumns,
|
||||
} = useTable(
|
||||
{
|
||||
columns: columns as any[],
|
||||
data: data as any[],
|
||||
initialState,
|
||||
sortTypes,
|
||||
autoResetGlobalFilter: false,
|
||||
autoResetSortBy: false,
|
||||
disableSortRemove: true,
|
||||
},
|
||||
useGlobalFilter,
|
||||
useSortBy
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMediumScreen) {
|
||||
setHiddenColumns(['weightType', 'data']);
|
||||
} else if (isLargeScreen) {
|
||||
setHiddenColumns(['weightType']);
|
||||
}
|
||||
}, [setHiddenColumns, isMediumScreen, isLargeScreen]);
|
||||
|
||||
// @ts-expect-error
|
||||
const setClonedVariants = clonedVariants =>
|
||||
setVariants(cloneDeep(clonedVariants));
|
||||
@ -70,26 +225,7 @@ const FeatureOverviewVariants = () => {
|
||||
const handleCloseAddVariant = () => {
|
||||
setShowAddVariant(false);
|
||||
setEditing(false);
|
||||
setEditVariant({});
|
||||
};
|
||||
|
||||
const renderVariants = () => {
|
||||
return variants.map(variant => {
|
||||
return (
|
||||
<FeatureVariantListItem
|
||||
key={variant.name}
|
||||
variant={variant}
|
||||
editVariant={(name: string) => {
|
||||
const v = { ...variants.find(v => v.name === name) };
|
||||
setEditVariant(v);
|
||||
setEditing(true);
|
||||
setShowAddVariant(true);
|
||||
}}
|
||||
setDelDialog={setDelDialog}
|
||||
editable={editable}
|
||||
/>
|
||||
);
|
||||
});
|
||||
setVariantToEdit({});
|
||||
};
|
||||
|
||||
const renderStickiness = () => {
|
||||
@ -118,10 +254,7 @@ const FeatureOverviewVariants = () => {
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
<small
|
||||
className={classnames(styles.paragraph, styles.helperText)}
|
||||
style={{ display: 'block', marginTop: '0.5rem' }}
|
||||
>
|
||||
<small style={{ display: 'block', marginTop: '0.5rem' }}>
|
||||
By overriding the stickiness you can control which parameter
|
||||
is used to ensure consistent traffic allocation across
|
||||
variants.{' '}
|
||||
@ -245,55 +378,82 @@ const FeatureOverviewVariants = () => {
|
||||
return jsonpatch.compare(feature.variants, newVariants);
|
||||
};
|
||||
|
||||
return (
|
||||
<section style={{ padding: '16px' }}>
|
||||
<Typography variant="body1">
|
||||
Variants allows you to return a variant object if the feature
|
||||
toggle is considered enabled for the current request. When using
|
||||
variants you should use the{' '}
|
||||
<code style={{ color: 'navy' }}>getVariant()</code> method in
|
||||
the Client SDK.
|
||||
</Typography>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={variants?.length > 0}
|
||||
show={
|
||||
<Table className={styles.variantTable}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Variant name</TableCell>
|
||||
<TableCell className={styles.labels} />
|
||||
<TableCell>Weight</TableCell>
|
||||
<TableCell>Weight Type</TableCell>
|
||||
<TableCell className={styles.actions} />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>{renderVariants()}</TableBody>
|
||||
</Table>
|
||||
}
|
||||
elseShow={<p>No variants defined.</p>}
|
||||
/>
|
||||
|
||||
<br />
|
||||
|
||||
<div>
|
||||
<PermissionButton
|
||||
onClick={() => {
|
||||
const addVariant = () => {
|
||||
setEditing(false);
|
||||
if (variants.length === 0) {
|
||||
setEditVariant({ weight: 1000 });
|
||||
setVariantToEdit({ weight: 1000 });
|
||||
} else {
|
||||
setEditVariant({ weightType: 'variable' });
|
||||
setVariantToEdit({
|
||||
weightType: 'variable',
|
||||
});
|
||||
}
|
||||
setShowAddVariant(true);
|
||||
}}
|
||||
className={styles.addVariantButton}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContent
|
||||
isLoading={loading}
|
||||
header={
|
||||
<PageHeader
|
||||
title="Variants"
|
||||
actions={
|
||||
<>
|
||||
<PermissionButton
|
||||
onClick={addVariant}
|
||||
data-testid={'ADD_VARIANT_BUTTON'}
|
||||
permission={UPDATE_FEATURE_VARIANTS}
|
||||
projectId={projectId}
|
||||
>
|
||||
New variant
|
||||
</PermissionButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Alert severity="info" sx={{ marginBottom: '1rem' }}>
|
||||
Variants allows you to return a variant object if the feature
|
||||
toggle is considered enabled for the current request. When using
|
||||
variants you should use the{' '}
|
||||
<code style={{ fontWeight: 'bold' }}>getVariant()</code> method
|
||||
in the Client SDK.
|
||||
</Alert>
|
||||
<Table {...getTableProps()}>
|
||||
<SortableTableHeader headerGroups={headerGroups} />
|
||||
<TableBody {...getTableBodyProps()}>
|
||||
{rows.map(row => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<TableRow
|
||||
hover
|
||||
{...row.getRowProps()}
|
||||
style={{ height: '75px' }}
|
||||
>
|
||||
{row.cells.map(cell => (
|
||||
<TableCell
|
||||
{...cell.getCellProps()}
|
||||
padding="none"
|
||||
>
|
||||
{cell.render('Cell')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<ConditionallyRender
|
||||
condition={rows.length === 0}
|
||||
show={
|
||||
<TablePlaceholder>
|
||||
No variants available. Get started by adding one.
|
||||
</TablePlaceholder>
|
||||
}
|
||||
/>
|
||||
|
||||
<br />
|
||||
|
||||
<div>
|
||||
<ConditionallyRender
|
||||
condition={editable}
|
||||
show={renderStickiness()}
|
||||
@ -314,13 +474,11 @@ const FeatureOverviewVariants = () => {
|
||||
validateName={validateName}
|
||||
validateWeight={validateWeight}
|
||||
// @ts-expect-error
|
||||
editVariant={editVariant}
|
||||
editVariant={variantToEdit}
|
||||
title={editing ? 'Edit variant' : 'Add variant'}
|
||||
/>
|
||||
|
||||
{delDialogueMarkup}
|
||||
</section>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureOverviewVariants;
|
||||
|
@ -1,90 +0,0 @@
|
||||
import { Chip, IconButton, TableCell, TableRow, Tooltip } from '@mui/material';
|
||||
import { Delete, Edit } from '@mui/icons-material';
|
||||
|
||||
import styles from '../variants.module.scss';
|
||||
import { IFeatureVariant } from 'interfaces/featureToggle';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { weightTypes } from '../AddFeatureVariant/enums';
|
||||
|
||||
interface IFeatureVariantListItem {
|
||||
variant: IFeatureVariant;
|
||||
editVariant: any;
|
||||
setDelDialog: any;
|
||||
editable: boolean;
|
||||
}
|
||||
|
||||
const FeatureVariantListItem = ({
|
||||
variant,
|
||||
editVariant,
|
||||
setDelDialog,
|
||||
editable,
|
||||
}: IFeatureVariantListItem) => {
|
||||
const { FIX } = weightTypes;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell data-testid={'VARIANT_NAME'}>{variant.name}</TableCell>
|
||||
<TableCell className={styles.chipContainer}>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(variant.payload)}
|
||||
show={<Chip label="Payload" />}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
variant.overrides && variant.overrides.length > 0
|
||||
}
|
||||
show={
|
||||
<Chip
|
||||
style={{
|
||||
backgroundColor: 'rgba(173, 216, 230, 0.2)',
|
||||
}}
|
||||
label="Overrides"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell data-testid={'VARIANT_WEIGHT'}>
|
||||
{variant.weight / 10.0} %
|
||||
</TableCell>
|
||||
<TableCell data-testid={'VARIANT_WEIGHT_TYPE'}>
|
||||
{variant.weightType === FIX ? 'Fix' : 'Variable'}
|
||||
</TableCell>
|
||||
<ConditionallyRender
|
||||
condition={editable}
|
||||
show={
|
||||
<TableCell className={styles.actions}>
|
||||
<div className={styles.actionsContainer}>
|
||||
<Tooltip title="Edit variant" arrow>
|
||||
<IconButton
|
||||
data-testid={'VARIANT_EDIT_BUTTON'}
|
||||
onClick={() => editVariant(variant.name)}
|
||||
size="large"
|
||||
>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete variant" arrow>
|
||||
<IconButton
|
||||
data-testid={`VARIANT_DELETE_BUTTON_${variant.name}`}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setDelDialog({
|
||||
show: true,
|
||||
name: variant.name,
|
||||
});
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableCell>
|
||||
}
|
||||
elseShow={<TableCell className={styles.actions} />}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureVariantListItem;
|
@ -0,0 +1,27 @@
|
||||
import { Chip } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { IOverride, IPayload } from 'interfaces/featureToggle';
|
||||
|
||||
interface IPayloadOverridesCellProps {
|
||||
payload: IPayload;
|
||||
overrides: IOverride[];
|
||||
}
|
||||
|
||||
export const PayloadOverridesCell = ({
|
||||
payload,
|
||||
overrides,
|
||||
}: IPayloadOverridesCellProps) => {
|
||||
return (
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(payload)}
|
||||
show={<TextCell>Payload</TextCell>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={overrides && overrides.length > 0}
|
||||
show={<TextCell>Overrides</TextCell>}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
import { Edit, Delete } from '@mui/icons-material';
|
||||
import { Box } from '@mui/material';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions';
|
||||
import { IFeatureVariant } from 'interfaces/featureToggle';
|
||||
|
||||
interface IVarintsActionCellProps {
|
||||
projectId: string;
|
||||
editVariant: (name: string) => void;
|
||||
setDelDialog: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
name: string;
|
||||
show: boolean;
|
||||
}>
|
||||
>;
|
||||
variant: IFeatureVariant;
|
||||
}
|
||||
|
||||
export const VariantsActionCell = ({
|
||||
projectId,
|
||||
setDelDialog,
|
||||
variant,
|
||||
editVariant,
|
||||
}: IVarintsActionCellProps) => {
|
||||
return (
|
||||
<Box
|
||||
style={{ display: 'flex', justifyContent: 'flex-end' }}
|
||||
data-loading
|
||||
>
|
||||
<PermissionIconButton
|
||||
size="large"
|
||||
data-testid={`VARIANT_EDIT_BUTTON_${variant.name}`}
|
||||
permission={UPDATE_FEATURE_VARIANTS}
|
||||
projectId={projectId}
|
||||
onClick={() => editVariant(variant.name)}
|
||||
>
|
||||
<Edit />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
size="large"
|
||||
permission={UPDATE_FEATURE_VARIANTS}
|
||||
data-testid={`VARIANT_DELETE_BUTTON_${variant.name}`}
|
||||
projectId={projectId}
|
||||
onClick={() =>
|
||||
setDelDialog({
|
||||
show: true,
|
||||
name: variant.name,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Delete />
|
||||
</PermissionIconButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,100 +0,0 @@
|
||||
.variantTable {
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
|
||||
th,
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
th:first-of-type,
|
||||
td:first-of-type {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
tbody tr:hover {
|
||||
background-color: rgba(173, 216, 230, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
th.labels {
|
||||
display: none;
|
||||
}
|
||||
td.labels {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
th.labels {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td.labels {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
th.actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td.actions {
|
||||
height: 100%;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.actionsContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal {
|
||||
max-width: 90%;
|
||||
width: 600px;
|
||||
position: absolute !important;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.modal {
|
||||
top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
i {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.inputWeight {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.flexCenter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.marginL10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.addVariantButton {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.helperText {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
}
|
Loading…
Reference in New Issue
Block a user