config-ui/src/plugins/register/tapd/transformation.tsx (266 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import { useEffect, useState } from 'react'; import { FormGroup, Intent, Tag } from '@blueprintjs/core'; import { HelpTooltip, MultiSelector, PageLoading } from '@/components'; import { useProxyPrefix, useRefreshData } from '@/hooks'; import * as API from './api'; import * as S from './styled'; import { uniqWith } from 'lodash'; enum StandardType { Requirement = 'Requirement', Bug = 'BUG', Incident = 'INCIDENT', } enum StandardStatus { Todo = 'TODO', InProgress = 'IN-PROGRESS', Done = 'DONE', } interface Props { entities: string[]; connectionId: ID; scopeId: ID; transformation: any; setTransformation: React.Dispatch<React.SetStateAction<any>>; } export const TapdTransformation = ({ entities, connectionId, scopeId, transformation, setTransformation }: Props) => { const [featureTypeList, setFeatureTypeList] = useState<string[]>([]); const [bugTypeList, setBugTypeList] = useState<string[]>([]); const [incidentTypeList, setIncidentTypeList] = useState<string[]>([]); const [todoStatusList, setTodoStatusList] = useState<string[]>([]); const [inProgressStatusList, setInProgressStatusList] = useState<string[]>([]); const [doneStatusList, setDoneStatusList] = useState<string[]>([]); const prefix = useProxyPrefix({ plugin: 'tapd', connectionId }); const { ready, data } = useRefreshData<{ statusList: Array<{ id: string; name: string; }>; typeList: Array<{ id: string; name: string; }>; }>(async () => { if (!prefix) { return { statusList: [], typeList: [], }; } const [storyType, bugType, taskType, storyStatus, bugStatus, taskStatus] = await Promise.all([ API.getStoryType(prefix, scopeId), { BUG: 'bug' } as Record<string, string>, { TASK: 'task' } as Record<string, string>, API.getStatus(prefix, scopeId, 'story'), API.getStatus(prefix, scopeId, 'bug'), { open: 'task-open', progressing: 'task-progressing', done: 'task-done' } as Record<string, string>, ]); const statusList: { id: string; name: string }[] = uniqWith( [ { id: 'open', name: taskStatus.open }, { id: 'progressing', name: taskStatus.progressing }, { id: 'done', name: taskStatus.done }, ...(Object.values(storyStatus.data) as string[]).map((it) => ({ id: it, name: it })), ...(Object.values(bugStatus.data) as string[]).map((it) => ({ id: it, name: it })), ], (a, b) => a.id === b.id, ); const typeList: { id: string; name: string }[] = [ ...storyType.data.map((it: any) => ({ id: it.Category.id, name: it.Category.name })), { id: 'BUG', name: bugType['BUG'] }, { id: 'TASK', name: taskType['TASK'] }, ]; return { statusList, typeList, }; }, [prefix]); useEffect(() => { const typeList = Object.entries(transformation.typeMappings ?? {}).map(([key, value]: any) => ({ key, value })); setFeatureTypeList(typeList.filter((it) => it.value === StandardType.Requirement).map((it) => it.key)); setBugTypeList(typeList.filter((it) => it.value === StandardType.Bug).map((it) => it.key)); setIncidentTypeList(typeList.filter((it) => it.value === StandardType.Incident).map((it) => it.key)); const statusList = Object.entries(transformation.statusMappings ?? {}).map(([key, value]: any) => ({ key, value })); setTodoStatusList(statusList.filter((it) => it.value === StandardStatus.Todo).map((it) => it.key)); setInProgressStatusList(statusList.filter((it) => it.value === StandardStatus.InProgress).map((it) => it.key)); setDoneStatusList(statusList.filter((it) => it.value === StandardStatus.Done).map((it) => it.key)); }, [transformation]); if (!ready || !data) { return <PageLoading />; } const { statusList, typeList } = data; const transformaType = (its: string[], standardType: string) => { return its.reduce((acc, cur) => { acc[cur] = standardType; return acc; }, {} as Record<string, string>); }; return ( <S.TransformationWrapper> {entities.includes('TICKET') && ( <div className="issue-tracking"> <h2>Issue Tracking</h2> <div className="issue-type"> <div className="title"> <span>Issue Type Mapping</span> <HelpTooltip content="Standardize your issue types to the following issue types to view metrics such as `Requirement lead time` and `Bug age` in built-in dashboards." /> </div> <div className="list"> <FormGroup inline label="Requirement"> <MultiSelector items={typeList} disabledItems={typeList.filter((v) => [...bugTypeList, ...incidentTypeList].includes(v.id))} getKey={(it) => it.id} getName={(it) => it.name} selectedItems={typeList.filter((v) => featureTypeList.includes(v.id))} onChangeItems={(selectedItems) => setTransformation({ ...transformation, typeMappings: { ...transformaType( selectedItems.map((v) => v.id), StandardType.Requirement, ), ...transformaType(bugTypeList, StandardType.Bug), ...transformaType(incidentTypeList, StandardType.Incident), }, }) } /> </FormGroup> <FormGroup inline label="Bug"> <MultiSelector items={typeList} disabledItems={typeList.filter((v) => [...featureTypeList, ...incidentTypeList].includes(v.id))} getKey={(it) => it.id} getName={(it) => it.name} selectedItems={typeList.filter((v) => bugTypeList.includes(v.id))} onChangeItems={(selectedItems) => setTransformation({ ...transformation, typeMappings: { ...transformaType(featureTypeList, StandardType.Requirement), ...transformaType( selectedItems.map((v) => v.id), StandardType.Bug, ), ...transformaType(incidentTypeList, StandardType.Incident), }, }) } /> </FormGroup> <FormGroup inline label={ <> <span>Incident</span> <Tag intent={Intent.PRIMARY} style={{ marginLeft: 4 }}> DORA </Tag> </> } > <MultiSelector items={typeList} disabledItems={typeList.filter((v) => [...featureTypeList, ...bugTypeList].includes(v.id))} getKey={(it) => it.id} getName={(it) => it.name} selectedItems={typeList.filter((v) => incidentTypeList.includes(v.id))} onChangeItems={(selectedItems) => setTransformation({ ...transformation, typeMappings: { ...transformaType(featureTypeList, StandardType.Requirement), ...transformaType(bugTypeList, StandardType.Bug), ...transformaType( selectedItems.map((v) => v.id), StandardType.Incident, ), }, }) } /> </FormGroup> </div> </div> <div className="issue-status"> <div className="title"> <span>Issue Status Mapping</span> <HelpTooltip content="Standardize your issue statuses to the following issue statuses to view metrics such as `Requirement Delivery Rate` in built-in dashboards." /> </div> <div className="list"> <FormGroup inline label="TODO"> <MultiSelector items={statusList} disabledItems={statusList.filter((v) => [...inProgressStatusList, ...doneStatusList].includes(v.name), )} getKey={(it) => it.id} getName={(it) => it.name} selectedItems={statusList.filter((v) => todoStatusList.includes(v.name))} onChangeItems={(selectedItems) => setTransformation({ ...transformation, statusMappings: { ...transformaType( selectedItems.map((v) => v.name), StandardStatus.Todo, ), ...transformaType(inProgressStatusList, StandardStatus.InProgress), ...transformaType(doneStatusList, StandardStatus.Done), }, }) } /> </FormGroup> <FormGroup inline label="IN-PROGRESS"> <MultiSelector items={statusList} disabledItems={statusList.filter((v) => [...todoStatusList, ...doneStatusList].includes(v.name))} getKey={(it) => it.id} getName={(it) => it.name} selectedItems={statusList.filter((v) => inProgressStatusList.includes(v.name))} onChangeItems={(selectedItems) => setTransformation({ ...transformation, statusMappings: { ...transformaType(todoStatusList, StandardStatus.Todo), ...transformaType( selectedItems.map((v) => v.name), StandardStatus.InProgress, ), ...transformaType(doneStatusList, StandardStatus.Done), }, }) } /> </FormGroup> <FormGroup inline label="DONE"> <MultiSelector items={statusList} disabledItems={statusList.filter((v) => [...todoStatusList, ...inProgressStatusList].includes(v.name), )} getKey={(it) => it.id} getName={(it) => it.name} selectedItems={statusList.filter((v) => doneStatusList.includes(v.name))} onChangeItems={(selectedItems) => setTransformation({ ...transformation, statusMappings: { ...transformaType(todoStatusList, StandardStatus.Todo), ...transformaType(inProgressStatusList, StandardStatus.InProgress), ...transformaType( selectedItems.map((v) => v.name), StandardStatus.Done, ), }, }) } /> </FormGroup> </div> </div> </div> )} </S.TransformationWrapper> ); };