1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-28 17:55:15 +02:00

feat: display created by user in search (#7292)

This commit is contained in:
Mateusz Kwasniewski 2024-06-06 11:51:54 +02:00 committed by GitHub
parent e79d0707cf
commit 2cc4b5faab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 84 additions and 13 deletions

View File

@ -13,8 +13,13 @@ const setupApi = () => {
name: 'featureA', name: 'featureA',
tags: [{ type: 'backend', value: 'sdk' }], tags: [{ type: 'backend', value: 'sdk' }],
type: 'operational', type: 'operational',
createdBy: { id: 1, name: 'author' },
},
{
name: 'featureB',
type: 'release',
createdBy: { id: 1, name: 'author' },
}, },
{ name: 'featureB', type: 'release' },
]; ];
testServerRoute(server, '/api/admin/search/features', { testServerRoute(server, '/api/admin/search/features', {
features, features,

View File

@ -39,6 +39,7 @@ import {
useProjectFeatureSearch, useProjectFeatureSearch,
useProjectFeatureSearchActions, useProjectFeatureSearchActions,
} from './useProjectFeatureSearch'; } from './useProjectFeatureSearch';
import { UserAvatar } from '../../../common/UserAvatar/UserAvatar';
interface IPaginatedProjectFeatureTogglesProps { interface IPaginatedProjectFeatureTogglesProps {
environments: string[]; environments: string[];
@ -101,6 +102,7 @@ export const ProjectFeatureToggles = ({
const isPlaceholder = Boolean(initialLoad || (loading && total)); const isPlaceholder = Boolean(initialLoad || (loading && total));
const featureLifecycleEnabled = useUiFlag('featureLifecycle'); const featureLifecycleEnabled = useUiFlag('featureLifecycle');
const flagCreatorEnabled = useUiFlag('flagCreator');
const columns = useMemo( const columns = useMemo(
() => [ () => [
@ -167,6 +169,30 @@ export const ProjectFeatureToggles = ({
width: '1%', width: '1%',
}, },
}), }),
...(flagCreatorEnabled
? [
columnHelper.accessor('createdBy', {
id: 'createdBy',
header: 'By',
cell: ({ row: { original } }) => {
return (
<UserAvatar
user={{
id: original.createdBy.id,
name: original.createdBy.name,
imageUrl: original.createdBy.imageUrl,
}}
/>
);
},
enableSorting: false,
meta: {
width: '1%',
align: 'center',
},
}),
]
: []),
columnHelper.accessor('lastSeenAt', { columnHelper.accessor('lastSeenAt', {
id: 'lastSeenAt', id: 'lastSeenAt',
header: 'Last seen', header: 'Last seen',
@ -305,6 +331,11 @@ export const ProjectFeatureToggles = ({
type: '-', type: '-',
name: `Feature name ${index}`, name: `Feature name ${index}`,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
createdBy: {
id: 0,
name: '',
imageUrl: '',
},
dependencyType: null, dependencyType: null,
favorite: false, favorite: false,
impressionData: false, impressionData: false,
@ -404,6 +435,16 @@ export const ProjectFeatureToggles = ({
id: 'createdAt', id: 'createdAt',
isVisible: columnVisibility.createdAt, isVisible: columnVisibility.createdAt,
}, },
...(flagCreatorEnabled
? [
{
header: 'By',
id: 'createdBy',
isVisible:
columnVisibility.createdBy,
},
]
: []),
{ {
header: 'Last seen', header: 'Last seen',
id: 'lastSeenAt', id: 'lastSeenAt',

View File

@ -57,6 +57,7 @@ export const useDefaultColumnVisibility = (allColumnIds: string[]) => {
'lastSeenAt', 'lastSeenAt',
...(featureLifecycleEnabled ? ['lifecycle'] : []), ...(featureLifecycleEnabled ? ['lifecycle'] : []),
'createdAt', 'createdAt',
'createdBy',
'type', 'type',
'tags', 'tags',
...showEnvironments(3), ...showEnvironments(3),

View File

@ -87,6 +87,7 @@ export type UiFlags = {
enableLegacyVariants?: boolean; enableLegacyVariants?: boolean;
navigationSidebar?: boolean; navigationSidebar?: boolean;
commandBarUI?: boolean; commandBarUI?: boolean;
flagCreator?: boolean;
}; };
export interface IVersionInfo { export interface IVersionInfo {

View File

@ -28,7 +28,7 @@ export interface FeatureSearchResponseSchema {
*/ */
createdAt: string | null; createdAt: string | null;
/** User who created the feature flag */ /** User who created the feature flag */
createdBy?: FeatureSearchResponseSchemaCreatedBy; createdBy: FeatureSearchResponseSchemaCreatedBy;
/** /**
* The type of dependency. 'parent' means that the feature is a parent feature, 'child' means that the feature is a child feature. * The type of dependency. 'parent' means that the feature is a parent feature, 'child' means that the feature is a child feature.
* @nullable * @nullable

View File

@ -14,7 +14,7 @@ export type FeatureSearchResponseSchemaCreatedBy = {
* URL used for the user profile image * URL used for the user profile image
* @nullable * @nullable
*/ */
imageUrl: string | null; imageUrl: string;
/** Name of the user */ /** Name of the user */
name: string; name: string;
}; };

View File

@ -122,6 +122,7 @@ exports[`should create default config 1`] = `
}, },
}, },
"filterInvalidClientMetrics": false, "filterInvalidClientMetrics": false,
"flagCreator": false,
"googleAuthEnabled": false, "googleAuthEnabled": false,
"killInsightsUI": false, "killInsightsUI": false,
"killScheduledChangeRequestCache": false, "killScheduledChangeRequestCache": false,

View File

@ -19,6 +19,7 @@ import type {
} from '../feature-toggle/types/feature-toggle-strategies-store-type'; } from '../feature-toggle/types/feature-toggle-strategies-store-type';
import { applyGenericQueryParams, applySearchFilters } from './search-utils'; import { applyGenericQueryParams, applySearchFilters } from './search-utils';
import type { FeatureSearchEnvironmentSchema } from '../../openapi/spec/feature-search-environment-schema'; import type { FeatureSearchEnvironmentSchema } from '../../openapi/spec/feature-search-environment-schema';
import { generateImageUrl } from '../../util';
const sortEnvironments = (overview: IFeatureOverview[]) => { const sortEnvironments = (overview: IFeatureOverview[]) => {
return overview.map((data: IFeatureOverview) => ({ return overview.map((data: IFeatureOverview) => ({
@ -404,6 +405,11 @@ class FeatureSearchStore implements IFeatureSearchStore {
if (!entry) { if (!entry) {
// Create a new entry // Create a new entry
const name =
row.user_name ||
row.user_username ||
row.user_email ||
'unknown';
entry = { entry = {
type: row.type, type: row.type,
description: row.description, description: row.description,
@ -419,12 +425,12 @@ class FeatureSearchStore implements IFeatureSearchStore {
segments: row.segment_name ? [row.segment_name] : [], segments: row.segment_name ? [row.segment_name] : [],
createdBy: { createdBy: {
id: Number(row.user_id), id: Number(row.user_id),
name: name: name,
row.user_name || imageUrl: generateImageUrl({
row.user_username || id: row.user_id,
row.user_email || email: row.user_email,
'unknown', username: name,
imageUrl: row.user_image_url, }),
}, },
}; };
if (featureLifecycleEnabled) { if (featureLifecycleEnabled) {

View File

@ -175,11 +175,21 @@ test('should search matching features by name', async () => {
features: [ features: [
{ {
name: 'my_feature_a', name: 'my_feature_a',
createdBy: { id: 1, name: 'user@getunleash.io' }, createdBy: {
id: 1,
name: 'user@getunleash.io',
imageUrl:
'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g',
},
}, },
{ {
name: 'my_feature_b', name: 'my_feature_b',
createdBy: { id: 1, name: 'user@getunleash.io' }, createdBy: {
id: 1,
name: 'user@getunleash.io',
imageUrl:
'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g',
},
}, },
], ],
total: 2, total: 2,

View File

@ -21,6 +21,7 @@ export const featureSearchResponseSchema = {
'favorite', 'favorite',
'impressionData', 'impressionData',
'createdAt', 'createdAt',
'createdBy',
'environments', 'environments',
'segments', 'segments',
], ],
@ -197,7 +198,6 @@ export const featureSearchResponseSchema = {
description: `URL used for the user profile image`, description: `URL used for the user profile image`,
type: 'string', type: 'string',
example: 'https://example.com/242x200.png', example: 'https://example.com/242x200.png',
nullable: true,
}, },
}, },
}, },

View File

@ -62,7 +62,8 @@ export type IFlagKey =
| 'enableLegacyVariants' | 'enableLegacyVariants'
| 'debugMetrics' | 'debugMetrics'
| 'navigationSidebar' | 'navigationSidebar'
| 'commandBarUI'; | 'commandBarUI'
| 'flagCreator';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@ -299,6 +300,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_COMMAND_BAR_UI, process.env.UNLEASH_EXPERIMENTAL_COMMAND_BAR_UI,
false, false,
), ),
flagCreator: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_FLAG_CREATOR,
false,
),
}; };
export const defaultExperimentalOptions: IExperimentalOptions = { export const defaultExperimentalOptions: IExperimentalOptions = {

View File

@ -53,6 +53,7 @@ process.nextTick(async () => {
createProjectWithEnvironmentConfig: true, createProjectWithEnvironmentConfig: true,
manyStrategiesPagination: true, manyStrategiesPagination: true,
enableLegacyVariants: false, enableLegacyVariants: false,
flagCreator: true,
}, },
}, },
authentication: { authentication: {