mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Playground results light
This commit is contained in:
		
							parent
							
								
									2e94cd660c
								
							
						
					
					
						commit
						672d948d24
					
				| @ -1,4 +1,4 @@ | ||||
| import { ComponentProps, useMemo, useState, VFC } from 'react'; | ||||
| import { ComponentProps, useState, VFC } from 'react'; | ||||
| import { | ||||
|     Autocomplete, | ||||
|     Box, | ||||
|  | ||||
| @ -0,0 +1,26 @@ | ||||
| import { colors } from 'themes/colors'; | ||||
| import { Alert, styled } from '@mui/material'; | ||||
| import { SdkContextSchema } from '../../playground.model'; | ||||
| 
 | ||||
| interface IContextBannerProps { | ||||
|     context: SdkContextSchema; | ||||
| } | ||||
| 
 | ||||
| const StyledContextFieldList = styled('ul')(() => ({ | ||||
|     color: colors.black, | ||||
|     listStyleType: 'none', | ||||
|     paddingInlineStart: 16, | ||||
| })); | ||||
| 
 | ||||
| export const ContextBanner = ({ context }: IContextBannerProps) => { | ||||
|     return ( | ||||
|         <Alert severity="info" sx={{ my: 2 }}> | ||||
|             Your results are generated based on this configuration | ||||
|             <StyledContextFieldList> | ||||
|                 {Object.entries(context).map(([key, value]) => ( | ||||
|                     <li key={key}>{`${key}: ${value}`}</li> | ||||
|                 ))} | ||||
|             </StyledContextFieldList> | ||||
|         </Alert> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,70 @@ | ||||
| import React from 'react'; | ||||
| import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; | ||||
| import { colors } from 'themes/colors'; | ||||
| import { ReactComponent as FeatureEnabledIcon } from 'assets/icons/isenabled-true.svg'; | ||||
| import { ReactComponent as FeatureDisabledIcon } from 'assets/icons/isenabled-false.svg'; | ||||
| import { Chip, styled, useTheme } from '@mui/material'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| interface IFeatureStatusCellProps { | ||||
|     enabled: boolean; | ||||
| } | ||||
| 
 | ||||
| const StyledFalseChip = styled(Chip)(() => ({ | ||||
|     width: 80, | ||||
|     borderRadius: '5px', | ||||
|     border: `1px solid ${colors.red['700']}`, | ||||
|     backgroundColor: colors.red['200'], | ||||
|     ['& .MuiChip-label']: { | ||||
|         color: colors.red['700'], | ||||
|     }, | ||||
|     ['& .MuiChip-icon']: { | ||||
|         color: colors.red['700'], | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const StyledTrueChip = styled(Chip)(() => ({ | ||||
|     width: 80, | ||||
|     borderRadius: '5px', | ||||
|     border: `1px solid ${colors.green['700']}`, | ||||
|     backgroundColor: colors.green['100'], | ||||
|     ['& .MuiChip-label']: { | ||||
|         color: colors.green['700'], | ||||
|     }, | ||||
|     ['& .MuiChip-icon']: { | ||||
|         color: colors.green['700'], | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| export const FeatureStatusCell = ({ enabled }: IFeatureStatusCellProps) => { | ||||
|     const theme = useTheme(); | ||||
|     const icon = ( | ||||
|         <ConditionallyRender | ||||
|             condition={enabled} | ||||
|             show={ | ||||
|                 <FeatureEnabledIcon | ||||
|                     stroke={theme.palette.success.main} | ||||
|                     strokeWidth="0.25" | ||||
|                 /> | ||||
|             } | ||||
|             elseShow={ | ||||
|                 <FeatureDisabledIcon | ||||
|                     stroke={theme.palette.error.main} | ||||
|                     strokeWidth="0.25" | ||||
|                 /> | ||||
|             } | ||||
|         /> | ||||
|     ); | ||||
| 
 | ||||
|     const label = enabled ? 'True' : 'False'; | ||||
| 
 | ||||
|     return ( | ||||
|         <TextCell> | ||||
|             <ConditionallyRender | ||||
|                 condition={enabled} | ||||
|                 show={<StyledTrueChip icon={icon} label={label} />} | ||||
|                 elseShow={<StyledFalseChip icon={icon} label={label} />} | ||||
|             /> | ||||
|         </TextCell> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,238 @@ | ||||
| import { useEffect, useMemo, useState } from 'react'; | ||||
| import { useSearchParams } from 'react-router-dom'; | ||||
| import { SortingRule, useGlobalFilter, useSortBy, useTable } from 'react-table'; | ||||
| import { PageContent } from 'component/common/PageContent/PageContent'; | ||||
| import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||||
| import { | ||||
|     SortableTableHeader, | ||||
|     Table, | ||||
|     TableBody, | ||||
|     TableCell, | ||||
|     TablePlaceholder, | ||||
|     TableRow, | ||||
| } from 'component/common/Table'; | ||||
| import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; | ||||
| import { sortTypes } from 'utils/sortTypes'; | ||||
| import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Search } from 'component/common/Search/Search'; | ||||
| import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; | ||||
| import { useSearch } from 'hooks/useSearch'; | ||||
| import { createLocalStorage } from 'utils/createLocalStorage'; | ||||
| import { FeatureStatusCell } from './FeatureStatusCell/FeatureStatusCell'; | ||||
| import { PlaygroundFeatureSchema } from '../playground.model'; | ||||
| 
 | ||||
| const defaultSort: SortingRule<string> = { id: 'name' }; | ||||
| const { value, setValue } = createLocalStorage( | ||||
|     'PlaygroundResultsTable:v1', | ||||
|     defaultSort | ||||
| ); | ||||
| 
 | ||||
| interface IPlaygroundResultsTableProps { | ||||
|     features?: PlaygroundFeatureSchema[]; | ||||
|     loading: boolean; | ||||
| } | ||||
| 
 | ||||
| export const PlaygroundResultsTable = ({ | ||||
|     features, | ||||
|     loading, | ||||
| }: IPlaygroundResultsTableProps) => { | ||||
|     const [searchParams, setSearchParams] = useSearchParams(); | ||||
| 
 | ||||
|     const [searchValue, setSearchValue] = useState( | ||||
|         searchParams.get('search') || '' | ||||
|     ); | ||||
| 
 | ||||
|     const { | ||||
|         data: searchedData, | ||||
|         getSearchText, | ||||
|         getSearchContext, | ||||
|     } = useSearch(COLUMNS, searchValue, features || []); | ||||
| 
 | ||||
|     const data = useMemo(() => { | ||||
|         return loading | ||||
|             ? Array(5).fill({ | ||||
|                   name: 'Feature name', | ||||
|                   project: 'Feature Project', | ||||
|                   variant: 'Feature variant', | ||||
|                   enabled: 'Feature state', | ||||
|               }) | ||||
|             : searchedData; | ||||
|     }, [searchedData, loading]); | ||||
| 
 | ||||
|     const [initialState] = useState(() => ({ | ||||
|         sortBy: [ | ||||
|             { | ||||
|                 id: searchParams.get('sort') || value.id, | ||||
|                 desc: searchParams.has('order') | ||||
|                     ? searchParams.get('order') === 'desc' | ||||
|                     : value.desc, | ||||
|             }, | ||||
|         ], | ||||
|     })); | ||||
| 
 | ||||
|     const { | ||||
|         getTableProps, | ||||
|         getTableBodyProps, | ||||
|         headerGroups, | ||||
|         state: { sortBy }, | ||||
|         rows, | ||||
|         prepareRow, | ||||
|     } = useTable( | ||||
|         { | ||||
|             initialState, | ||||
|             columns: COLUMNS as any, | ||||
|             data: data as any, | ||||
|             sortTypes, | ||||
|             autoResetGlobalFilter: false, | ||||
|             autoResetSortBy: false, | ||||
|             disableSortRemove: true, | ||||
|             defaultColumn: { | ||||
|                 Cell: HighlightCell, | ||||
|             }, | ||||
|         }, | ||||
|         useGlobalFilter, | ||||
|         useSortBy | ||||
|     ); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (loading) { | ||||
|             return; | ||||
|         } | ||||
|         const tableState: Record<string, string> = | ||||
|             Object.fromEntries(searchParams); | ||||
|         tableState.sort = sortBy[0].id; | ||||
|         if (sortBy[0].desc) { | ||||
|             tableState.order = 'desc'; | ||||
|         } else if (tableState.order) { | ||||
|             delete tableState.order; | ||||
|         } | ||||
|         if (searchValue) { | ||||
|             tableState.search = searchValue; | ||||
|         } | ||||
| 
 | ||||
|         setSearchParams(tableState, { | ||||
|             replace: true, | ||||
|         }); | ||||
|         setValue({ id: sortBy[0].id, desc: sortBy[0].desc || false }); | ||||
| 
 | ||||
|         // eslint-disable-next-line react-hooks/exhaustive-deps -- don't re-render after search params change
 | ||||
|     }, [loading, sortBy, searchValue]); | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent | ||||
|             header={ | ||||
|                 <PageHeader | ||||
|                     titleElement={ | ||||
|                         features !== undefined | ||||
|                             ? `Results (${ | ||||
|                                   rows.length < data.length | ||||
|                                       ? `${rows.length} of ${data.length}` | ||||
|                                       : data.length | ||||
|                               })` | ||||
|                             : 'Results' | ||||
|                     } | ||||
|                     actions={ | ||||
|                         <Search | ||||
|                             initialValue={searchValue} | ||||
|                             onChange={setSearchValue} | ||||
|                             hasFilters | ||||
|                             getSearchContext={getSearchContext} | ||||
|                         /> | ||||
|                     } | ||||
|                 /> | ||||
|             } | ||||
|             isLoading={loading} | ||||
|         > | ||||
|             <ConditionallyRender | ||||
|                 condition={!loading && data.length === 0} | ||||
|                 show={() => ( | ||||
|                     <TablePlaceholder> | ||||
|                         None of the feature toggles were evaluated yet. | ||||
|                     </TablePlaceholder> | ||||
|                 )} | ||||
|                 elseShow={() => ( | ||||
|                     <> | ||||
|                         <SearchHighlightProvider | ||||
|                             value={getSearchText(searchValue)} | ||||
|                         > | ||||
|                             <Table {...getTableProps()} rowHeight="standard"> | ||||
|                                 <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> | ||||
|                         <ConditionallyRender | ||||
|                             condition={searchValue?.length > 0} | ||||
|                             show={ | ||||
|                                 <TablePlaceholder> | ||||
|                                     No feature toggles found matching “ | ||||
|                                     {searchValue}” | ||||
|                                 </TablePlaceholder> | ||||
|                             } | ||||
|                         /> | ||||
|                     </> | ||||
|                 )} | ||||
|             /> | ||||
|         </PageContent> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const COLUMNS = [ | ||||
|     { | ||||
|         Header: 'Name', | ||||
|         accessor: 'name', | ||||
|         searchable: true, | ||||
|         width: '60%', | ||||
|         Cell: ({ value }: any) => ( | ||||
|             <LinkCell title={value} to={`/feature/${value}`} /> | ||||
|         ), | ||||
|     }, | ||||
|     { | ||||
|         Header: 'Project ID', | ||||
|         accessor: 'projectId', | ||||
|         sortType: 'alphanumeric', | ||||
|         filterName: 'projectId', | ||||
|         searchable: true, | ||||
|         maxWidth: 170, | ||||
|         Cell: ({ value }: any) => ( | ||||
|             <LinkCell title={value} to={`/projects/${value}`} /> | ||||
|         ), | ||||
|     }, | ||||
|     { | ||||
|         Header: 'Variant', | ||||
|         id: 'variant', | ||||
|         accessor: 'variant.name', | ||||
|         sortType: 'alphanumeric', | ||||
|         filterName: 'variant', | ||||
|         searchable: true, | ||||
|         maxWidth: 170, | ||||
|         Cell: ({ value }: any) => <HighlightCell value={value} />, | ||||
|     }, | ||||
|     { | ||||
|         Header: 'isEnabled', | ||||
|         accessor: 'isEnabled', | ||||
|         maxWidth: 170, | ||||
|         Cell: ({ value }: any) => <FeatureStatusCell enabled={value} />, | ||||
|         sortType: 'boolean', | ||||
|     }, | ||||
| ]; | ||||
							
								
								
									
										248
									
								
								frontend/src/component/playground/Playground/playground.model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								frontend/src/component/playground/Playground/playground.model.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,248 @@ | ||||
| export enum PlaygroundFeatureSchemaVariantPayloadTypeEnum { | ||||
|     Json = 'json', | ||||
|     Csv = 'csv', | ||||
|     String = 'string', | ||||
| } | ||||
| 
 | ||||
| export interface PlaygroundFeatureSchemaVariantPayload { | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchemaVariantPayload | ||||
|      */ | ||||
|     type: PlaygroundFeatureSchemaVariantPayloadTypeEnum; | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchemaVariantPayload | ||||
|      */ | ||||
|     value: string; | ||||
| } | ||||
| 
 | ||||
| export interface PlaygroundFeatureSchemaVariant { | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchemaVariant | ||||
|      */ | ||||
|     name: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {boolean} | ||||
|      * @memberof PlaygroundFeatureSchemaVariant | ||||
|      */ | ||||
|     enabled: boolean; | ||||
|     /** | ||||
|      * | ||||
|      * @type {PlaygroundFeatureSchemaVariantPayload} | ||||
|      * @memberof PlaygroundFeatureSchemaVariant | ||||
|      */ | ||||
|     payload?: PlaygroundFeatureSchemaVariantPayload; | ||||
| } | ||||
| 
 | ||||
| export interface PlaygroundFeatureSchema { | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchema | ||||
|      */ | ||||
|     name: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchema | ||||
|      */ | ||||
|     projectId: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {boolean} | ||||
|      * @memberof PlaygroundFeatureSchema | ||||
|      */ | ||||
|     isEnabled: boolean; | ||||
|     /** | ||||
|      * | ||||
|      * @type {PlaygroundFeatureSchemaVariant} | ||||
|      * @memberof PlaygroundFeatureSchema | ||||
|      */ | ||||
|     variant: PlaygroundFeatureSchemaVariant | null; | ||||
| } | ||||
| export interface PlaygroundResponseSchema { | ||||
|     /** | ||||
|      * | ||||
|      * @type {PlaygroundRequestSchema} | ||||
|      * @memberof PlaygroundResponseSchema | ||||
|      */ | ||||
|     input: PlaygroundRequestSchema; | ||||
|     /** | ||||
|      * | ||||
|      * @type {Array<PlaygroundFeatureSchema>} | ||||
|      * @memberof PlaygroundResponseSchema | ||||
|      */ | ||||
|     features: Array<PlaygroundFeatureSchema>; | ||||
| } | ||||
| 
 | ||||
| export interface PlaygroundRequestSchema { | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundRequestSchema | ||||
|      */ | ||||
|     environment: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {PlaygroundRequestSchemaProjects} | ||||
|      * @memberof PlaygroundRequestSchema | ||||
|      */ | ||||
|     projects?: Array<string> | string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {SdkContextSchema} | ||||
|      * @memberof PlaygroundRequestSchema | ||||
|      */ | ||||
|     context: SdkContextSchema; | ||||
| } | ||||
| 
 | ||||
| export interface PlaygroundFeatureSchemaVariantPayload { | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchemaVariantPayload | ||||
|      */ | ||||
|     type: PlaygroundFeatureSchemaVariantPayloadTypeEnum; | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchemaVariantPayload | ||||
|      */ | ||||
|     value: string; | ||||
| } | ||||
| 
 | ||||
| export interface PlaygroundFeatureSchemaVariant { | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchemaVariant | ||||
|      */ | ||||
|     name: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {boolean} | ||||
|      * @memberof PlaygroundFeatureSchemaVariant | ||||
|      */ | ||||
|     enabled: boolean; | ||||
|     /** | ||||
|      * | ||||
|      * @type {PlaygroundFeatureSchemaVariantPayload} | ||||
|      * @memberof PlaygroundFeatureSchemaVariant | ||||
|      */ | ||||
|     payload?: PlaygroundFeatureSchemaVariantPayload; | ||||
| } | ||||
| 
 | ||||
| export interface PlaygroundFeatureSchema { | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchema | ||||
|      */ | ||||
|     name: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundFeatureSchema | ||||
|      */ | ||||
|     projectId: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {boolean} | ||||
|      * @memberof PlaygroundFeatureSchema | ||||
|      */ | ||||
|     isEnabled: boolean; | ||||
|     /** | ||||
|      * | ||||
|      * @type {PlaygroundFeatureSchemaVariant} | ||||
|      * @memberof PlaygroundFeatureSchema | ||||
|      */ | ||||
|     variant: PlaygroundFeatureSchemaVariant | null; | ||||
| } | ||||
| export interface PlaygroundResponseSchema { | ||||
|     /** | ||||
|      * | ||||
|      * @type {PlaygroundRequestSchema} | ||||
|      * @memberof PlaygroundResponseSchema | ||||
|      */ | ||||
|     input: PlaygroundRequestSchema; | ||||
|     /** | ||||
|      * | ||||
|      * @type {Array<PlaygroundFeatureSchema>} | ||||
|      * @memberof PlaygroundResponseSchema | ||||
|      */ | ||||
|     features: Array<PlaygroundFeatureSchema>; | ||||
| } | ||||
| 
 | ||||
| export interface PlaygroundRequestSchema { | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof PlaygroundRequestSchema | ||||
|      */ | ||||
|     environment: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type Array<string> | string | ||||
|      * @memberof PlaygroundRequestSchema | ||||
|      */ | ||||
|     projects?: Array<string> | string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {SdkContextSchema} | ||||
|      * @memberof PlaygroundRequestSchema | ||||
|      */ | ||||
|     context: SdkContextSchema; | ||||
| } | ||||
| 
 | ||||
| export interface SdkContextSchema { | ||||
|     [key: string]: string | any; | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof SdkContextSchema | ||||
|      */ | ||||
|     appName: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {Date} | ||||
|      * @memberof SdkContextSchema | ||||
|      */ | ||||
|     currentTime?: Date; | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof SdkContextSchema | ||||
|      * @deprecated | ||||
|      */ | ||||
|     environment?: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {{ [key: string]: string; }} | ||||
|      * @memberof SdkContextSchema | ||||
|      */ | ||||
|     properties?: { [key: string]: string }; | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof SdkContextSchema | ||||
|      */ | ||||
|     remoteAddress?: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof SdkContextSchema | ||||
|      */ | ||||
|     sessionId?: string; | ||||
|     /** | ||||
|      * | ||||
|      * @type {string} | ||||
|      * @memberof SdkContextSchema | ||||
|      */ | ||||
|     userId?: string; | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user