mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-19 01:17:18 +02:00
feat: Playground environment diff table (#4002)
This commit is contained in:
parent
eb8f16da8d
commit
16a3f6069c
@ -24,7 +24,7 @@ import { AdvancedPlaygroundResponseSchema } from 'openapi';
|
|||||||
export const AdvancedPlayground: VFC<{}> = () => {
|
export const AdvancedPlayground: VFC<{}> = () => {
|
||||||
const { environments: availableEnvironments } = useEnvironments();
|
const { environments: availableEnvironments } = useEnvironments();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const matches = useMediaQuery(theme.breakpoints.down('lg'));
|
const matches = true;
|
||||||
|
|
||||||
const [environments, setEnvironments] = useState<string[]>([]);
|
const [environments, setEnvironments] = useState<string[]>([]);
|
||||||
const [projects, setProjects] = useState<string[]>([]);
|
const [projects, setProjects] = useState<string[]>([]);
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
import { Link, Popover, styled, Typography, useTheme } from '@mui/material';
|
||||||
|
import { flexRow } from '../../../../../themes/themeStyles';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
AdvancedPlaygroundEnvironmentFeatureSchema,
|
||||||
|
AdvancedPlaygroundFeatureSchemaEnvironments,
|
||||||
|
} from 'openapi';
|
||||||
|
import { PlaygroundEnvironmentTable } from '../../PlaygroundEnvironmentTable/PlaygroundEnvironmentTable';
|
||||||
|
import { PlaygroundEnvironmentDiffTable } from '../../PlaygroundEnvironmentTable/PlaygroundEnvironmentDiffTable';
|
||||||
|
|
||||||
|
const StyledContainer = styled(
|
||||||
|
'div',
|
||||||
|
{}
|
||||||
|
)(({ theme }) => ({
|
||||||
|
flexGrow: 0,
|
||||||
|
...flexRow,
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
margin: theme.spacing(0, 1.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButton = styled(Link)(({ theme }) => ({
|
||||||
|
textAlign: 'left',
|
||||||
|
textDecorationStyle: 'dotted',
|
||||||
|
textUnderlineOffset: theme.spacing(0.75),
|
||||||
|
color: theme.palette.neutral.dark,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export interface IAdvancedPlaygroundEnvironmentCellProps {
|
||||||
|
value: AdvancedPlaygroundFeatureSchemaEnvironments;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdvancedPlaygroundEnvironmentDiffCell = ({
|
||||||
|
value,
|
||||||
|
}: IAdvancedPlaygroundEnvironmentCellProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [anchor, setAnchorEl] = useState<null | Element>(null);
|
||||||
|
|
||||||
|
const onOpen = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) =>
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
|
||||||
|
const onClose = () => setAnchorEl(null);
|
||||||
|
|
||||||
|
const open = Boolean(anchor);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<>
|
||||||
|
<StyledButton variant={'body2'} onClick={onOpen}>
|
||||||
|
Preview diff
|
||||||
|
</StyledButton>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
open={open}
|
||||||
|
id={`${value}-result-details`}
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClose={onClose}
|
||||||
|
anchorEl={anchor}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: -320,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 3 }}>
|
||||||
|
Environments diff
|
||||||
|
</Typography>
|
||||||
|
<PlaygroundEnvironmentDiffTable features={value} />
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -34,6 +34,7 @@ import {
|
|||||||
AdvancedPlaygroundFeatureSchema,
|
AdvancedPlaygroundFeatureSchema,
|
||||||
} from 'openapi';
|
} from 'openapi';
|
||||||
import { capitalizeFirst } from 'utils/capitalizeFirst';
|
import { capitalizeFirst } from 'utils/capitalizeFirst';
|
||||||
|
import { AdvancedPlaygroundEnvironmentDiffCell } from './AdvancedPlaygroundEnvironmentCell/AdvancedPlaygroundEnvironmentDiffCell';
|
||||||
|
|
||||||
const defaultSort: SortingRule<string> = { id: 'name' };
|
const defaultSort: SortingRule<string> = { id: 'name' };
|
||||||
const { value, setValue } = createLocalStorage(
|
const { value, setValue } = createLocalStorage(
|
||||||
@ -111,7 +112,9 @@ export const AdvancedPlaygroundResultsTable = ({
|
|||||||
id: 'diff',
|
id: 'diff',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
Cell: ({ row }: any) => (
|
Cell: ({ row }: any) => (
|
||||||
<StyledButton variant={'body2'}>Preview diff</StyledButton>
|
<AdvancedPlaygroundEnvironmentDiffCell
|
||||||
|
value={row.original.environments}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
import React, { useMemo, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
useFlexLayout,
|
||||||
|
useGlobalFilter,
|
||||||
|
useSortBy,
|
||||||
|
useTable,
|
||||||
|
} from 'react-table';
|
||||||
|
|
||||||
|
import { VirtualizedTable } from 'component/common/Table';
|
||||||
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
|
import { AdvancedPlaygroundFeatureSchemaEnvironments } from 'openapi';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { FeatureStatusCell } from '../PlaygroundResultsTable/FeatureStatusCell/FeatureStatusCell';
|
||||||
|
import { HighlightCell } from '../../../common/Table/cells/HighlightCell/HighlightCell';
|
||||||
|
import { capitalizeFirst } from 'utils/capitalizeFirst';
|
||||||
|
|
||||||
|
interface IPlaygroundEnvironmentTableProps {
|
||||||
|
features: AdvancedPlaygroundFeatureSchemaEnvironments;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaygroundEnvironmentDiffTable = ({
|
||||||
|
features,
|
||||||
|
}: IPlaygroundEnvironmentTableProps) => {
|
||||||
|
const environments = Object.keys(features);
|
||||||
|
const firstEnvFeatures = features[environments[0]];
|
||||||
|
const firstContext = firstEnvFeatures[0].context;
|
||||||
|
|
||||||
|
const data = useMemo(
|
||||||
|
() =>
|
||||||
|
firstEnvFeatures.map((item, index) => ({
|
||||||
|
...Object.fromEntries(
|
||||||
|
environments.map(env => [env, features[env][index]])
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
[JSON.stringify(features)]
|
||||||
|
);
|
||||||
|
type RowType = typeof data[0];
|
||||||
|
|
||||||
|
const contextFieldsHeaders = Object.keys(firstContext).map(
|
||||||
|
contextField => ({
|
||||||
|
Header: capitalizeFirst(contextField),
|
||||||
|
accessor: `${environments[0]}.context.${contextField}`,
|
||||||
|
minWidth: 160,
|
||||||
|
Cell: HighlightCell,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const environmentHeaders = environments.map(environment => ({
|
||||||
|
Header: environment,
|
||||||
|
accessor: (row: RowType) =>
|
||||||
|
row[environment]?.isEnabled
|
||||||
|
? 'true'
|
||||||
|
: row[environment]?.strategies?.result === 'unknown'
|
||||||
|
? 'unknown'
|
||||||
|
: 'false',
|
||||||
|
Cell: ({ row }: { row: { original: RowType } }) => {
|
||||||
|
return <FeatureStatusCell feature={row.original[environment]} />;
|
||||||
|
},
|
||||||
|
sortType: 'playgroundResultState',
|
||||||
|
maxWidth: 120,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const COLUMNS = useMemo(() => {
|
||||||
|
return [...contextFieldsHeaders, ...environmentHeaders];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { headerGroups, rows, prepareRow } = useTable(
|
||||||
|
{
|
||||||
|
columns: COLUMNS as any[],
|
||||||
|
data,
|
||||||
|
sortTypes,
|
||||||
|
autoResetGlobalFilter: false,
|
||||||
|
autoResetHiddenColumns: false,
|
||||||
|
autoResetSortBy: false,
|
||||||
|
disableSortRemove: true,
|
||||||
|
disableMultiSort: true,
|
||||||
|
},
|
||||||
|
useGlobalFilter,
|
||||||
|
useFlexLayout,
|
||||||
|
useSortBy
|
||||||
|
);
|
||||||
|
|
||||||
|
const parentRef = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
ref={parentRef}
|
||||||
|
sx={{
|
||||||
|
overflow: 'auto',
|
||||||
|
maxHeight: '800px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VirtualizedTable
|
||||||
|
parentRef={parentRef}
|
||||||
|
rows={rows}
|
||||||
|
headerGroups={headerGroups}
|
||||||
|
prepareRow={prepareRow}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -42,5 +42,5 @@ test('should render environment table', async () => {
|
|||||||
expect(screen.getByText('clientA')).toBeInTheDocument();
|
expect(screen.getByText('clientA')).toBeInTheDocument();
|
||||||
expect(screen.getByText('variantName')).toBeInTheDocument();
|
expect(screen.getByText('variantName')).toBeInTheDocument();
|
||||||
expect(screen.getByText('False')).toBeInTheDocument();
|
expect(screen.getByText('False')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('myapp')).not.toBeInTheDocument();
|
expect(screen.getByText('myapp')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -30,14 +30,14 @@ export const PlaygroundEnvironmentTable = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
const dynamicHeaders = Object.keys(features[0].context)
|
const dynamicHeaders = Object.keys(features[0].context).map(
|
||||||
.filter(contextField => contextField !== 'appName')
|
contextField => ({
|
||||||
.map(contextField => ({
|
|
||||||
Header: capitalizeFirst(contextField),
|
Header: capitalizeFirst(contextField),
|
||||||
accessor: `context.${contextField}`,
|
accessor: `context.${contextField}`,
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
Cell: HighlightCell,
|
Cell: HighlightCell,
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const COLUMNS = useMemo(() => {
|
const COLUMNS = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@ -108,6 +108,11 @@ export const PlaygroundEnvironmentTable = ({
|
|||||||
columns: COLUMNS as any,
|
columns: COLUMNS as any,
|
||||||
data: features,
|
data: features,
|
||||||
sortTypes,
|
sortTypes,
|
||||||
|
autoResetGlobalFilter: false,
|
||||||
|
autoResetHiddenColumns: false,
|
||||||
|
autoResetSortBy: false,
|
||||||
|
disableSortRemove: true,
|
||||||
|
disableMultiSort: true,
|
||||||
},
|
},
|
||||||
useGlobalFilter,
|
useGlobalFilter,
|
||||||
useFlexLayout,
|
useFlexLayout,
|
||||||
|
Loading…
Reference in New Issue
Block a user