mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
feat: connect dashboard static widgets to data (#6062)
This PR connects the static widgets to actual data
This commit is contained in:
parent
832884b4f5
commit
7d6d4064a8
@ -18,6 +18,19 @@ const StyledGrid = styled(Box)(({ theme }) => ({
|
||||
export const ExecutiveDashboard: VFC = () => {
|
||||
const { executiveDashboardData, loading, error } = useExecutiveDashboard();
|
||||
|
||||
const calculateFlagPerUsers = () => {
|
||||
if (
|
||||
executiveDashboardData.users.total === 0 ||
|
||||
executiveDashboardData.flags.total === 0
|
||||
)
|
||||
return '0';
|
||||
|
||||
return (
|
||||
executiveDashboardData.flags.total /
|
||||
executiveDashboardData.users.total
|
||||
).toFixed(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={(theme) => ({ paddingBottom: theme.spacing(4) })}>
|
||||
@ -30,14 +43,13 @@ export const ExecutiveDashboard: VFC = () => {
|
||||
/>
|
||||
</Box>
|
||||
<StyledGrid>
|
||||
<UserStats />
|
||||
<UsersChart
|
||||
userTrends={executiveDashboardData?.userTrends ?? []}
|
||||
/>
|
||||
<FlagStats />
|
||||
<FlagsChart
|
||||
flagsTrends={executiveDashboardData?.flagsTrends ?? []}
|
||||
<UserStats count={executiveDashboardData.users.total} />
|
||||
<FlagStats
|
||||
count={executiveDashboardData.flags.total}
|
||||
flagsPerUser={calculateFlagPerUsers()}
|
||||
/>
|
||||
<UsersChart userTrends={executiveDashboardData.userTrends} />
|
||||
<FlagsChart flagTrends={executiveDashboardData.flagTrends} />
|
||||
</StyledGrid>
|
||||
</>
|
||||
);
|
||||
|
@ -78,7 +78,15 @@ const StyledSettingsIcon = styled(Settings)(({ theme }) => ({
|
||||
marginRight: theme.spacing(0.5),
|
||||
}));
|
||||
|
||||
export const FlagStats = () => {
|
||||
interface IFlagStatsProps {
|
||||
count: number;
|
||||
flagsPerUser: string;
|
||||
}
|
||||
|
||||
export const FlagStats: React.FC<IFlagStatsProps> = ({
|
||||
count,
|
||||
flagsPerUser,
|
||||
}) => {
|
||||
return (
|
||||
<StyledContent>
|
||||
<StyledHeader variant='h1'>
|
||||
@ -98,7 +106,7 @@ export const FlagStats = () => {
|
||||
</StyledHeader>
|
||||
<StyledRingContainer>
|
||||
<StyledRing>
|
||||
<StyledRingContent>9999</StyledRingContent>
|
||||
<StyledRingContent>{count}</StyledRingContent>
|
||||
</StyledRing>
|
||||
</StyledRingContainer>
|
||||
|
||||
@ -116,7 +124,7 @@ export const FlagStats = () => {
|
||||
</StyledHeaderContainer>
|
||||
<Typography variant='body2'>Flags per user</Typography>
|
||||
</StyledTextContainer>
|
||||
<StyledFlagCountPerUser>3.5</StyledFlagCountPerUser>
|
||||
<StyledFlagCountPerUser>{flagsPerUser}</StyledFlagCountPerUser>
|
||||
</StyledInsightsContainer>
|
||||
</StyledContent>
|
||||
);
|
||||
|
@ -22,27 +22,27 @@ import { ExecutiveSummarySchema } from 'openapi';
|
||||
|
||||
const createData = (
|
||||
theme: Theme,
|
||||
flagsTrends: ExecutiveSummarySchema['flagsTrends'] = [],
|
||||
flagTrends: ExecutiveSummarySchema['flagTrends'] = [],
|
||||
) => ({
|
||||
labels: flagsTrends.map((item) => item.date),
|
||||
labels: flagTrends.map((item) => item.date),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Total flags',
|
||||
data: flagsTrends.map((item) => item.total),
|
||||
data: flagTrends.map((item) => item.total),
|
||||
borderColor: theme.palette.primary.main,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
fill: true,
|
||||
},
|
||||
{
|
||||
label: 'Archived flags',
|
||||
data: flagsTrends.map((item) => item.archived),
|
||||
borderColor: theme.palette.error.main,
|
||||
backgroundColor: theme.palette.error.main,
|
||||
label: 'Stale',
|
||||
data: flagTrends.map((item) => item.stale),
|
||||
borderColor: theme.palette.warning.main,
|
||||
backgroundColor: theme.palette.warning.main,
|
||||
fill: true,
|
||||
},
|
||||
{
|
||||
label: 'Active flags',
|
||||
data: flagsTrends.map((item) => item.active),
|
||||
data: flagTrends.map((item) => item.active),
|
||||
borderColor: theme.palette.success.main,
|
||||
backgroundColor: theme.palette.success.main,
|
||||
fill: true,
|
||||
@ -102,17 +102,17 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
|
||||
}) as const;
|
||||
|
||||
interface IFlagsChartComponentProps {
|
||||
flagsTrends: ExecutiveSummarySchema['flagsTrends'];
|
||||
flagTrends: ExecutiveSummarySchema['flagTrends'];
|
||||
}
|
||||
|
||||
const FlagsChartComponent: VFC<IFlagsChartComponentProps> = ({
|
||||
flagsTrends,
|
||||
flagTrends,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { locationSettings } = useLocationSettings();
|
||||
const data = useMemo(
|
||||
() => createData(theme, flagsTrends),
|
||||
[theme, flagsTrends],
|
||||
() => createData(theme, flagTrends),
|
||||
[theme, flagTrends],
|
||||
);
|
||||
const options = createOptions(theme, locationSettings);
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { ChevronRight } from '@mui/icons-material';
|
||||
import { Box, Typography, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const StyledContent = styled(Box)(({ theme }) => ({
|
||||
@ -73,40 +76,59 @@ const StyledLink = styled(Link)({
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
export const UserStats = () => {
|
||||
interface IUserStatsProps {
|
||||
count: number;
|
||||
}
|
||||
|
||||
export const UserStats: React.FC<IUserStatsProps> = ({ count }) => {
|
||||
const showInactiveUsers = useUiFlag('showInactiveUsers');
|
||||
|
||||
return (
|
||||
<StyledContent>
|
||||
<StyledHeader variant='h1'>Total users</StyledHeader>
|
||||
<StyledUserContainer>
|
||||
<StyledUserBox>
|
||||
<StyledUserCount variant='h2'>9999</StyledUserCount>
|
||||
</StyledUserBox>
|
||||
<StyledCustomShadow />
|
||||
</StyledUserContainer>
|
||||
<>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<StyledContent>
|
||||
<StyledHeader variant='h1'>Total users</StyledHeader>
|
||||
<StyledUserContainer>
|
||||
<StyledUserBox>
|
||||
<StyledUserCount variant='h2'>
|
||||
{count}
|
||||
</StyledUserCount>
|
||||
</StyledUserBox>
|
||||
<StyledCustomShadow />
|
||||
</StyledUserContainer>
|
||||
|
||||
<StyledUserDistributionContainer>
|
||||
<UserDistribution />
|
||||
</StyledUserDistributionContainer>
|
||||
<ConditionallyRender
|
||||
condition={showInactiveUsers}
|
||||
show={
|
||||
<>
|
||||
<StyledUserDistributionContainer>
|
||||
<UserDistribution />
|
||||
</StyledUserDistributionContainer>
|
||||
|
||||
<StyledDistInfoContainer>
|
||||
<UserDistributionInfo
|
||||
type='active'
|
||||
percentage='70'
|
||||
count='9999'
|
||||
/>
|
||||
<UserDistributionInfo
|
||||
type='inactive'
|
||||
percentage='30'
|
||||
count='9999'
|
||||
/>
|
||||
</StyledDistInfoContainer>
|
||||
<StyledDistInfoContainer>
|
||||
<UserDistributionInfo
|
||||
type='active'
|
||||
percentage='70'
|
||||
count='9999'
|
||||
/>
|
||||
<UserDistributionInfo
|
||||
type='inactive'
|
||||
percentage='30'
|
||||
count='9999'
|
||||
/>
|
||||
</StyledDistInfoContainer>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<StyledLinkContainer>
|
||||
<StyledLink to='/admin/users'>
|
||||
View users <ChevronRight />
|
||||
</StyledLink>
|
||||
</StyledLinkContainer>
|
||||
</StyledContent>
|
||||
<StyledLinkContainer>
|
||||
<StyledLink to='/admin/users'>
|
||||
View users <ChevronRight />
|
||||
</StyledLink>
|
||||
</StyledLinkContainer>
|
||||
</StyledContent>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,7 @@ import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { ExecutiveSummarySchema } from 'openapi';
|
||||
|
||||
interface IUseExecutiveDashboardDataOutput {
|
||||
executiveDashboardData: ExecutiveSummarySchema | undefined;
|
||||
executiveDashboardData: ExecutiveSummarySchema;
|
||||
refetchExecutiveDashboard: () => void;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
@ -27,7 +27,12 @@ export const useExecutiveDashboard = (
|
||||
}, [path]);
|
||||
|
||||
return {
|
||||
executiveDashboardData: data,
|
||||
executiveDashboardData: data || {
|
||||
users: { total: 0, inactive: 0, active: 0 },
|
||||
flags: { total: 0 },
|
||||
userTrends: [],
|
||||
flagTrends: [],
|
||||
},
|
||||
refetchExecutiveDashboard,
|
||||
loading: !error && !data,
|
||||
error,
|
||||
|
@ -80,6 +80,7 @@ export type UiFlags = {
|
||||
changeRequestConflictHandling?: boolean;
|
||||
feedbackComments?: Variant;
|
||||
displayUpgradeEdgeBanner?: boolean;
|
||||
showInactiveUsers?: boolean;
|
||||
};
|
||||
|
||||
export interface IVersionInfo {
|
||||
|
@ -3,12 +3,12 @@
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
import type { CreateActionsSchema } from './createActionsSchema';
|
||||
import type { ActionsSchema } from './actionsSchema';
|
||||
|
||||
/**
|
||||
* A response model with a list of action sets.
|
||||
*/
|
||||
export interface ActionsListSchema {
|
||||
/** A list of action sets. */
|
||||
actions: CreateActionsSchema[];
|
||||
actions: ActionsSchema[];
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ export interface ActionsSchema {
|
||||
createdAt?: string;
|
||||
/** The id of user that created this action set */
|
||||
createdByUserId?: number;
|
||||
/** Whether this action set is enabled or not */
|
||||
enabled?: boolean;
|
||||
/** The id of the action set */
|
||||
id: number;
|
||||
/** Defines a matching rule for the observable event that will trigger the action set */
|
||||
|
@ -14,6 +14,8 @@ export interface CreateActionsSchema {
|
||||
actions: CreateActionSchema[];
|
||||
/** The id of the service account that will execute the action */
|
||||
actorId: number;
|
||||
/** Whether this action set is enabled or not */
|
||||
enabled?: boolean;
|
||||
/** Defines a matching rule for the observable event that will trigger the action set */
|
||||
match: CreateActionsSchemaMatch;
|
||||
/** The name of the action set */
|
||||
|
@ -3,20 +3,21 @@
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
import type { ExecutiveSummarySchemaFlagsTrendsItem } from './executiveSummarySchemaFlagsTrendsItem';
|
||||
import type { ExecutiveSummarySchemaUserStats } from './executiveSummarySchemaUserStats';
|
||||
import type { ExecutiveSummarySchemaFlags } from './executiveSummarySchemaFlags';
|
||||
import type { ExecutiveSummarySchemaFlagTrendsItem } from './executiveSummarySchemaFlagTrendsItem';
|
||||
import type { ExecutiveSummarySchemaUsers } from './executiveSummarySchemaUsers';
|
||||
import type { ExecutiveSummarySchemaUserTrendsItem } from './executiveSummarySchemaUserTrendsItem';
|
||||
|
||||
/**
|
||||
* Executive summary of Unleash usage
|
||||
*/
|
||||
export interface ExecutiveSummarySchema {
|
||||
/** High level flag count statistics */
|
||||
flags: ExecutiveSummarySchemaFlags;
|
||||
/** How number of flags changed over time */
|
||||
flagsTrends: ExecutiveSummarySchemaFlagsTrendsItem[];
|
||||
/** The type of single-sign option configured for the Unleash instance */
|
||||
ssoType?: string;
|
||||
flagTrends: ExecutiveSummarySchemaFlagTrendsItem[];
|
||||
/** High level user count statistics */
|
||||
userStats: ExecutiveSummarySchemaUserStats;
|
||||
users: ExecutiveSummarySchemaUsers;
|
||||
/** How number of users changed over time */
|
||||
userTrends: ExecutiveSummarySchemaUserTrendsItem[];
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
export type ExecutiveSummarySchemaFlagTrendsItem = {
|
||||
/** The number of active flags on a particular day */
|
||||
active: number;
|
||||
/** A UTC date when the stats were captured. Time is the very end of a given day. */
|
||||
date: string;
|
||||
/** The number of time calculated potentially stale flags on a particular day */
|
||||
potentiallyStale?: number;
|
||||
/** The number of user marked stale flags on a particular day */
|
||||
stale: number;
|
||||
/** The number of all flags on a particular day */
|
||||
total: number;
|
||||
};
|
13
frontend/src/openapi/models/executiveSummarySchemaFlags.ts
Normal file
13
frontend/src/openapi/models/executiveSummarySchemaFlags.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
/**
|
||||
* High level flag count statistics
|
||||
*/
|
||||
export type ExecutiveSummarySchemaFlags = {
|
||||
/** The number of non-archived flags */
|
||||
total: number;
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
export type ExecutiveSummarySchemaFlagsTrendsItem = {
|
||||
/** The number of non-archived flags on a particular day */
|
||||
active: number;
|
||||
/** The number of archived flags on a particular day */
|
||||
archived: number;
|
||||
/** A UTC date when the stats were captured. Time is the very end of a given day. */
|
||||
date: string;
|
||||
/** The number of all flags on a particular day */
|
||||
total: number;
|
||||
};
|
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
import type { ExecutiveSummarySchemaUserStatsRoles } from './executiveSummarySchemaUserStatsRoles';
|
||||
|
||||
/**
|
||||
* High level user count statistics
|
||||
*/
|
||||
export type ExecutiveSummarySchemaUserStats = {
|
||||
/** The number of active Unleash users who have logged in in the past 90 days */
|
||||
active: number;
|
||||
/** The number of inactive Unleash users who have not logged in in the past 90 days. */
|
||||
inactive: number;
|
||||
/** The number of users licensed to use Unleash */
|
||||
licensed: number;
|
||||
/** The number of users with a given [root role](https://docs.getunleash.io/reference/rbac#predefined-roles) */
|
||||
roles: ExecutiveSummarySchemaUserStatsRoles;
|
||||
/** The number of actual Unleash users */
|
||||
total: number;
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
/**
|
||||
* The number of users with a given [root role](https://docs.getunleash.io/reference/rbac#predefined-roles)
|
||||
*/
|
||||
export type ExecutiveSummarySchemaUserStatsRoles = {
|
||||
/** The number of users with the `admin` root role */
|
||||
admin: number;
|
||||
/** The number of users with the `editor` root role */
|
||||
editor: number;
|
||||
/** The number of users with the `viewer` root role */
|
||||
viewer: number;
|
||||
};
|
17
frontend/src/openapi/models/executiveSummarySchemaUsers.ts
Normal file
17
frontend/src/openapi/models/executiveSummarySchemaUsers.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
/**
|
||||
* High level user count statistics
|
||||
*/
|
||||
export type ExecutiveSummarySchemaUsers = {
|
||||
/** The number of active Unleash users who have user Unleash in the past 60 days */
|
||||
active: number;
|
||||
/** The number of inactive Unleash users who have not used Unleash in the past 60 days. */
|
||||
inactive: number;
|
||||
/** The number of actual Unleash users */
|
||||
total: number;
|
||||
};
|
@ -495,10 +495,10 @@ export * from './eventSchemaType';
|
||||
export * from './eventsSchema';
|
||||
export * from './eventsSchemaVersion';
|
||||
export * from './executiveSummarySchema';
|
||||
export * from './executiveSummarySchemaFlagsTrendsItem';
|
||||
export * from './executiveSummarySchemaUserStats';
|
||||
export * from './executiveSummarySchemaUserStatsRoles';
|
||||
export * from './executiveSummarySchemaFlagTrendsItem';
|
||||
export * from './executiveSummarySchemaFlags';
|
||||
export * from './executiveSummarySchemaUserTrendsItem';
|
||||
export * from './executiveSummarySchemaUsers';
|
||||
export * from './exportFeatures404';
|
||||
export * from './exportQuerySchema';
|
||||
export * from './exportQuerySchemaAnyOf';
|
||||
|
@ -14,6 +14,13 @@ import type { PlaygroundFeatureSchemaVariantPayload } from './playgroundFeatureS
|
||||
export type PlaygroundFeatureSchemaVariant = {
|
||||
/** Whether the variant is enabled or not. If the feature is disabled or if it doesn't have variants, this property will be `false` */
|
||||
enabled: boolean;
|
||||
/** Use `featureEnabled` instead. */
|
||||
feature_enabled?: boolean;
|
||||
/**
|
||||
* Whether the feature is enabled or not.
|
||||
* @deprecated
|
||||
*/
|
||||
featureEnabled?: boolean;
|
||||
/** The variant's name. If there is no variant or if the toggle is disabled, this will be `disabled` */
|
||||
name: string;
|
||||
/** An optional payload attached to the variant. */
|
||||
|
@ -29,6 +29,10 @@ export interface ProjectSchema {
|
||||
mode?: ProjectSchemaMode;
|
||||
/** The name of this project */
|
||||
name: string;
|
||||
/** The number of potentially stale features this project has */
|
||||
potentiallyStaleFeatureCount?: number;
|
||||
/** The number of stale features this project has */
|
||||
staleFeatureCount?: number;
|
||||
/** When this project was last updated. */
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
|
@ -13,6 +13,11 @@ export type ProxyFeatureSchemaVariant = {
|
||||
enabled: boolean;
|
||||
/** Whether the feature is enabled or not. */
|
||||
feature_enabled?: boolean;
|
||||
/**
|
||||
* Use `feature_enabled` instead.
|
||||
* @deprecated
|
||||
*/
|
||||
featureEnabled?: boolean;
|
||||
/** The variants name. Is unique for this feature toggle */
|
||||
name: string;
|
||||
/** Extra data configured for this variant */
|
||||
|
@ -13,6 +13,11 @@ export interface VariantFlagSchema {
|
||||
enabled?: boolean;
|
||||
/** Whether the feature is enabled or not. */
|
||||
feature_enabled?: boolean;
|
||||
/**
|
||||
* Use `feature_enabled` instead.
|
||||
* @deprecated
|
||||
*/
|
||||
featureEnabled?: boolean;
|
||||
/** The name of the variant. Will always be disabled if `enabled` is false. */
|
||||
name?: string;
|
||||
/** Additional data associated with this variant. */
|
||||
|
@ -127,6 +127,7 @@ exports[`should create default config 1`] = `
|
||||
"proPlanAutoCharge": false,
|
||||
"responseTimeWithAppNameKillSwitch": false,
|
||||
"scheduledConfigurationChanges": false,
|
||||
"showInactiveUsers": false,
|
||||
"strictSchemaValidation": false,
|
||||
"stripClientHeadersOn304": false,
|
||||
},
|
||||
|
@ -47,7 +47,8 @@ export type IFlagKey =
|
||||
| 'changeRequestConflictHandling'
|
||||
| 'executiveDashboard'
|
||||
| 'feedbackComments'
|
||||
| 'createdByUserIdDataMigration';
|
||||
| 'createdByUserIdDataMigration'
|
||||
| 'showInactiveUsers';
|
||||
|
||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||
|
||||
@ -227,6 +228,10 @@ const flags: IFlags = {
|
||||
process.env.CREATED_BY_USERID_DATA_MIGRATION,
|
||||
false,
|
||||
),
|
||||
showInactiveUsers: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_SHOW_INACTIVE_USERS,
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||
|
Loading…
Reference in New Issue
Block a user