1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +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',
tags: [{ type: 'backend', value: 'sdk' }],
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', {
features,

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@ export interface FeatureSearchResponseSchema {
*/
createdAt: string | null;
/** 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.
* @nullable

View File

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

View File

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

View File

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

View File

@ -175,11 +175,21 @@ test('should search matching features by name', async () => {
features: [
{
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',
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,

View File

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

View File

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

View File

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