diff --git a/web-server/pages/api/internal/team/[team_id]/dora_metrics.ts b/web-server/pages/api/internal/team/[team_id]/dora_metrics.ts
index 09d3f52ef..7c01fb132 100644
--- a/web-server/pages/api/internal/team/[team_id]/dora_metrics.ts
+++ b/web-server/pages/api/internal/team/[team_id]/dora_metrics.ts
@@ -1,6 +1,7 @@
import { endOfDay, startOfDay } from 'date-fns';
import * as yup from 'yup';
+import { getTeamRepos } from '@/api/resources/team_repos';
import { Endpoint } from '@/api-helpers/global';
import {
repoFiltersFromTeamProdBranches,
@@ -17,8 +18,8 @@ import {
} from '@/utils/cockpitMetricUtils';
import { isoDateString, getAggregateAndTrendsIntervalTime } from '@/utils/date';
-import { getAllTeamsReposProdBranchesForOrgAsMap } from './repo_branches';
import { getTeamLeadTimePRs } from './insights';
+import { getAllTeamsReposProdBranchesForOrgAsMap } from './repo_branches';
const pathSchema = yup.object().shape({
team_id: yup.string().uuid().required()
@@ -86,7 +87,8 @@ endpoint.handle.GET(getSchema, async (req, res) => {
meanTimeToRestoreResponse,
changeFailureRateResponse,
deploymentFrequencyResponse,
- leadtimePrs
+ leadtimePrs,
+ teamRepos
] = await Promise.all([
fetchLeadTimeStats({
teamId,
@@ -148,10 +150,10 @@ endpoint.handle.GET(getSchema, async (req, res) => {
}),
getTeamLeadTimePRs(teamId, from_date, to_date, prFilters).then(
(r) => r.data
- )
+ ),
+ getTeamRepos(teamId)
]);
- console.log('🚀 ~ endpoint.handle.GET ~ leadTimeResponse:', leadTimeResponse);
return res.send({
lead_time_stats: leadTimeResponse.lead_time_stats,
lead_time_trends: leadTimeResponse.lead_time_trends,
@@ -167,7 +169,8 @@ endpoint.handle.GET(getSchema, async (req, res) => {
deploymentFrequencyResponse.deployment_frequency_stats,
deployment_frequency_trends:
deploymentFrequencyResponse.deployment_frequency_trends,
- lead_time_prs: leadtimePrs
+ lead_time_prs: leadtimePrs,
+ assigned_repos: teamRepos
} as TeamDoraMetricsApiResponseType);
});
diff --git a/web-server/pages/dora-metrics/index.tsx b/web-server/pages/dora-metrics/index.tsx
index 25b41ae9c..1cf07c2f7 100644
--- a/web-server/pages/dora-metrics/index.tsx
+++ b/web-server/pages/dora-metrics/index.tsx
@@ -22,6 +22,7 @@ function Page() {
}
pageTitle="DORA metrics"
isLoading={isLoading}
+ teamDateSelectorMode="single"
>
diff --git a/web-server/src/components/PageHeader.tsx b/web-server/src/components/PageHeader.tsx
index a66591307..db1957ebd 100644
--- a/web-server/src/components/PageHeader.tsx
+++ b/web-server/src/components/PageHeader.tsx
@@ -20,6 +20,7 @@ import { FlexBox, FlexBoxProps } from './FlexBox';
import { Hotkey } from './Hotkey';
import { Logo } from './Logo/Logo';
import { Tabs } from './Tabs';
+import { TeamSelector } from './TeamSelector/TeamSelector';
import { Line } from './Text';
type SubRoute = {
@@ -104,6 +105,9 @@ export const PageHeader: FC<
<>
+ {teamDateSelectorMode && (
+
+ )}
{selectBranch && }
{additionalFilters?.map((filter, i) => (
{filter}
diff --git a/web-server/src/components/TeamSelector/TeamPopover.tsx b/web-server/src/components/TeamSelector/TeamPopover.tsx
new file mode 100644
index 000000000..7a312ba6d
--- /dev/null
+++ b/web-server/src/components/TeamSelector/TeamPopover.tsx
@@ -0,0 +1,535 @@
+import {
+ CheckCircleOutlineRounded,
+ RadioButtonUnchecked,
+ RadioButtonChecked,
+ EditRounded,
+ SearchRounded,
+ ClearRounded
+} from '@mui/icons-material';
+import {
+ alpha,
+ Box,
+ Button,
+ CircularProgress,
+ Divider,
+ InputAdornment,
+ MenuItem,
+ Popover,
+ Stack,
+ SxProps,
+ TextField,
+ Typography,
+ useTheme
+} from '@mui/material';
+import Link from 'next/link';
+import pluralize from 'pluralize';
+import {
+ FC,
+ useCallback,
+ MutableRefObject,
+ Dispatch,
+ SetStateAction
+} from 'react';
+import { useDispatch } from 'react-redux';
+
+import { FlexBox, FlexBoxProps } from '@/components/FlexBox';
+import Scrollbar from '@/components/Scrollbar';
+import { MenuListWrapperSecondary } from '@/components/Shared';
+import IntegrationsData from '@/components/TeamSelector/integrations-data.svg';
+import TeamData from '@/components/TeamSelector/team-data.svg';
+import { Line } from '@/components/Text';
+import { track } from '@/constants/events';
+import { ROUTES } from '@/constants/routes';
+import { useActiveRouteEvent } from '@/hooks/useActiveRouteEvent';
+import { BoolState, useBoolState, useEasyState } from '@/hooks/useEasyState';
+import { useSingleTeamConfig } from '@/hooks/useStateTeamConfig';
+import { appSlice, updateTeamMemberDataSetting } from '@/slices/app';
+import { useSelector } from '@/store';
+import { Team } from '@/types/api/teams';
+import { UserWithAvatar } from '@/types/resources';
+import { homogenize } from '@/utils/datatype';
+import { depFn } from '@/utils/fn';
+
+import { defaultPopoverProps } from './defaultPopoverProps';
+
+export const TeamPopover: FC<{
+ teamElRef: MutableRefObject;
+ teamsPop: BoolState;
+ showAllTeams: boolean;
+ loadingTeams: boolean;
+ isSingleMode: boolean;
+ hideTeamMemberFilter: boolean;
+ setShowAllTeams: Dispatch>;
+ teams: Team[];
+ apiTeams: Team[];
+ usersMap: Record;
+ setProdBranchNamesByTeamId: (teamId: string) => void;
+ closeOnSelect?: boolean;
+}> = ({
+ teamElRef,
+ teamsPop,
+ showAllTeams,
+ setShowAllTeams,
+ apiTeams,
+ teams,
+ loadingTeams,
+ setProdBranchNamesByTeamId,
+ isSingleMode,
+ hideTeamMemberFilter,
+ closeOnSelect
+}) => {
+ const theme = useTheme();
+ const { team } = useSingleTeamConfig();
+
+ const updatingTeamMemberFilter = useBoolState();
+
+ const isRoleEng = false;
+ const activeRouteEvent = useActiveRouteEvent('APP_TEAM_CHANGE_SINGLE');
+ const dispatch = useDispatch();
+
+ const teamSearchFilter = useEasyState('');
+
+ const toggleTeamMemberFilter = useCallback(
+ (enabled: boolean) => {
+ if (!team?.id) return;
+
+ depFn(updatingTeamMemberFilter.trackAsync, async () =>
+ dispatch(
+ updateTeamMemberDataSetting({
+ teamId: team?.id,
+ enabled
+ })
+ )
+ );
+ },
+ [dispatch, team?.id, updatingTeamMemberFilter.trackAsync]
+ );
+
+ const listFilteredBySearch = apiTeams.filter((team) =>
+ teamSearchFilter.value
+ ? homogenize(team.name).includes(homogenize(teamSearchFilter.value))
+ : true
+ );
+
+ const teamReposMap = useSelector((s) => s.app.teamsProdBranchMap);
+
+ return (
+
+
+
+
+
+
+
+ Currently showing all teams
+
+
+ {loadingTeams && }
+
+
+
+ }
+ justifyContent="stretch"
+ alignItems="stretch"
+ spacing={0}
+ height="100%"
+ >
+
+ 3 ? '270px' : undefined}
+ >
+ {apiTeams.length ? (
+ <>
+ {Boolean(apiTeams.length > 4 || teamSearchFilter.value) && (
+ <>
+
+
+
+ ),
+ endAdornment: (
+
+
+
+ )
+ }}
+ sx={{ mb: 1 / 2 }}
+ value={teamSearchFilter.value}
+ onChange={teamSearchFilter.eventHandler}
+ />
+
+ {apiTeams.length} {pluralize('team', apiTeams.length)}{' '}
+ present
+ {listFilteredBySearch.length !== apiTeams.length
+ ? ` (${listFilteredBySearch.length} shown)`
+ : ''}
+
+ >
+ )}
+ {listFilteredBySearch.map((apiTeam) => {
+ const selected = teams.some(
+ (team) => team.id === apiTeam.id
+ );
+
+ return (
+
+ );
+ })}
+ >
+ ) : !loadingTeams ? (
+
+ No teams to show
+
+
+
+
+ ) : (
+ 'We getting your teams together, but someone seems missing 🤔'
+ )}
+
+
+
+
+ {!hideTeamMemberFilter && (
+ <>
+
+
+
+
+
+
+
+
+ }
+ justifyContent="stretch"
+ alignItems="stretch"
+ spacing={0}
+ height="100%"
+ >
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ );
+};
+
+const dataFilterMenuItemSx: SxProps = {
+ maxWidth: '300px',
+ borderRadius: 1,
+ flex: 1,
+ alignItems: 'flex-start',
+ overflow: 'hidden'
+};
+
+const DataFilterRadio: FC<{ checked: boolean; saving?: boolean }> = ({
+ checked,
+ saving
+}) => {
+ return (
+
+ {saving ? (
+
+ ) : checked ? (
+
+
+
+ ) : (
+
+ )}
+
+ );
+};
+
+const DataFilterMenuItem: typeof FlexBox = (props: FlexBoxProps) => {
+ return (
+
+ );
+};
+
+const CustomLoadingButton = () => {
+ const theme = useTheme();
+ return (
+
+
+
+ );
+};
diff --git a/web-server/src/components/TeamSelector/TeamSelector.tsx b/web-server/src/components/TeamSelector/TeamSelector.tsx
new file mode 100644
index 000000000..df5d3866c
--- /dev/null
+++ b/web-server/src/components/TeamSelector/TeamSelector.tsx
@@ -0,0 +1,145 @@
+import {
+ KeyboardArrowDownRounded,
+ AdjustRounded,
+ GroupWorkRounded,
+ WorkspacesOutlined,
+ TerminalOutlined
+} from '@mui/icons-material';
+import { Box } from '@mui/material';
+import { useRouter } from 'next/router';
+import { FC, useRef } from 'react';
+
+import { useAuth } from '@/hooks/useAuth';
+import { useBoolState } from '@/hooks/useEasyState';
+import { useSingleTeamConfig } from '@/hooks/useStateTeamConfig';
+
+import { DatePopover } from './DatePopover';
+import { TeamPopover } from './TeamPopover';
+import { useTeamSelectorSetup } from './useTeamSelectorSetup';
+
+import { FlexBox } from '../FlexBox';
+import { HeaderBtn } from '../HeaderBtn';
+import { LightTooltip } from '../Shared';
+
+export type TeamSelectorModes =
+ | 'single'
+ | 'multiple'
+ | 'date-only'
+ | 'single-only'
+ | 'multiple-only';
+
+export const TeamSelector: FC<{
+ mode?: TeamSelectorModes;
+ closeOnSelect?: boolean;
+}> = ({ mode = 'single', closeOnSelect = false }) => {
+ const teamElRef = useRef(null);
+ const dateElRef = useRef(null);
+ const teamsPop = useBoolState(false);
+ const datesPop = useBoolState(false);
+ const { org } = useAuth();
+ const {
+ teams,
+ apiTeams,
+ dateRangeLabel,
+ teamsLabel,
+ usersMap,
+ hideDateSelector,
+ hideTeamSelector,
+ loadingTeams,
+ setRange,
+ setShowAllTeams,
+ dateRange,
+ isSingleMode,
+ showAllTeams,
+ setProdBranchNamesByTeamId
+ } = useTeamSelectorSetup({ mode });
+
+ const { team } = useSingleTeamConfig();
+
+ const router = useRouter();
+ const hideTeamMemberFilter = true;
+
+ if (!org) return null;
+
+ return (
+ <>
+
+ {!hideTeamSelector && (
+
+
+ {!isSingleMode ? (
+
+ ) : (
+
+ )}
+
+ {!hideTeamMemberFilter && (
+
+ {team?.member_filter_enabled ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+ }
+ endIcon={}
+ onClick={teamsPop.true}
+ sx={{
+ minWidth: '220px',
+ '> .MuiButton-endIcon': { marginLeft: 'auto' }
+ }}
+ >
+ {teamsLabel}
+
+ )}
+ {!hideDateSelector && (
+ }
+ onClick={datesPop.true}
+ sx={{ minWidth: '220px', justifyContent: 'space-between' }}
+ >
+ {dateRangeLabel}
+
+ )}
+
+
+
+ >
+ );
+};
diff --git a/web-server/src/components/TeamSelector/useTeamSelectorSetup.tsx b/web-server/src/components/TeamSelector/useTeamSelectorSetup.tsx
index e759e532e..fee5d515e 100644
--- a/web-server/src/components/TeamSelector/useTeamSelectorSetup.tsx
+++ b/web-server/src/components/TeamSelector/useTeamSelectorSetup.tsx
@@ -110,6 +110,7 @@ export const useTeamSelectorSetup = ({ mode }: UseTeamSelectorSetupArgs) => {
() => payload?.teams || ([] as Team[]),
[payload?.teams]
);
+
const usersMap = useMemo(
() => payload?.users || ([] as unknown as FetchTeamsResponse['users']),
[payload?.users]
@@ -131,9 +132,11 @@ export const useTeamSelectorSetup = ({ mode }: UseTeamSelectorSetupArgs) => {
const teamsLabel = teams.length
? !isSingleMode
? `${teams.length} ${pluralize('team', teams.length)} selected`
- : `${teams[0]?.name} (${teams[0]?.member_ids.length} ${pluralize(
- 'member',
- teams[0]?.member_ids.length
+ : `${teams[0]?.name} (${
+ teamReposProdBranchMap[teams[0]?.id]?.length || 0
+ } ${pluralize(
+ 'repo',
+ teamReposProdBranchMap[teams[0]?.id]?.length || 0
)})`
: `Select ${!isSingleMode ? plural('Team') : 'Team'}`;
diff --git a/web-server/src/content/DoraMetrics/DoraCards/ChangeFailureRateCard.tsx b/web-server/src/content/DoraMetrics/DoraCards/ChangeFailureRateCard.tsx
index f23e2c3dc..b3e044bb6 100644
--- a/web-server/src/content/DoraMetrics/DoraCards/ChangeFailureRateCard.tsx
+++ b/web-server/src/content/DoraMetrics/DoraCards/ChangeFailureRateCard.tsx
@@ -60,9 +60,7 @@ export const ChangeFailureRateCard = () => {
IntegrationGroup.CODE
);
- const isIncidentProviderIntegrationEnabled = integrationSet.has(
- IntegrationGroup.INCIDENT
- );
+ const isIncidentProviderIntegrationEnabled = true;
const canShowIncidentsData =
isCodeProviderIntegrationEnabled && isIncidentProviderIntegrationEnabled;
diff --git a/web-server/src/content/DoraMetrics/DoraCards/ChangeTimeCard.tsx b/web-server/src/content/DoraMetrics/DoraCards/ChangeTimeCard.tsx
index 1b1a094cf..919b7b270 100644
--- a/web-server/src/content/DoraMetrics/DoraCards/ChangeTimeCard.tsx
+++ b/web-server/src/content/DoraMetrics/DoraCards/ChangeTimeCard.tsx
@@ -8,7 +8,6 @@ import {
darken,
List,
ListItem,
- Stack,
useTheme
} from '@mui/material';
import Link from 'next/link';
@@ -18,21 +17,20 @@ import { useMemo } from 'react';
import { Chart2, ChartOptions } from '@/components/Chart2';
import { FlexBox } from '@/components/FlexBox';
import { useOverlayPage } from '@/components/OverlayPageContext';
-import { MiniSwitch } from '@/components/Shared';
import { Line } from '@/components/Text';
import { track } from '@/constants/events';
import { ROUTES } from '@/constants/routes';
import { isRoleLessThanEM } from '@/constants/useRoute';
-import { getTrendsDataFromArray } from '@/content/Cockpit/codeMetrics/shared';
import {
CardRoot,
NoDataImg
} from '@/content/DoraMetrics/DoraCards/sharedComponents';
import { usePropsForChangeTimeCard } from '@/content/DoraMetrics/DoraCards/sharedHooks';
import { useAuth } from '@/hooks/useAuth';
+import { useSelector } from '@/store';
import { ChangeTimeModes } from '@/types/resources';
-import { mergeDateValueTupleArray } from '@/utils/array';
-import { getDurationString } from '@/utils/date';
+import { merge } from '@/utils/datatype';
+import { getDurationString, getSortedDatesAsArrayFromMap } from '@/utils/date';
import { getDoraLink } from '../../PullRequests/DeploymentFrequencyGraph';
import {
@@ -74,18 +72,21 @@ export const ChangeTimeCard = () => {
const {
reposCountWithWorkflowConfigured,
- isActiveModeSwitchDisabled,
isSufficientDataAvailable,
- activeModePrevTrendsData,
- activeModeCurrentTrendsData,
activeModeProps,
isAllAssignedReposHaveDeploymentsConfigured,
allAssignedRepos,
reposWithNoDeploymentsConfigured,
- prevChangeTime,
- toggleActiveModeValue
+ prevChangeTime
} = usePropsForChangeTimeCard();
+ const [currentLeadTimeTrendsData, prevLeadTimeTrendsData] = useSelector(
+ (s) => [
+ s.doraMetrics.metrics_summary?.lead_time_trends.current,
+ s.doraMetrics.metrics_summary?.lead_time_trends.previous
+ ]
+ );
+
const isCodeProviderIntegrationEnabled = true;
const showClassificationBadge =
@@ -102,27 +103,25 @@ export const ChangeTimeCard = () => {
);
+ const mergedLeadTimeTrends = merge(
+ currentLeadTimeTrendsData,
+ prevLeadTimeTrendsData
+ );
+
const series = useMemo(
() => [
{
label: 'Lead Time',
fill: 'start',
- data: getTrendsDataFromArray(
- mergeDateValueTupleArray(
- activeModePrevTrendsData,
- activeModeCurrentTrendsData
- )
- ).map((point) => point || 0),
+ data: getSortedDatesAsArrayFromMap(mergedLeadTimeTrends).map(
+ (key) => mergedLeadTimeTrends[key].lead_time
+ ),
backgroundColor: activeModeProps.backgroundColor,
borderColor: alpha(activeModeProps.backgroundColor, 0.5),
lineTension: 0.2
}
],
- [
- activeModeCurrentTrendsData,
- activeModePrevTrendsData,
- activeModeProps.backgroundColor
- ]
+ [activeModeProps.backgroundColor, mergedLeadTimeTrends]
);
return (
@@ -350,78 +349,6 @@ export const ChangeTimeCard = () => {
{' '}
{'->'}
-
-
- Cycle Time
-
-
- {!reposCountWithWorkflowConfigured ? (
-
-
- No assigned repos have deployment workflow
- configured.
-
- {!isEng && (
-
-
- }
- variant="outlined"
- sx={{ width: 'fit-content' }}
- >
- Configure deployment workflows here
-
-
- )}
-
- ) : (
- <>
-
- No Lead Time data available
- >
- )}
-
- )
- }
- darkTip
- >
-
-
-
-
- Lead Time
-
-
) : isCodeProviderIntegrationEnabled ? (
diff --git a/web-server/src/content/DoraMetrics/DoraCards/MeanTimeToRestoreCard.tsx b/web-server/src/content/DoraMetrics/DoraCards/MeanTimeToRestoreCard.tsx
index 57541a937..55f7e7866 100644
--- a/web-server/src/content/DoraMetrics/DoraCards/MeanTimeToRestoreCard.tsx
+++ b/web-server/src/content/DoraMetrics/DoraCards/MeanTimeToRestoreCard.tsx
@@ -11,9 +11,7 @@ import {
CardRoot,
NoDataImg
} from '@/content/DoraMetrics/DoraCards/sharedComponents';
-import { useAuth } from '@/hooks/useAuth';
import { useDoraMetricsGraph } from '@/hooks/useDoraMetricsGraph';
-import { IntegrationGroup } from '@/types/resources';
import { getDurationString } from '@/utils/date';
import { NoIncidentsLabel } from './NoIncidentsLabel';
@@ -48,13 +46,11 @@ const chartOptions = {
} as ChartOptions;
export const MeanTimeToRestoreCard = () => {
- const { integrationSet } = useAuth();
const { isNoDataAvailable, ...meanTimeToRestoreProps } =
useMeanTimeToRestoreProps();
+
const { trendsSeriesMap } = useDoraMetricsGraph();
- const isIncidentProviderIntegrationEnabled = integrationSet.has(
- IntegrationGroup.INCIDENT
- );
+ const isIncidentProviderIntegrationEnabled = true;
const canShowMTRData =
!isNoDataAvailable && isIncidentProviderIntegrationEnabled;
diff --git a/web-server/src/content/DoraMetrics/DoraCards/WeeklyDeliveryVolumeCard.tsx b/web-server/src/content/DoraMetrics/DoraCards/WeeklyDeliveryVolumeCard.tsx
index 80e3ccf4a..7836f7d4d 100644
--- a/web-server/src/content/DoraMetrics/DoraCards/WeeklyDeliveryVolumeCard.tsx
+++ b/web-server/src/content/DoraMetrics/DoraCards/WeeklyDeliveryVolumeCard.tsx
@@ -19,6 +19,7 @@ import {
} from '@/hooks/useStateTeamConfig';
import { useSelector } from '@/store';
import { IntegrationGroup } from '@/types/resources';
+import { merge } from '@/utils/datatype';
import { getSortedDatesAsArrayFromMap } from '@/utils/date';
import { useAvgWeeklyDeploymentFrequency } from './sharedHooks';
@@ -56,15 +57,16 @@ export const WeeklyDeliveryVolumeCard = () => {
const { integrationSet } = useAuth();
const dateRangeLabel = useCurrentDateRangeLabel();
const deploymentFrequencyProps = useAvgWeeklyDeploymentFrequency();
- const deploymentsConfigured = useSelector(
- (s) => s.doraMetrics.deploymentsConfigured
- );
+ const deploymentsConfigured = true;
const isCodeProviderIntegrationEnabled = integrationSet.has(
IntegrationGroup.CODE
);
- const weekDeliveryVolumeData = useSelector(
- (s) => s.doraMetrics.metrics_summary?.deployment_frequency_trends || {}
+ const weekDeliveryVolumeData = useSelector((s) =>
+ merge(
+ s.doraMetrics.metrics_summary?.deployment_frequency_trends.current,
+ s.doraMetrics.metrics_summary?.deployment_frequency_trends.previous
+ )
);
const totalDeployments = useSelector(
diff --git a/web-server/src/content/DoraMetrics/DoraCards/sharedHooks.tsx b/web-server/src/content/DoraMetrics/DoraCards/sharedHooks.tsx
index 8ea4b06a0..e447f8df1 100644
--- a/web-server/src/content/DoraMetrics/DoraCards/sharedHooks.tsx
+++ b/web-server/src/content/DoraMetrics/DoraCards/sharedHooks.tsx
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo } from 'react';
+import { Row } from '@/constants/db';
import {
ChangeTimeThresholds,
updatedDeploymentFrequencyThresholds
@@ -20,18 +21,18 @@ export const useMeanTimeToRestoreProps = () => {
const meanTimeToRestore = useSelector(
(s) =>
s.doraMetrics.metrics_summary?.mean_time_to_restore_stats.current
- .time_to_restore_average
+ .mean_time_to_recovery
);
const currAvgTimeToRestore = useSelector(
(s) =>
s.doraMetrics.metrics_summary?.mean_time_to_restore_stats.current
- .time_to_restore_average || 0
+ .mean_time_to_recovery || 0
);
const prevAvgTimeToRestore = useSelector(
(s) =>
s.doraMetrics.metrics_summary?.mean_time_to_restore_stats.previous
- .time_to_restore_average || 0
+ .mean_time_to_recovery || 0
);
const incidents = useSelector(
@@ -78,7 +79,7 @@ export const useMeanTimeToRestoreProps = () => {
export const useLeadTimeProps = () => {
const leadTime = useSelector(
- (s) => s.doraMetrics.metrics_summary?.lead_time_stats.current_average
+ (s) => s.doraMetrics.metrics_summary?.lead_time_stats.current.lead_time
);
return useMemo(() => {
@@ -107,9 +108,7 @@ export const useLeadTimeProps = () => {
export const useDoraStats = () => {
const { integrationSet } = useAuth();
const leadTimeProps = useLeadTimeProps();
- const depsConfigured = useSelector(
- (s) => s.doraMetrics.deploymentsConfigured
- );
+ const depsConfigured = true;
const { count: df } = useAvgWeeklyDeploymentFrequency();
const { count: cfr } = useChangeFailureRateProps();
const { count: mttr, isNoDataAvailable } = useMeanTimeToRestoreProps();
@@ -136,12 +135,10 @@ export const usePropsForChangeTimeCard = () => {
const allAssignedRepos = useSelector(
(s) => s.doraMetrics.allReposAssignedToTeam
);
- const reposWithWorkflowConfigured = useSelector(
- (s) => s.doraMetrics.workflowConfiguredRepos
- );
const prevLeadTime = useSelector(
- (s) => s.doraMetrics.metrics_summary?.lead_time_stats.previous_average || 0
+ (s) =>
+ s.doraMetrics.metrics_summary?.lead_time_stats.previous.lead_time || 0
);
const [currLeadTimeTrendsData, prevLeadTimeTrendsData] = useSelector((s) => [
@@ -171,26 +168,17 @@ export const usePropsForChangeTimeCard = () => {
const activeModeCurrentTrendsData = currLeadTimeTrendsData;
- const isAllAssignedReposHaveDeploymentsConfigured = useSelector(
- (s) =>
- s.doraMetrics.allReposAssignedToTeam.length ===
- s.doraMetrics.workflowConfiguredRepos.length
- );
+ const isAllAssignedReposHaveDeploymentsConfigured = true;
- const reposWithNoDeploymentsConfigured = useMemo(() => {
- const workflowConfiguredRepoIdsSet = new Set(
- reposWithWorkflowConfigured.map((r) => r.id)
- );
- return allAssignedRepos.filter(
- (r) => !workflowConfiguredRepoIdsSet.has(r.id)
- );
- }, [allAssignedRepos, reposWithWorkflowConfigured]);
+ const reposWithNoDeploymentsConfigured = [] as (Row<'TeamRepos'> &
+ Row<'OrgRepo'>)[];
const isShowingLeadTime = true;
const isShowingCycleTime = false;
const reposCountWithWorkflowConfigured =
- allAssignedRepos.length - reposWithNoDeploymentsConfigured.length;
+ Number(allAssignedRepos?.length) -
+ Number(reposWithNoDeploymentsConfigured?.length);
const isActiveModeSwitchDisabled = false;
@@ -220,18 +208,15 @@ export const useAvgWeeklyDeploymentFrequency = () => {
let avgDeploymentFrequency = useSelector(
(s) =>
s.doraMetrics.metrics_summary?.deployment_frequency_stats.current
- .avg_deployment_frequency || 0
+ .avg_daily_deployment_frequency || 0
);
let prevAvgDeploymentFrequency = useSelector(
(s) =>
s.doraMetrics.metrics_summary?.deployment_frequency_stats.previous
- .avg_deployment_frequency || 0
+ .avg_daily_deployment_frequency || 0
);
- const interval = useSelector(
- (s) =>
- s.doraMetrics.metrics_summary?.deployment_frequency_stats.current.duration
- );
+ const interval = 'week';
const metricInterval = useMemo(() => {
return {
diff --git a/web-server/src/content/DoraMetrics/DoraMetricsBody.tsx b/web-server/src/content/DoraMetrics/DoraMetricsBody.tsx
index a26852912..f2eacea67 100644
--- a/web-server/src/content/DoraMetrics/DoraMetricsBody.tsx
+++ b/web-server/src/content/DoraMetrics/DoraMetricsBody.tsx
@@ -50,29 +50,26 @@ export const DoraMetricsBody = () => {
!s.doraMetrics.metrics_summary?.change_failure_rate_stats.current
.change_failure_rate &&
!s.doraMetrics.metrics_summary?.mean_time_to_restore_stats.current
- .time_to_restore_average &&
- !s.doraMetrics.metrics_summary?.lead_time_stats.current_average &&
+ .incident_count &&
+ !s.doraMetrics.metrics_summary?.lead_time_stats.current.lead_time &&
!s.doraMetrics.metrics_summary?.deployment_frequency_stats.current
- .avg_deployment_frequency
+ .avg_daily_deployment_frequency
);
useEffect(() => {
if (!singleTeamId) return;
dispatch(
fetchTeamDoraMetrics({
- org_id: orgId,
- team_id: singleTeamId,
- from_date: dates.start,
- to_date: dates.end,
+ orgId,
+ teamId: singleTeamId,
+ fromDate: dates.start,
+ toDate: dates.end,
branches:
activeBranchMode === ActiveBranchMode.PROD
? null
: activeBranchMode === ActiveBranchMode.ALL
? '^'
- : branches,
- manager_teams_array: [
- { team_ids: [singleTeamId], manager_id: team?.manager_id || null }
- ]
+ : branches
})
);
}, [
diff --git a/web-server/src/hooks/useDoraMetricsGraph/index.tsx b/web-server/src/hooks/useDoraMetricsGraph/index.tsx
index 5fbdc999c..91243900e 100644
--- a/web-server/src/hooks/useDoraMetricsGraph/index.tsx
+++ b/web-server/src/hooks/useDoraMetricsGraph/index.tsx
@@ -1,10 +1,9 @@
import { lighten, rgbToHex } from '@mui/material';
import { useMemo } from 'react';
-import { getTrendsDataFromArray } from '@/content/Cockpit/codeMetrics/shared';
import { useSelector } from '@/store';
import { brandColors } from '@/theme/schemes/theme';
-import { mergeDateValueTupleArray } from '@/utils/array';
+import { merge } from '@/utils/datatype';
import { getSortedDatesAsArrayFromMap } from '@/utils/date';
export const useDoraMetricsGraph = () => {
@@ -12,86 +11,83 @@ export const useDoraMetricsGraph = () => {
(s) => s.doraMetrics.metrics_summary?.lead_time_trends
);
- const meanTimeToRestoreTrends = useSelector(
- (s) => s.doraMetrics.metrics_summary?.mean_time_to_restore_trends
- );
- const changeFailureRateTrends = useSelector(
- (s) => s.doraMetrics.metrics_summary?.change_failure_rate_trends
- );
+ const meanTimeToRestoreTrends = useSelector((s) => ({
+ ...s.doraMetrics.metrics_summary?.mean_time_to_restore_trends.current,
+ ...s.doraMetrics.metrics_summary?.mean_time_to_restore_trends.previous
+ }));
+
+ const changeFailureRateTrends = useSelector((s) => ({
+ ...s.doraMetrics.metrics_summary?.change_failure_rate_trends.current,
+ ...s.doraMetrics.metrics_summary?.change_failure_rate_trends.previous
+ }));
const activeTrends = leadTimeTrends;
+ const mergedLeadTimeTrends = merge(
+ leadTimeTrends?.current,
+ leadTimeTrends?.previous
+ );
if (!activeTrends) return { trendsSeriesMap: null, yAxisLabels: [] };
const yAxisLabels = useMemo(() => {
- const sprintLabels =
- mergeDateValueTupleArray(
- activeTrends.previous?.breakdown.first_response_time,
- activeTrends.current?.breakdown.first_response_time
- ).map((s) => s[0]) || [];
- return sprintLabels;
+ return getSortedDatesAsArrayFromMap({
+ ...activeTrends.previous,
+ ...activeTrends.current
+ });
}, [activeTrends]);
const firstCommitToOpenTrendsData = useMemo(
() =>
- getTrendsDataFromArray(
- mergeDateValueTupleArray(
- leadTimeTrends.previous?.breakdown.first_commit_to_open,
- leadTimeTrends.current?.breakdown.first_commit_to_open
- )
- ),
- [leadTimeTrends]
+ getSortedDatesAsArrayFromMap(mergedLeadTimeTrends).map((key) => ({
+ x: key,
+ y: mergedLeadTimeTrends[key].first_commit_to_open ?? 0
+ })),
+ [mergedLeadTimeTrends]
);
const firstResponseTimeTrendsData = useMemo(
() =>
- getTrendsDataFromArray(
- mergeDateValueTupleArray(
- activeTrends.previous?.breakdown.first_response_time,
- activeTrends.current?.breakdown.first_response_time
- )
- ),
- [activeTrends]
+ getSortedDatesAsArrayFromMap(mergedLeadTimeTrends).map((key) => ({
+ x: key,
+ y: mergedLeadTimeTrends[key].first_response_time ?? 0
+ })),
+ [mergedLeadTimeTrends]
);
const reworkTimeTrendsData = useMemo(
() =>
- getTrendsDataFromArray(
- mergeDateValueTupleArray(
- activeTrends.previous?.breakdown.rework_time,
- activeTrends.current?.breakdown.rework_time
- )
- ),
- [activeTrends]
+ getSortedDatesAsArrayFromMap(mergedLeadTimeTrends).map((key) => ({
+ x: key,
+ y: mergedLeadTimeTrends[key].rework_time ?? 0
+ })),
+ [mergedLeadTimeTrends]
);
const mergeTimeTrendsData = useMemo(
() =>
- getTrendsDataFromArray(
- mergeDateValueTupleArray(
- activeTrends.previous?.breakdown.merge_time,
- activeTrends.current?.breakdown.merge_time
- )
- ),
- [activeTrends]
+ getSortedDatesAsArrayFromMap(mergedLeadTimeTrends).map((key) => ({
+ x: key,
+ y: mergedLeadTimeTrends[key].merge_time ?? 0
+ })),
+ [mergedLeadTimeTrends]
);
const deployTimeTrendsData = useMemo(
() =>
- getTrendsDataFromArray(
- mergeDateValueTupleArray(
- leadTimeTrends.previous?.breakdown.merge_time,
- leadTimeTrends.current?.breakdown.merge_time
- )
- ),
- [leadTimeTrends]
+ getSortedDatesAsArrayFromMap(mergedLeadTimeTrends).map((key) => ({
+ x: key,
+ y: mergedLeadTimeTrends[key].merge_to_deploy ?? 0
+ })),
+ [mergedLeadTimeTrends]
);
const changeFailureRateTrendsData = useMemo(
() =>
getSortedDatesAsArrayFromMap(changeFailureRateTrends).map((key) => ({
x: key,
- y: Number(changeFailureRateTrends[key].percentage.toFixed(2) ?? 0)
+ y: Number(
+ changeFailureRateTrends[key].change_failure_rate?.toFixed(2) ?? 0
+ )
})),
[changeFailureRateTrends]
);
@@ -100,7 +96,7 @@ export const useDoraMetricsGraph = () => {
() =>
getSortedDatesAsArrayFromMap(meanTimeToRestoreTrends).map((key) => ({
x: key,
- y: meanTimeToRestoreTrends[key] ?? 0
+ y: meanTimeToRestoreTrends[key].mean_time_to_recovery ?? 0
})),
[meanTimeToRestoreTrends]
);
@@ -147,28 +143,17 @@ export const useDoraMetricsGraph = () => {
y: point || 0
}))
},
- totalCycleTimeTrends: {
- id: `Total Cycle Time`,
- color: rgbToHex(lighten(brandColors.ticketState.todo, 0.5)),
- data: firstResponseTimeTrendsData.map((point, index) => ({
- x: yAxisLabels[index],
- y:
- (point || 0) +
- (reworkTimeTrendsData[index] || 0) +
- (mergeTimeTrendsData[index] || 0)
- }))
- },
totalLeadTimeTrends: {
id: `Total Lead Time`,
color: rgbToHex(lighten(brandColors.ticketState.todo, 0.5)),
data: firstCommitToOpenTrendsData.map((point, index) => ({
x: yAxisLabels[index],
y:
- (point || 0) +
- (firstResponseTimeTrendsData[index] || 0) +
- (reworkTimeTrendsData[index] || 0) +
- (mergeTimeTrendsData[index] || 0) +
- (deployTimeTrendsData[index] || 0)
+ (point?.y || 0) +
+ (firstResponseTimeTrendsData[index]?.y || 0) +
+ (reworkTimeTrendsData[index]?.y || 0) +
+ (mergeTimeTrendsData[index]?.y || 0) +
+ (deployTimeTrendsData[index]?.y || 0)
}))
},
meanTimeToRestoreTrends: [
diff --git a/web-server/src/slices/dora_metrics.ts b/web-server/src/slices/dora_metrics.ts
index 386481542..e81170878 100644
--- a/web-server/src/slices/dora_metrics.ts
+++ b/web-server/src/slices/dora_metrics.ts
@@ -3,6 +3,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
import { omit } from 'ramda';
import { handleApi } from '@/api-helpers/axios-api-instance';
+import { Row } from '@/constants/db';
import { StateFetchConfig } from '@/types/redux';
import {
Deployment,
@@ -10,10 +11,7 @@ import {
TeamDeploymentsApiResponse,
DeploymentWithIncidents,
IncidentsWithDeploymentResponseType,
- RepoWithSingleWorkflow,
- ManagerTeamsMap,
RepoFilterConfig,
- TeamDeploymentsConfigured,
IncidentApiResponseType,
ChangeTimeModes
} from '@/types/resources';
@@ -31,10 +29,7 @@ export type State = StateFetchConfig<{
| 'deploymentsConfiguredForAllRepos'
| 'deploymentsConfigured'
>;
- allReposAssignedToTeam: RepoWithSingleWorkflow[];
- workflowConfiguredRepos: RepoWithSingleWorkflow[];
- deploymentsConfigured: TeamDeploymentsConfigured['deployments_configured'];
- deploymentsConfiguredForAllRepos: TeamDeploymentsConfigured['deployments_configured_for_all_repos'];
+ allReposAssignedToTeam: (Row<'TeamRepos'> & Row<'OrgRepo'>)[];
all_deployments: DeploymentWithIncidents[];
resolved_incidents: IncidentsWithDeploymentResponseType[];
team_deployments: TeamDeploymentsApiResponse;
@@ -49,9 +44,6 @@ const initialState: State = {
activeChangeTimeMode: ChangeTimeModes.CYCLE_TIME,
metrics_summary: null,
allReposAssignedToTeam: [],
- workflowConfiguredRepos: [],
- deploymentsConfigured: false,
- deploymentsConfiguredForAllRepos: false,
all_deployments: [],
resolved_incidents: [],
team_deployments: {
@@ -96,11 +88,7 @@ export const doraMetricsSlice = createSlice({
],
action.payload
);
- state.allReposAssignedToTeam = action.payload.allReposAssignedToTeam;
- state.workflowConfiguredRepos = action.payload.workflowConfiguredRepos;
- state.deploymentsConfigured = action.payload.deploymentsConfigured;
- state.deploymentsConfiguredForAllRepos =
- action.payload.deploymentsConfiguredForAllRepos;
+ state.allReposAssignedToTeam = action.payload.assigned_repos;
}
);
addFetchCasesToReducer(
@@ -138,18 +126,20 @@ type DoraMetricsApiParamsType = {
export const fetchTeamDoraMetrics = createAsyncThunk(
'dora_metrics/fetchTeamDoraMetrics',
- async (
- params: DoraMetricsApiParamsType & {
- org_id: ID;
- manager_teams_array: ManagerTeamsMap[];
- }
- ) => {
+ async (params: {
+ teamId: ID;
+ orgId: ID;
+ fromDate: Date;
+ toDate: Date;
+ branches: string;
+ }) => {
return await handleApi(
- `internal/team/${params.team_id}/dora_metrics`,
+ `internal/team/${params.teamId}/dora_metrics`,
{
params: {
- ...params,
- manager_teams_array: JSON.stringify(params.manager_teams_array)
+ org_id: params.orgId,
+ from_date: params.fromDate,
+ to_date: params.toDate
}
}
);
diff --git a/web-server/src/types/resources.ts b/web-server/src/types/resources.ts
index 9e03fbf5a..27ce0d44e 100644
--- a/web-server/src/types/resources.ts
+++ b/web-server/src/types/resources.ts
@@ -538,11 +538,7 @@ export type ChangeFailureRateApiResponse = {
export type ChangeFailureRateTrendsApiResponse = Record<
DateString,
- {
- percentage: number;
- failed_deployments: number;
- total_deployments: number;
- }
+ ChangeFailureRateApiResponse
>;
export type TeamDoraMetricsApiResponseType = {
@@ -579,6 +575,7 @@ export type TeamDoraMetricsApiResponseType = {
previous: DeploymentFrequencyTrends;
};
lead_time_prs: PR[];
+ assigned_repos: (Row<'TeamRepos'> & Row<'OrgRepo'>)[];
};
export enum ActiveBranchMode {