1
0
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:
Fredrik Strand Oseberg 2024-01-30 10:07:16 +01:00 committed by GitHub
parent 832884b4f5
commit 7d6d4064a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 194 additions and 121 deletions

View File

@ -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>
</>
);

View File

@ -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>
);

View File

@ -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);

View File

@ -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>
</>
);
};

View File

@ -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,

View File

@ -80,6 +80,7 @@ export type UiFlags = {
changeRequestConflictHandling?: boolean;
feedbackComments?: Variant;
displayUpgradeEdgeBanner?: boolean;
showInactiveUsers?: boolean;
};
export interface IVersionInfo {

View File

@ -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[];
}

View File

@ -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 */

View File

@ -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 */

View File

@ -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[];
}

View File

@ -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;
};

View 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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View 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;
};

View File

@ -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';

View File

@ -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. */

View File

@ -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;
}

View File

@ -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 */

View File

@ -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. */

View File

@ -127,6 +127,7 @@ exports[`should create default config 1`] = `
"proPlanAutoCharge": false,
"responseTimeWithAppNameKillSwitch": false,
"scheduledConfigurationChanges": false,
"showInactiveUsers": false,
"strictSchemaValidation": false,
"stripClientHeadersOn304": false,
},

View File

@ -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 = {