Skip to content
16 changes: 11 additions & 5 deletions src/common/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,13 +543,19 @@ export interface components {
* @description Returns a list of use case with the number of bugs
*/
WidgetBugsByUseCase: {
data: (components['schemas']['UseCase'] & {
/** @description Unique bugs */
data: {
title: {
full: string;
simple?: string;
prefix?: string;
info?: string;
};
description: string;
uniqueBugs?: number;
bugs: number;
usecase_id: number;
/** Format: float */
usecase_completion?: number;
})[];
usecase_id: number;
}[];
/**
* @default bugsByUseCase
* @example bugsByUseCase
Expand Down
14 changes: 11 additions & 3 deletions src/features/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,11 +675,19 @@ export type Report = {
update_date?: string;
};
export type WidgetBugsByUseCase = {
data: (UseCase & {
data: {
title: {
full: string;
simple?: string;
prefix?: string;
info?: string;
};
description: string;
uniqueBugs?: number;
bugs: number;
usecase_id: number;
usecase_completion?: number;
})[];
usecase_id: number;
}[];
kind: 'bugsByUseCase';
};
export type WidgetBugsByDevice = {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_CARD_TITLE": "Unique Bugs by Usecase",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_COLUMN_LEFT": "Use case",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_COLUMN_RIGHT": "Bugs/Tot.",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_LIST_HEADER": "total bugs",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_LIST_CONTENT": "total bugs",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_LIST_HEADER": "Total",
"__CAMPAIGN_PAGE_WIDGET_PROGRESS_CARD_TITLE": "Stato avanzamento",
"__CAMPAIGN_PAGE_WIDGET_PROGRESS_CARD_TOOLTIP": "This widget shows the progress of the campaign's tasks",
"__CAMPAIGN_PAGE_WIDGET_PROGRESS_DESCRIPTION_FOOTER": "over <1>{{expectedDuration}} expected</1>",
Expand Down
3 changes: 2 additions & 1 deletion src/locales/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_CARD_TITLE": "Bug unici per Usecase",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_COLUMN_LEFT": "Use Case",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_COLUMN_RIGHT": "Bugs / Total",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_LIST_HEADER": "bug totali",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_LIST_CONTENT": "bug totali",
"__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_LIST_HEADER": "Totale",
"__CAMPAIGN_PAGE_WIDGET_PROGRESS_CARD_TITLE": "Stato avanzamento",
"__CAMPAIGN_PAGE_WIDGET_PROGRESS_CARD_TOOLTIP": "Tieni sotto controllo gli use case completati dai tester, quanto tempo è passato dall’inizio della campagna e quanto manca alla fine.",
"__CAMPAIGN_PAGE_WIDGET_PROGRESS_DESCRIPTION_FOOTER": "su un totale di <1>{{expectedDuration}}</1>",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,30 @@
import { PieChart } from '@appquality/unguess-design-system';
import { PieChart, Skeleton, theme } from '@appquality/unguess-design-system';
import { BugsByUseCaseVisualizationProps } from './types';
import { useBugsByUsecase } from './useBugsByUsecase';
import { useMaxItems } from './useMaxItems';

const pieChartProps = {
width: '100%',
height: '270px',
data: [
{
id: 'sass',
label: 'sass',
value: 309,
color: 'hsl(162, 70%, 50%)',
},
{
id: 'make',
label: 'make',
value: 420,
color: 'hsl(175, 70%, 50%)',
},
{
id: 'erlang',
label: 'erlang',
value: 300,
color: 'hsl(159, 70%, 50%)',
},
{
id: 'lisp',
label: 'lisp',
value: 491,
color: 'hsl(243, 70%, 50%)',
},
{
id: 'go',
label: 'go',
value: 108,
color: 'hsl(163, 70%, 50%)',
},
],
centerItem: {
label: 'Tot. bugs',
value: '27',
},
export const ChartUniqueBugs4UseCase = ({
campaignId,
}: BugsByUseCaseVisualizationProps) => {
const { items, total, isLoading, isError } = useBugsByUsecase(campaignId);
const newItems = useMaxItems(items, 6);

if (isLoading || isError) {
return <Skeleton />;
}
return (
<div style={{ marginBottom: theme.space.lg }}>
<PieChart
legend={{
width: '100%',
columns: 2,
}}
width="100%"
height="270px"
centerItem={{ label: 'Tot. Bugs', value: total.toString() }}
data={newItems}
theme={{ labels: { text: { fontSize: 10 } } }}
/>
</div>
);
};
export const ChartUniqueBugs4UseCase = () => (
<div>
<PieChart {...pieChartProps} />
</div>
);
Original file line number Diff line number Diff line change
@@ -1,56 +1,45 @@
import { useEffect, useState } from 'react';
import { Skeleton, XL } from '@appquality/unguess-design-system';
import { useEffect, useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetCampaignsByCidWidgetsQuery } from 'src/features/api';
import { List } from '../List';
import { ListItem } from '../List/ListItem';
import { ListItemProps } from '../List/type';
import { BugsByUseCaseVisualizationProps } from './types';
import { useBugsByUsecase } from './useBugsByUsecase';

export const ListUniqueBugs4UseCase = () => {
export const ListUniqueBugs4UseCase = ({
campaignId,
}: BugsByUseCaseVisualizationProps) => {
const { t } = useTranslation();
const { data } = useGetCampaignsByCidWidgetsQuery({
cid: 3044,
s: 'bugs-by-usecase',
});

const [total, setTotal] = useState(0);
const [items, setItems] = useState<ListItemProps[]>([]);

useEffect(() => {
if (data && 'kind' in data && data.kind === 'bugsByUseCase') {
const newTotal = data.data.reduce(
(acc, current) => acc + current.bugs,
0
);
const currentItems = data.data.map((item) => ({
key: item.usecase_id,
children: item.title,
numerator: item.bugs,
denominator: newTotal,
}));
setItems(currentItems);
setTotal(newTotal);
}
}, [data]);

const [currentPage, setCurrentPage] = useState<number>(1);
const { items, total, isLoading, isError } = useBugsByUsecase(campaignId);
const [currentPage, setCurrentPage] = useState(1);
const [paginatedItems, setPaginatedItems] = useState(items);

const pageSize = 6;

const maxPages = Math.ceil(items.length / pageSize);
const maxPages = useMemo(
() => Math.ceil(items.length / pageSize),
[items, pageSize]
);

useEffect(() => {
setPaginatedItems(
items.slice((currentPage - 1) * pageSize, currentPage * pageSize)
);
}, [currentPage, items]);

if (isLoading || isError) {
return <Skeleton />;
}

return (
<List
header={t('__CAMPAIGN_WIDGET_UNIQUE_BUGS_BY_USECASE_HEADER_LABEL')}
title={`${total} ${t(
'__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_LIST_HEADER'
)}`}
header={t('__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_LIST_HEADER')}
title={
<>
{total}{' '}
<XL tag="span" isBold>
{t('__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_LIST_CONTENT')}
</XL>
</>
}
>
<List.Columns>
<List.Columns.Label isBold>
Expand Down
9 changes: 7 additions & 2 deletions src/pages/Campaign/widgets/UniqueBugs4UseCase/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import FlipCard from '../widgetCards/FlipCard';
import { ChartUniqueBugs4UseCase } from './ChartUniqueBugs4UseCase';
import { ListUniqueBugs4UseCase } from './ListUniqueBugs4UseCase';

const UniqueBugs4UseCase = ({ contentHeight }: { contentHeight: string }) => {
const { t } = useTranslation();
const { campaignId } = useParams();
if (!campaignId) {
return null;
}
return (
<FlipCard>
<FlipCard.Header>
{t('__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE_CARD_TITLE')}
</FlipCard.Header>
<FlipCard.Body
height={contentHeight}
front={<ChartUniqueBugs4UseCase />}
back={<ListUniqueBugs4UseCase />}
front={<ChartUniqueBugs4UseCase campaignId={campaignId} />}
back={<ListUniqueBugs4UseCase campaignId={campaignId} />}
/>
</FlipCard>
);
Expand Down
16 changes: 16 additions & 0 deletions src/pages/Campaign/widgets/UniqueBugs4UseCase/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface BugsByUseCaseVisualizationProps {
campaignId: string;
}
interface PieDatum {
[key: string]: string | number;
}

export interface WidgetItem extends PieDatum {
id: string;
label: string;
value: number;
key: number;
children: string;
numerator: number;
denominator: number;
}
76 changes: 76 additions & 0 deletions src/pages/Campaign/widgets/UniqueBugs4UseCase/useBugsByUsecase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useState, useEffect } from 'react';
import {
useGetCampaignsByCidWidgetsQuery,
WidgetBugsByUseCase,
} from 'src/features/api';

import { WidgetItem } from './types';

function abbreviateUsecase(string: string): string {
return string
.toLowerCase()
.replace(/use case /, 'UC')
.replace(/caso d'uso /, 'UC');
}

function getSimpleTitle(title: WidgetBugsByUseCase['data'][0]['title']) {
if (title.simple) {
return title.simple.toLowerCase();
}
return title.full
.replace(/\[(.*?)\]/, '')
.replace(/use case\s?[0-9]*:*/i, '')
.replace(/caso d'uso\s?[0-9]*:*/i, '')
.toLowerCase();
}
function getArcLinkLabel(title: WidgetBugsByUseCase['data'][0]['title']) {
return abbreviateUsecase(title.full);
}

function getLegendLabel(title: WidgetBugsByUseCase['data'][0]['title']) {
if (title.prefix && title.simple) {
return `${title.prefix} ${title.simple}`;
}
return getSimpleTitle(title);
}

export const useBugsByUsecase = (campaignId: string) => {
const { data, isFetching, isLoading, isError } =
useGetCampaignsByCidWidgetsQuery({
cid: parseInt(campaignId, 10),
s: 'bugs-by-usecase',
});
const [total, setTotal] = useState(0);
const [items, setItems] = useState<WidgetItem[]>([]);

useEffect(() => {
if (data && 'kind' in data && data.kind === 'bugsByUseCase') {
const currentTotal = data.data.reduce(
(acc, current) => acc + current.bugs,
0
);
setTotal(currentTotal);
setItems(
data.data.map((item) => ({
id: getArcLinkLabel(item.title),
label: getLegendLabel(item.title),
value: item.bugs,
key: item.usecase_id,
children: item.title.full,
numerator: item.bugs,
denominator: currentTotal,
}))
);
return;
}
setTotal(0);
setItems([]);
}, [data]);

return {
total,
items,
isLoading: isLoading || isFetching || !data,
isError,
};
};
32 changes: 32 additions & 0 deletions src/pages/Campaign/widgets/UniqueBugs4UseCase/useMaxItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useTranslation } from 'react-i18next';
import { WidgetItem } from './types';

interface Datum {
id: string;
value: number;
[key: string]: string | number | undefined;
}

export const useMaxItems = (items: WidgetItem[], maxItems = 6): Datum[] => {
const { t } = useTranslation();
items.sort((x, y) => {
if (x.value < y.value) {
return 1;
}
if (x.value > y.value) {
return -1;
}
return 0;
});
const exceding = items.slice(maxItems - 1, items.length);
const excedingValue = exceding.reduce((acc, curr) => acc + curr.value, 0);
const excedingLabel = t('__CAMPAIGN_PAGE_WIDGET_BUGS_BY_USECASE', 'others');
const excedingData = {
id: excedingLabel,
label: excedingLabel,
value: excedingValue,
};
const newItems: Datum[] = items.slice(0, maxItems - 1);
newItems.push(excedingData);
return newItems;
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ const WidgetCardFaceContent = styled.div`

background-color: white;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;

&.face-enter {
opacity: 0;
Expand Down