mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-31 01:16:01 +02:00
feat: add usage of segment in list (#3853)
This commit is contained in:
parent
49722d5c48
commit
f73d36fda3
@ -11,7 +11,13 @@ import {
|
|||||||
import { useTable, useGlobalFilter, useSortBy } from 'react-table';
|
import { useTable, useGlobalFilter, useSortBy } from 'react-table';
|
||||||
import { CreateSegmentButton } from 'component/segments/CreateSegmentButton/CreateSegmentButton';
|
import { CreateSegmentButton } from 'component/segments/CreateSegmentButton/CreateSegmentButton';
|
||||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
import { useMediaQuery } from '@mui/material';
|
import {
|
||||||
|
Box,
|
||||||
|
Checkbox,
|
||||||
|
styled,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
} from '@mui/material';
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
@ -28,10 +34,13 @@ import { Search } from 'component/common/Search/Search';
|
|||||||
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
||||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
|
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
|
||||||
|
import { RowSelectCell } from '../project/Project/ProjectFeatureToggles/RowSelectCell/RowSelectCell';
|
||||||
|
import useUiConfig from '../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
|
||||||
export const SegmentTable = () => {
|
export const SegmentTable = () => {
|
||||||
const projectId = useOptionalPathParam('projectId');
|
const projectId = useOptionalPathParam('projectId');
|
||||||
const { segments, loading } = useSegments();
|
const { segments, loading } = useSegments();
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const [initialState] = useState({
|
const [initialState] = useState({
|
||||||
sortBy: [{ id: 'createdAt' }],
|
sortBy: [{ id: 'createdAt' }],
|
||||||
@ -56,6 +65,10 @@ export const SegmentTable = () => {
|
|||||||
return segments;
|
return segments;
|
||||||
}, [segments, projectId]);
|
}, [segments, projectId]);
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => getColumns(uiConfig.flags.segmentContextFieldUsage),
|
||||||
|
[uiConfig.flags.segmentContextFieldUsage]
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
getTableBodyProps,
|
getTableBodyProps,
|
||||||
@ -68,7 +81,7 @@ export const SegmentTable = () => {
|
|||||||
} = useTable(
|
} = useTable(
|
||||||
{
|
{
|
||||||
initialState,
|
initialState,
|
||||||
columns: COLUMNS as any,
|
columns: columns as any,
|
||||||
data: data as any,
|
data: data as any,
|
||||||
sortTypes,
|
sortTypes,
|
||||||
autoResetGlobalFilter: false,
|
autoResetGlobalFilter: false,
|
||||||
@ -95,7 +108,7 @@ export const SegmentTable = () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
setHiddenColumns,
|
setHiddenColumns,
|
||||||
COLUMNS
|
columns
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -170,7 +183,7 @@ export const SegmentTable = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLUMNS = [
|
const getColumns = (segmentContextFieldUsage?: boolean) => [
|
||||||
{
|
{
|
||||||
id: 'Icon',
|
id: 'Icon',
|
||||||
width: '1%',
|
width: '1%',
|
||||||
@ -182,10 +195,41 @@ const COLUMNS = [
|
|||||||
Header: 'Name',
|
Header: 'Name',
|
||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
width: '60%',
|
width: '60%',
|
||||||
Cell: ({ value, row: { original } }: any) => (
|
Cell: ({
|
||||||
<HighlightCell value={value} subtitle={original.description} />
|
row: {
|
||||||
|
original: { name, description, id },
|
||||||
|
},
|
||||||
|
}: any) => (
|
||||||
|
<LinkCell
|
||||||
|
title={name}
|
||||||
|
to={`/segments/edit/${id}`}
|
||||||
|
subtitle={description}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
...(segmentContextFieldUsage
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
Header: 'Used in',
|
||||||
|
width: '60%',
|
||||||
|
Cell: ({ value, row: { original } }: any) => (
|
||||||
|
<TextCell
|
||||||
|
sx={{
|
||||||
|
color:
|
||||||
|
original.usedInProjects === 0 &&
|
||||||
|
original.usedInFeatures === 0
|
||||||
|
? theme.palette.text.disabled
|
||||||
|
: 'inherit',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>{original.usedInProjects} projects</Box>
|
||||||
|
<Box>{original.usedInFeatures} feature toggles</Box>
|
||||||
|
</TextCell>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
|
||||||
{
|
{
|
||||||
Header: 'Project',
|
Header: 'Project',
|
||||||
accessor: 'project',
|
accessor: 'project',
|
||||||
|
@ -51,6 +51,7 @@ export interface IFlags {
|
|||||||
variantMetrics?: boolean;
|
variantMetrics?: boolean;
|
||||||
strategyImprovements?: boolean;
|
strategyImprovements?: boolean;
|
||||||
disableBulkToggle?: boolean;
|
disableBulkToggle?: boolean;
|
||||||
|
segmentContextFieldUsage?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
@ -90,6 +90,7 @@ exports[`should create default config 1`] = `
|
|||||||
"personalAccessTokensKillSwitch": false,
|
"personalAccessTokensKillSwitch": false,
|
||||||
"proPlanAutoCharge": false,
|
"proPlanAutoCharge": false,
|
||||||
"responseTimeWithAppNameKillSwitch": false,
|
"responseTimeWithAppNameKillSwitch": false,
|
||||||
|
"segmentContextFieldUsage": false,
|
||||||
"strategyImprovements": false,
|
"strategyImprovements": false,
|
||||||
"strictSchemaValidation": false,
|
"strictSchemaValidation": false,
|
||||||
"variantMetrics": false,
|
"variantMetrics": false,
|
||||||
@ -120,6 +121,7 @@ exports[`should create default config 1`] = `
|
|||||||
"personalAccessTokensKillSwitch": false,
|
"personalAccessTokensKillSwitch": false,
|
||||||
"proPlanAutoCharge": false,
|
"proPlanAutoCharge": false,
|
||||||
"responseTimeWithAppNameKillSwitch": false,
|
"responseTimeWithAppNameKillSwitch": false,
|
||||||
|
"segmentContextFieldUsage": false,
|
||||||
"strategyImprovements": false,
|
"strategyImprovements": false,
|
||||||
"strictSchemaValidation": false,
|
"strictSchemaValidation": false,
|
||||||
"variantMetrics": false,
|
"variantMetrics": false,
|
||||||
|
@ -99,7 +99,12 @@ export const createStores = (
|
|||||||
),
|
),
|
||||||
userSplashStore: new UserSplashStore(db, eventBus, getLogger),
|
userSplashStore: new UserSplashStore(db, eventBus, getLogger),
|
||||||
roleStore: new RoleStore(db, eventBus, getLogger),
|
roleStore: new RoleStore(db, eventBus, getLogger),
|
||||||
segmentStore: new SegmentStore(db, eventBus, getLogger),
|
segmentStore: new SegmentStore(
|
||||||
|
db,
|
||||||
|
eventBus,
|
||||||
|
getLogger,
|
||||||
|
config.flagResolver,
|
||||||
|
),
|
||||||
groupStore: new GroupStore(db),
|
groupStore: new GroupStore(db),
|
||||||
publicSignupTokenStore: new PublicSignupTokenStore(
|
publicSignupTokenStore: new PublicSignupTokenStore(
|
||||||
db,
|
db,
|
||||||
|
@ -6,6 +6,7 @@ import NotFoundError from '../error/notfound-error';
|
|||||||
import { PartialSome } from '../types/partial';
|
import { PartialSome } from '../types/partial';
|
||||||
import User from '../types/user';
|
import User from '../types/user';
|
||||||
import { Db } from './db';
|
import { Db } from './db';
|
||||||
|
import { IFlagResolver } from '../types';
|
||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
segments: 'segments',
|
segments: 'segments',
|
||||||
@ -29,7 +30,9 @@ interface ISegmentRow {
|
|||||||
description?: string;
|
description?: string;
|
||||||
segment_project_id?: string;
|
segment_project_id?: string;
|
||||||
created_by?: string;
|
created_by?: string;
|
||||||
created_at?: Date;
|
created_at: Date;
|
||||||
|
used_in_projects?: number;
|
||||||
|
used_in_features?: number;
|
||||||
constraints: IConstraint[];
|
constraints: IConstraint[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,9 +49,17 @@ export default class SegmentStore implements ISegmentStore {
|
|||||||
|
|
||||||
private db: Db;
|
private db: Db;
|
||||||
|
|
||||||
constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) {
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
db: Db,
|
||||||
|
eventBus: EventEmitter,
|
||||||
|
getLogger: LogProvider,
|
||||||
|
flagResolver: IFlagResolver,
|
||||||
|
) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
this.flagResolver = flagResolver;
|
||||||
this.logger = getLogger('lib/db/segment-store.ts');
|
this.logger = getLogger('lib/db/segment-store.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +107,35 @@ export default class SegmentStore implements ISegmentStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getAll(): Promise<ISegment[]> {
|
async getAll(): Promise<ISegment[]> {
|
||||||
|
if (this.flagResolver.isEnabled('segmentContextFieldUsage')) {
|
||||||
|
const rows: ISegmentRow[] = await this.db
|
||||||
|
.select(
|
||||||
|
this.prefixColumns(),
|
||||||
|
'used_in_projects',
|
||||||
|
'used_in_features',
|
||||||
|
)
|
||||||
|
.countDistinct(
|
||||||
|
`${T.featureStrategies}.project_name AS used_in_projects`,
|
||||||
|
)
|
||||||
|
.countDistinct(
|
||||||
|
`${T.featureStrategies}.feature_name AS used_in_features`,
|
||||||
|
)
|
||||||
|
.from(T.segments)
|
||||||
|
.leftJoin(
|
||||||
|
T.featureStrategySegment,
|
||||||
|
`${T.segments}.id`,
|
||||||
|
`${T.featureStrategySegment}.segment_id`,
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
T.featureStrategies,
|
||||||
|
`${T.featureStrategies}.id`,
|
||||||
|
`${T.featureStrategySegment}.feature_strategy_id`,
|
||||||
|
)
|
||||||
|
.groupBy(this.prefixColumns())
|
||||||
|
.orderBy('name', 'asc');
|
||||||
|
|
||||||
|
return rows.map(this.mapRow);
|
||||||
|
} else {
|
||||||
const rows: ISegmentRow[] = await this.db
|
const rows: ISegmentRow[] = await this.db
|
||||||
.select(this.prefixColumns())
|
.select(this.prefixColumns())
|
||||||
.from(T.segments)
|
.from(T.segments)
|
||||||
@ -103,6 +143,7 @@ export default class SegmentStore implements ISegmentStore {
|
|||||||
|
|
||||||
return rows.map(this.mapRow);
|
return rows.map(this.mapRow);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getActive(): Promise<ISegment[]> {
|
async getActive(): Promise<ISegment[]> {
|
||||||
const rows: ISegmentRow[] = await this.db
|
const rows: ISegmentRow[] = await this.db
|
||||||
@ -199,7 +240,7 @@ export default class SegmentStore implements ISegmentStore {
|
|||||||
throw new NotFoundError('No row');
|
throw new NotFoundError('No row');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const segment: ISegment = {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
@ -207,7 +248,15 @@ export default class SegmentStore implements ISegmentStore {
|
|||||||
constraints: row.constraints,
|
constraints: row.constraints,
|
||||||
createdBy: row.created_by,
|
createdBy: row.created_by,
|
||||||
createdAt: row.created_at,
|
createdAt: row.created_at,
|
||||||
|
usedInProjects: row.used_in_projects
|
||||||
|
? Number(row.used_in_projects)
|
||||||
|
: 0,
|
||||||
|
usedInFeatures: row.used_in_projects
|
||||||
|
? Number(row.used_in_features)
|
||||||
|
: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {}
|
destroy(): void {}
|
||||||
|
@ -118,7 +118,12 @@ export const createExportImportTogglesService = (
|
|||||||
const featureToggleStore = new FeatureToggleStore(db, eventBus, getLogger);
|
const featureToggleStore = new FeatureToggleStore(db, eventBus, getLogger);
|
||||||
const tagStore = new TagStore(db, eventBus, getLogger);
|
const tagStore = new TagStore(db, eventBus, getLogger);
|
||||||
const tagTypeStore = new TagTypeStore(db, eventBus, getLogger);
|
const tagTypeStore = new TagTypeStore(db, eventBus, getLogger);
|
||||||
const segmentStore = new SegmentStore(db, eventBus, getLogger);
|
const segmentStore = new SegmentStore(
|
||||||
|
db,
|
||||||
|
eventBus,
|
||||||
|
getLogger,
|
||||||
|
flagResolver,
|
||||||
|
);
|
||||||
const projectStore = new ProjectStore(
|
const projectStore = new ProjectStore(
|
||||||
db,
|
db,
|
||||||
eventBus,
|
eventBus,
|
||||||
|
@ -68,7 +68,12 @@ export const createFeatureToggleService = (
|
|||||||
eventBus,
|
eventBus,
|
||||||
getLogger,
|
getLogger,
|
||||||
);
|
);
|
||||||
const segmentStore = new SegmentStore(db, eventBus, getLogger);
|
const segmentStore = new SegmentStore(
|
||||||
|
db,
|
||||||
|
eventBus,
|
||||||
|
getLogger,
|
||||||
|
flagResolver,
|
||||||
|
);
|
||||||
const contextFieldStore = new ContextFieldStore(db, getLogger);
|
const contextFieldStore = new ContextFieldStore(db, getLogger);
|
||||||
const groupStore = new GroupStore(db);
|
const groupStore = new GroupStore(db);
|
||||||
const accountStore = new AccountStore(db, getLogger);
|
const accountStore = new AccountStore(db, getLogger);
|
||||||
|
@ -21,7 +21,8 @@ export type IFlagKey =
|
|||||||
| 'strategyImprovements'
|
| 'strategyImprovements'
|
||||||
| 'googleAuthEnabled'
|
| 'googleAuthEnabled'
|
||||||
| 'variantMetrics'
|
| 'variantMetrics'
|
||||||
| 'disableBulkToggle';
|
| 'disableBulkToggle'
|
||||||
|
| 'segmentContextFieldUsage';
|
||||||
|
|
||||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||||
|
|
||||||
@ -98,6 +99,10 @@ const flags: IFlags = {
|
|||||||
process.env.DISABLE_BULK_TOGGLE,
|
process.env.DISABLE_BULK_TOGGLE,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
segmentContextFieldUsage: parseEnvVarBoolean(
|
||||||
|
process.env.UNLEASH_SSEGMENT_CONTEXT_FIELD_USAGE,
|
||||||
|
false,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||||
|
@ -402,6 +402,8 @@ export interface ISegment {
|
|||||||
description?: string;
|
description?: string;
|
||||||
project?: string;
|
project?: string;
|
||||||
constraints: IConstraint[];
|
constraints: IConstraint[];
|
||||||
|
usedInProjects?: number;
|
||||||
|
usedInFeatures?: number;
|
||||||
createdBy?: string;
|
createdBy?: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ process.nextTick(async () => {
|
|||||||
responseTimeWithAppNameKillSwitch: false,
|
responseTimeWithAppNameKillSwitch: false,
|
||||||
variantMetrics: true,
|
variantMetrics: true,
|
||||||
strategyImprovements: true,
|
strategyImprovements: true,
|
||||||
|
segmentContextFieldUsage: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user