mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: application usage frontend (#4561)
This commit is contained in:
parent
2c3514954c
commit
a97fabd415
@ -58,10 +58,16 @@ yarn run e2e
|
||||
The frontend uses an OpenAPI client generated from the backend's OpenAPI spec.
|
||||
Whenever there are changes to the backend API, the client should be regenerated:
|
||||
|
||||
For now we only use generated types (src/openapi/models).
|
||||
We will use methods (src/openapi/apis) for new features soon.
|
||||
|
||||
```
|
||||
./scripts/generate-openapi.sh
|
||||
yarn gen:api
|
||||
rm -rf src/openapi/apis
|
||||
```
|
||||
|
||||
clean up `src/openapi/index.ts` imports, only keep first line `export * from './models';`
|
||||
|
||||
This script assumes that you have a running instance of the enterprise backend at `http://localhost:4242`.
|
||||
The new OpenAPI client will be generated from the runtime schema of this instance.
|
||||
The target URL can be changed by setting the `UNLEASH_OPENAPI_URL` env var.
|
||||
|
@ -1,13 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
CircularProgress,
|
||||
Icon,
|
||||
Link,
|
||||
styled,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { Avatar, CircularProgress, Icon, Link } from '@mui/material';
|
||||
import { Warning } from '@mui/icons-material';
|
||||
import { styles as themeStyles } from 'component/common';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
@ -27,11 +19,11 @@ import { useGlobalFilter, useSortBy, useTable } from 'react-table';
|
||||
import { sortTypes } from 'utils/sortTypes';
|
||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { ApplicationUsageCell } from './ApplicationUsageCell/ApplicationUsageCell';
|
||||
import { ApplicationSchema } from '../../../openapi';
|
||||
|
||||
export const ApplicationList = () => {
|
||||
const { applications: data, loading } = useApplications();
|
||||
const theme = useTheme();
|
||||
|
||||
const renderNoApplications = () => (
|
||||
<>
|
||||
@ -100,16 +92,11 @@ export const ApplicationList = () => {
|
||||
Header: 'Project(environment)',
|
||||
accessor: 'usage',
|
||||
width: '50%',
|
||||
Cell: () => (
|
||||
<TextCell>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
not connected
|
||||
</Typography>
|
||||
</TextCell>
|
||||
),
|
||||
Cell: ({
|
||||
row: { original },
|
||||
}: {
|
||||
row: { original: ApplicationSchema };
|
||||
}) => <ApplicationUsageCell usage={original.usage} />,
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
{
|
||||
|
@ -0,0 +1,34 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import { render } from 'utils/testRenderer';
|
||||
import { ApplicationUsageCell } from './ApplicationUsageCell';
|
||||
|
||||
test('displays not connected if no usage found', () => {
|
||||
render(<ApplicationUsageCell usage={[]} />);
|
||||
|
||||
expect(screen.getByText('not connected')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('display project and environments in correct manner', () => {
|
||||
render(
|
||||
<ApplicationUsageCell
|
||||
usage={[
|
||||
{ project: 'myProject', environments: ['dev', 'production'] },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const anchor = screen.getByRole('link');
|
||||
expect(anchor).toHaveAttribute('href', '/projects/myProject');
|
||||
expect(screen.getByText('(dev, production)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('when no specific project is defined, do not create link', () => {
|
||||
render(
|
||||
<ApplicationUsageCell
|
||||
usage={[{ project: '*', environments: ['dev', 'production'] }]}
|
||||
/>
|
||||
);
|
||||
|
||||
const anchor = screen.queryByRole('link');
|
||||
expect(anchor).not.toBeInTheDocument();
|
||||
});
|
@ -0,0 +1,64 @@
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { styled, Typography, useTheme } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ApplicationUsageSchema } from '../../../../openapi';
|
||||
|
||||
export interface IApplicationUsageCellProps {
|
||||
usage: ApplicationUsageSchema[] | undefined;
|
||||
}
|
||||
export interface IApplicationUsage {
|
||||
project: string;
|
||||
environments: string[];
|
||||
}
|
||||
|
||||
const StyledLink = styled(Link)(() => ({
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
textDecoration: 'none',
|
||||
'&:hover, &:focus': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}));
|
||||
|
||||
const formatProject = (projectInfo: IApplicationUsage, index: number) => {
|
||||
const separator = index !== 0 ? ', ' : '';
|
||||
const projectElement =
|
||||
projectInfo.project !== '*' ? (
|
||||
<StyledLink to={`/projects/${projectInfo.project}`}>
|
||||
{projectInfo.project}
|
||||
</StyledLink>
|
||||
) : (
|
||||
projectInfo.project
|
||||
);
|
||||
|
||||
const environments = ` (${projectInfo.environments.join(', ')})`;
|
||||
|
||||
return [separator, projectElement, environments];
|
||||
};
|
||||
|
||||
export const ApplicationUsageCell = ({ usage }: IApplicationUsageCellProps) => {
|
||||
const theme = useTheme();
|
||||
const formattedProjects = usage?.flatMap((p, index) =>
|
||||
formatProject(p, index)
|
||||
);
|
||||
return (
|
||||
<TextCell>
|
||||
<ConditionallyRender
|
||||
condition={usage !== undefined && usage.length > 0}
|
||||
show={
|
||||
<Typography variant="body2">{formattedProjects}</Typography>
|
||||
}
|
||||
elseShow={
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
not connected
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</TextCell>
|
||||
);
|
||||
};
|
@ -3,6 +3,7 @@
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
import type { ApplicationUsageSchema } from './applicationUsageSchema';
|
||||
|
||||
/**
|
||||
* Data about an application that's connected to Unleash via an SDK.
|
||||
@ -22,4 +23,6 @@ export interface ApplicationSchema {
|
||||
color?: string;
|
||||
/** An URL to an icon file to be used for the applications's entry in the application list */
|
||||
icon?: string;
|
||||
/** The list of projects the application has been using. */
|
||||
usage?: ApplicationUsageSchema[];
|
||||
}
|
||||
|
15
frontend/src/openapi/models/applicationUsageSchema.ts
Normal file
15
frontend/src/openapi/models/applicationUsageSchema.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data about an project that have been used by applications.
|
||||
*/
|
||||
export interface ApplicationUsageSchema {
|
||||
/** Name of the project */
|
||||
project: string;
|
||||
/** Which environments have been accessed in this project. */
|
||||
environments: string[];
|
||||
}
|
@ -32,6 +32,8 @@ export interface ChangeRequestSchema {
|
||||
segments: ChangeRequestSegmentChangeSchema[];
|
||||
/** A list of approvals that this change request has received. */
|
||||
approvals?: ChangeRequestApprovalSchema[];
|
||||
/** A list of rejections that this change request has received. */
|
||||
rejections?: ChangeRequestApprovalSchema[];
|
||||
/** All comments that have been made on this change request. */
|
||||
comments?: ChangeRequestCommentSchema[];
|
||||
/** The user who created this change request. */
|
||||
|
@ -17,4 +17,5 @@ export const ChangeRequestSchemaState = {
|
||||
Approved: 'Approved',
|
||||
Applied: 'Applied',
|
||||
Cancelled: 'Cancelled',
|
||||
Rejected: 'Rejected',
|
||||
} as const;
|
||||
|
@ -17,4 +17,5 @@ export const ChangeRequestStateSchemaState = {
|
||||
Approved: 'Approved',
|
||||
Applied: 'Applied',
|
||||
Cancelled: 'Cancelled',
|
||||
Rejected: 'Rejected',
|
||||
} as const;
|
||||
|
@ -24,5 +24,4 @@ export interface ClientMetricsEnvSchema {
|
||||
no?: number;
|
||||
/** How many times each variant was returned */
|
||||
variants?: ClientMetricsEnvSchemaVariants;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@ -20,5 +20,4 @@ export interface CreateApplicationSchema {
|
||||
color?: string;
|
||||
/** An URL to an icon file to be used for the applications's entry in the application list */
|
||||
icon?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@ -19,5 +19,4 @@ export interface CreateGroupSchema {
|
||||
rootRole?: number | null;
|
||||
/** A list of users belonging to this group */
|
||||
users?: CreateGroupSchemaUsersItem[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@ -20,5 +20,4 @@ export interface CreateStrategyVariantSchema {
|
||||
stickiness: string;
|
||||
/** Extra data configured for this variant */
|
||||
payload?: CreateStrategyVariantSchemaPayload;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export interface CreateUserResponseSchema {
|
||||
loginAttempts?: number;
|
||||
/** Is the welcome email sent to the user or not */
|
||||
emailSent?: boolean;
|
||||
/** Which [root role](https://docs.getunleash.io/reference/rbac#standard-roles) this user is assigned. Usually a numeric role ID, but can be a string when returning newly created user with an explicit string role. */
|
||||
/** Which [root role](https://docs.getunleash.io/reference/rbac#predefined-roles) this user is assigned. Usually a numeric role ID, but can be a string when returning newly created user with an explicit string role. */
|
||||
rootRole?: CreateUserResponseSchemaRootRole;
|
||||
/** The last time this user logged in */
|
||||
seenAt?: string | null;
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Which [root role](https://docs.getunleash.io/reference/rbac#standard-roles) this user is assigned. Usually a numeric role ID, but can be a string when returning newly created user with an explicit string role.
|
||||
* Which [root role](https://docs.getunleash.io/reference/rbac#predefined-roles) this user is assigned. Usually a numeric role ID, but can be a string when returning newly created user with an explicit string role.
|
||||
*/
|
||||
export type CreateUserResponseSchemaRootRole =
|
||||
| number
|
||||
|
@ -7,4 +7,4 @@
|
||||
/**
|
||||
* Extra associated data related to the event, such as feature toggle state, segment configuration, etc., if applicable.
|
||||
*/
|
||||
export type EventSchemaData = { [key: string]: any } | null;
|
||||
export type EventSchemaData = { [key: string]: any };
|
||||
|
@ -7,4 +7,4 @@
|
||||
/**
|
||||
* Data relating to the previous state of the event's subject.
|
||||
*/
|
||||
export type EventSchemaPreData = { [key: string]: any } | null;
|
||||
export type EventSchemaPreData = { [key: string]: any };
|
||||
|
@ -97,6 +97,7 @@ export const EventSchemaType = {
|
||||
'change-added': 'change-added',
|
||||
'change-discarded': 'change-discarded',
|
||||
'change-edited': 'change-edited',
|
||||
'change-request-rejected': 'change-request-rejected',
|
||||
'change-request-approved': 'change-request-approved',
|
||||
'change-request-approval-added': 'change-request-approval-added',
|
||||
'change-request-cancelled': 'change-request-cancelled',
|
||||
|
@ -15,12 +15,10 @@ export type ExportQuerySchema =
|
||||
environment: string;
|
||||
/** Whether to return a downloadable file */
|
||||
downloadFile?: boolean;
|
||||
[key: string]: any;
|
||||
})
|
||||
| (ExportQuerySchemaOneOfTwo & {
|
||||
/** The environment to export from */
|
||||
environment: string;
|
||||
/** Whether to return a downloadable file */
|
||||
downloadFile?: boolean;
|
||||
[key: string]: any;
|
||||
});
|
||||
|
@ -34,7 +34,10 @@ export interface FeatureSchema {
|
||||
createdAt?: string | null;
|
||||
/** The date the feature was archived */
|
||||
archivedAt?: string | null;
|
||||
/** The date when metrics where last collected for the feature */
|
||||
/**
|
||||
* The date when metrics where last collected for the feature. This field is deprecated, use the one in featureEnvironmentSchema
|
||||
* @deprecated
|
||||
*/
|
||||
lastSeenAt?: string | null;
|
||||
/** The list of environments where the feature can be used */
|
||||
environments?: FeatureEnvironmentSchema[];
|
||||
|
14
frontend/src/openapi/models/getApiTokensByName401.ts
Normal file
14
frontend/src/openapi/models/getApiTokensByName401.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
export type GetApiTokensByName401 = {
|
||||
/** The ID of the error instance */
|
||||
id?: string;
|
||||
/** The name of the error kind */
|
||||
name?: string;
|
||||
/** A description of what went wrong. */
|
||||
message?: string;
|
||||
};
|
14
frontend/src/openapi/models/getApiTokensByName403.ts
Normal file
14
frontend/src/openapi/models/getApiTokensByName403.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
export type GetApiTokensByName403 = {
|
||||
/** The ID of the error instance */
|
||||
id?: string;
|
||||
/** The name of the error kind */
|
||||
name?: string;
|
||||
/** A description of what went wrong. */
|
||||
message?: string;
|
||||
};
|
14
frontend/src/openapi/models/getRoleProjectAccess401.ts
Normal file
14
frontend/src/openapi/models/getRoleProjectAccess401.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
export type GetRoleProjectAccess401 = {
|
||||
/** The ID of the error instance */
|
||||
id?: string;
|
||||
/** The name of the error kind */
|
||||
name?: string;
|
||||
/** A description of what went wrong. */
|
||||
message?: string;
|
||||
};
|
14
frontend/src/openapi/models/getRoleProjectAccess403.ts
Normal file
14
frontend/src/openapi/models/getRoleProjectAccess403.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
export type GetRoleProjectAccess403 = {
|
||||
/** The ID of the error instance */
|
||||
id?: string;
|
||||
/** The name of the error kind */
|
||||
name?: string;
|
||||
/** A description of what went wrong. */
|
||||
message?: string;
|
||||
};
|
@ -69,6 +69,7 @@ export * from './apiTokenSchema';
|
||||
export * from './apiTokenSchemaType';
|
||||
export * from './apiTokensSchema';
|
||||
export * from './applicationSchema';
|
||||
export * from './applicationUsageSchema';
|
||||
export * from './applicationsSchema';
|
||||
export * from './archiveFeature401';
|
||||
export * from './archiveFeature403';
|
||||
@ -403,6 +404,8 @@ export * from './getAllFeatureTypes401';
|
||||
export * from './getAllStrategies401';
|
||||
export * from './getAllToggles401';
|
||||
export * from './getAllToggles403';
|
||||
export * from './getApiTokensByName401';
|
||||
export * from './getApiTokensByName403';
|
||||
export * from './getApplication404';
|
||||
export * from './getArchivedFeatures401';
|
||||
export * from './getArchivedFeatures403';
|
||||
@ -491,6 +494,8 @@ export * from './getRawFeatureMetrics404';
|
||||
export * from './getRoleById400';
|
||||
export * from './getRoleById401';
|
||||
export * from './getRoleById404';
|
||||
export * from './getRoleProjectAccess401';
|
||||
export * from './getRoleProjectAccess403';
|
||||
export * from './getRoles401';
|
||||
export * from './getRoles403';
|
||||
export * from './getSamlSettings400';
|
||||
@ -637,6 +642,8 @@ export * from './projectCreatedSchemaMode';
|
||||
export * from './projectEnvironmentSchema';
|
||||
export * from './projectOverviewSchema';
|
||||
export * from './projectOverviewSchemaMode';
|
||||
export * from './projectRoleSchema';
|
||||
export * from './projectRoleUsageSchema';
|
||||
export * from './projectSchema';
|
||||
export * from './projectSchemaMode';
|
||||
export * from './projectSettingsSchema';
|
||||
|
21
frontend/src/openapi/models/projectRoleSchema.ts
Normal file
21
frontend/src/openapi/models/projectRoleSchema.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
/**
|
||||
* An overview of how many users and groups are mapped to the specified project with the specified role.
|
||||
*/
|
||||
export interface ProjectRoleSchema {
|
||||
/** The id of the project user and group count are counted for. */
|
||||
project: string;
|
||||
/** Id of the role the user and group count are counted for. */
|
||||
role?: number;
|
||||
/** Number of users mapped to this project. */
|
||||
userCount?: number;
|
||||
/** Number of service accounts mapped to this project. */
|
||||
serviceAccountCount?: number;
|
||||
/** Number of groups mapped to this project. */
|
||||
groupCount?: number;
|
||||
}
|
14
frontend/src/openapi/models/projectRoleUsageSchema.ts
Normal file
14
frontend/src/openapi/models/projectRoleUsageSchema.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
import type { ProjectRoleSchema } from './projectRoleSchema';
|
||||
|
||||
/**
|
||||
* A collection of projects with counts of users and groups mapped to them with specified roles.
|
||||
*/
|
||||
export interface ProjectRoleUsageSchema {
|
||||
/** A collection of projects with counts of users and groups mapped to them with specified roles. */
|
||||
projects?: ProjectRoleSchema[];
|
||||
}
|
@ -18,12 +18,7 @@ export interface SearchEventsSchema {
|
||||
project?: string;
|
||||
/** Find events by feature toggle name (case-sensitive). */
|
||||
feature?: string;
|
||||
/**
|
||||
Find events by a free-text search query.
|
||||
The query will be matched against the event type,
|
||||
the username or email that created the event (if any),
|
||||
and the event data payload (if any).
|
||||
*/
|
||||
/** Find events by a free-text search query. The query will be matched against the event type, the username or email that created the event (if any), and the event data payload (if any). */
|
||||
query?: string;
|
||||
/** The maximum amount of events to return in the search result */
|
||||
limit?: number;
|
||||
|
@ -97,6 +97,7 @@ export const SearchEventsSchemaType = {
|
||||
'change-added': 'change-added',
|
||||
'change-discarded': 'change-discarded',
|
||||
'change-edited': 'change-edited',
|
||||
'change-request-rejected': 'change-request-rejected',
|
||||
'change-request-approved': 'change-request-approved',
|
||||
'change-request-approval-added': 'change-request-approval-added',
|
||||
'change-request-cancelled': 'change-request-cancelled',
|
||||
|
@ -44,5 +44,4 @@ export interface StateSchema {
|
||||
segments?: SegmentSchema[];
|
||||
/** A list of segment/strategy pairings */
|
||||
featureStrategySegments?: FeatureStrategySegmentSchema[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@ -10,5 +10,4 @@
|
||||
export interface TokenStringListSchema {
|
||||
/** Tokens that we want to get access information about */
|
||||
tokens: string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@ -15,5 +15,4 @@ export interface UpdateUserSchema {
|
||||
name?: string;
|
||||
/** The role to assign to the user. Can be either the role's ID or its unique name. */
|
||||
rootRole?: UpdateUserSchemaRootRole;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export interface UserSchema {
|
||||
loginAttempts?: number;
|
||||
/** Is the welcome email sent to the user or not */
|
||||
emailSent?: boolean;
|
||||
/** Which [root role](https://docs.getunleash.io/reference/rbac#standard-roles) this user is assigned */
|
||||
/** Which [root role](https://docs.getunleash.io/reference/rbac#predefined-roles) this user is assigned */
|
||||
rootRole?: number;
|
||||
/** The last time this user logged in */
|
||||
seenAt?: string | null;
|
||||
|
@ -12,6 +12,6 @@ import type { RoleSchema } from './roleSchema';
|
||||
export interface UsersSchema {
|
||||
/** A list of users in the Unleash instance. */
|
||||
users: UserSchema[];
|
||||
/** A list of [root roles](https://docs.getunleash.io/reference/rbac#standard-roles) in the Unleash instance. */
|
||||
/** A list of [root roles](https://docs.getunleash.io/reference/rbac#predefined-roles) in the Unleash instance. */
|
||||
rootRoles?: RoleSchema[];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user