diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx index d3527b1cf7..a5fdc851a8 100644 --- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx +++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx @@ -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 ( <> ({ paddingBottom: theme.spacing(4) })}> @@ -30,14 +43,13 @@ export const ExecutiveDashboard: VFC = () => { /> - - - - + + + ); diff --git a/frontend/src/component/executiveDashboard/FlagStats/FlagStats.tsx b/frontend/src/component/executiveDashboard/FlagStats/FlagStats.tsx index 693c6e8091..846933254a 100644 --- a/frontend/src/component/executiveDashboard/FlagStats/FlagStats.tsx +++ b/frontend/src/component/executiveDashboard/FlagStats/FlagStats.tsx @@ -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 = ({ + count, + flagsPerUser, +}) => { return ( @@ -98,7 +106,7 @@ export const FlagStats = () => { - 9999 + {count} @@ -116,7 +124,7 @@ export const FlagStats = () => { Flags per user - 3.5 + {flagsPerUser} ); diff --git a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx index 25e0e05adb..0f81ad499d 100644 --- a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx @@ -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 = ({ - 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); diff --git a/frontend/src/component/executiveDashboard/UserStats/UserStats.tsx b/frontend/src/component/executiveDashboard/UserStats/UserStats.tsx index 0f13f64b06..4291e75d34 100644 --- a/frontend/src/component/executiveDashboard/UserStats/UserStats.tsx +++ b/frontend/src/component/executiveDashboard/UserStats/UserStats.tsx @@ -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 = ({ count }) => { + const showInactiveUsers = useUiFlag('showInactiveUsers'); + return ( - - Total users - - - 9999 - - - + <> + + + Total users + + + + {count} + + + + - - - + + + + - - - - + + + + + + } + /> - - - View users - - - + + + View users + + + + + ); }; diff --git a/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts index 74b9780d70..9a29d66071 100644 --- a/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts +++ b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts @@ -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, diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index 3562790a0d..4e008270ba 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -80,6 +80,7 @@ export type UiFlags = { changeRequestConflictHandling?: boolean; feedbackComments?: Variant; displayUpgradeEdgeBanner?: boolean; + showInactiveUsers?: boolean; }; export interface IVersionInfo { diff --git a/frontend/src/openapi/models/actionsListSchema.ts b/frontend/src/openapi/models/actionsListSchema.ts index 9147746926..c541c92802 100644 --- a/frontend/src/openapi/models/actionsListSchema.ts +++ b/frontend/src/openapi/models/actionsListSchema.ts @@ -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[]; } diff --git a/frontend/src/openapi/models/actionsSchema.ts b/frontend/src/openapi/models/actionsSchema.ts index 43155306e1..b5c9de16a5 100644 --- a/frontend/src/openapi/models/actionsSchema.ts +++ b/frontend/src/openapi/models/actionsSchema.ts @@ -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 */ diff --git a/frontend/src/openapi/models/createActionsSchema.ts b/frontend/src/openapi/models/createActionsSchema.ts index 41a07bdcdf..e2829c9250 100644 --- a/frontend/src/openapi/models/createActionsSchema.ts +++ b/frontend/src/openapi/models/createActionsSchema.ts @@ -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 */ diff --git a/frontend/src/openapi/models/executiveSummarySchema.ts b/frontend/src/openapi/models/executiveSummarySchema.ts index 34de0c32a5..fec4155686 100644 --- a/frontend/src/openapi/models/executiveSummarySchema.ts +++ b/frontend/src/openapi/models/executiveSummarySchema.ts @@ -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[]; } diff --git a/frontend/src/openapi/models/executiveSummarySchemaFlagTrendsItem.ts b/frontend/src/openapi/models/executiveSummarySchemaFlagTrendsItem.ts new file mode 100644 index 0000000000..72de2a7849 --- /dev/null +++ b/frontend/src/openapi/models/executiveSummarySchemaFlagTrendsItem.ts @@ -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; +}; diff --git a/frontend/src/openapi/models/executiveSummarySchemaFlags.ts b/frontend/src/openapi/models/executiveSummarySchemaFlags.ts new file mode 100644 index 0000000000..2a069ff9c3 --- /dev/null +++ b/frontend/src/openapi/models/executiveSummarySchemaFlags.ts @@ -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; +}; diff --git a/frontend/src/openapi/models/executiveSummarySchemaFlagsTrendsItem.ts b/frontend/src/openapi/models/executiveSummarySchemaFlagsTrendsItem.ts deleted file mode 100644 index c375526011..0000000000 --- a/frontend/src/openapi/models/executiveSummarySchemaFlagsTrendsItem.ts +++ /dev/null @@ -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; -}; diff --git a/frontend/src/openapi/models/executiveSummarySchemaUserStats.ts b/frontend/src/openapi/models/executiveSummarySchemaUserStats.ts deleted file mode 100644 index 1f65dfdcef..0000000000 --- a/frontend/src/openapi/models/executiveSummarySchemaUserStats.ts +++ /dev/null @@ -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; -}; diff --git a/frontend/src/openapi/models/executiveSummarySchemaUserStatsRoles.ts b/frontend/src/openapi/models/executiveSummarySchemaUserStatsRoles.ts deleted file mode 100644 index 67f0bd17c0..0000000000 --- a/frontend/src/openapi/models/executiveSummarySchemaUserStatsRoles.ts +++ /dev/null @@ -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; -}; diff --git a/frontend/src/openapi/models/executiveSummarySchemaUsers.ts b/frontend/src/openapi/models/executiveSummarySchemaUsers.ts new file mode 100644 index 0000000000..a524856f28 --- /dev/null +++ b/frontend/src/openapi/models/executiveSummarySchemaUsers.ts @@ -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; +}; diff --git a/frontend/src/openapi/models/index.ts b/frontend/src/openapi/models/index.ts index 7449137a17..3732a99d7e 100644 --- a/frontend/src/openapi/models/index.ts +++ b/frontend/src/openapi/models/index.ts @@ -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'; diff --git a/frontend/src/openapi/models/playgroundFeatureSchemaVariant.ts b/frontend/src/openapi/models/playgroundFeatureSchemaVariant.ts index 7d551c45e8..b38113ed67 100644 --- a/frontend/src/openapi/models/playgroundFeatureSchemaVariant.ts +++ b/frontend/src/openapi/models/playgroundFeatureSchemaVariant.ts @@ -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. */ diff --git a/frontend/src/openapi/models/projectSchema.ts b/frontend/src/openapi/models/projectSchema.ts index 9df5784694..86fe979673 100644 --- a/frontend/src/openapi/models/projectSchema.ts +++ b/frontend/src/openapi/models/projectSchema.ts @@ -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; } diff --git a/frontend/src/openapi/models/proxyFeatureSchemaVariant.ts b/frontend/src/openapi/models/proxyFeatureSchemaVariant.ts index 143aaa8e0b..47164856db 100644 --- a/frontend/src/openapi/models/proxyFeatureSchemaVariant.ts +++ b/frontend/src/openapi/models/proxyFeatureSchemaVariant.ts @@ -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 */ diff --git a/frontend/src/openapi/models/variantFlagSchema.ts b/frontend/src/openapi/models/variantFlagSchema.ts index e2472e2ee1..a88b346fe1 100644 --- a/frontend/src/openapi/models/variantFlagSchema.ts +++ b/frontend/src/openapi/models/variantFlagSchema.ts @@ -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. */ diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index f96b8c7962..038467708b 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -127,6 +127,7 @@ exports[`should create default config 1`] = ` "proPlanAutoCharge": false, "responseTimeWithAppNameKillSwitch": false, "scheduledConfigurationChanges": false, + "showInactiveUsers": false, "strictSchemaValidation": false, "stripClientHeadersOn304": false, }, diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 29755623ba..4fd63ab5fd 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -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 = {