diff --git a/apps/web/i18n b/apps/web/i18n index 2e5dac97..1754fbe3 160000 --- a/apps/web/i18n +++ b/apps/web/i18n @@ -1 +1 @@ -Subproject commit 2e5dac97f7f003afece8f4adda419cc524e52a96 +Subproject commit 1754fbe3465ea5ee3cc1433d9137a3f810d4da5c diff --git a/apps/web/src/common/components/Table/Download.tsx b/apps/web/src/common/components/Table/Download.tsx index 9e5e2c1f..225b6407 100644 --- a/apps/web/src/common/components/Table/Download.tsx +++ b/apps/web/src/common/components/Table/Download.tsx @@ -1,12 +1,68 @@ import React, { useState } from 'react'; -import { useTranslation } from 'next-i18next'; import { BsDownload } from 'react-icons/bs'; import { AiOutlineLoading } from 'react-icons/ai'; +import { useRequest } from 'ahooks'; +import { + apiDownloadFiles, + Status, +} from '@modules/analyze/DataView/MetricDetail/tableDownload'; -const Download = ({ downloadFun }: { downloadFun: () => void }) => { - const { t } = useTranslation(); - const [loadingDownLoad, setLoadingDownLoad] = useState(false); +// 对象驼峰转下划线 +const objectHumpToLine = (obj) => { + var newObj = new Object(); + for (let key in obj) { + newObj[key.replace(/([A-Z])/g, '_$1').toLowerCase()] = obj[key]; + } + return newObj; +}; +const Download = ({ + beginFun, + pollingFun, + query, + fileName, +}: { + beginFun: (query) => Promise; + pollingFun: (uuid) => Promise; + query: any; + fileName: string; +}) => { + const newQuery = objectHumpToLine(query); + newQuery['sort_opts'] && (newQuery['sort_opts'] = [newQuery['sort_opts']]); + const [loadingDownLoad, setLoadingDownLoad] = useState(false); + const downloadFinish = () => setLoadingDownLoad(false); + const { run, cancel } = useRequest(pollingFun, { + pollingInterval: 3000, + pollingWhenHidden: false, + manual: true, + onSuccess: ({ data }, params) => { + if (data.status === Status.COMPLETE && data.downdload_path) { + apiDownloadFiles(data.downdload_path, fileName, downloadFinish); + cancel(); + } else if (data.status === Status.UNKNOWN) { + downloadFinish(); + cancel(); + throw new Error('Download Error'); + } + }, + }); + const downloadFun = async () => { + try { + const { data } = await beginFun(newQuery); + if (data.code === 200 && data.status === Status.PENDING) { + setTimeout(() => { + run(data.uuid); + }, 2000); + } else if (data.status === Status.COMPLETE && data.downdload_path) { + apiDownloadFiles(data.downdload_path, fileName, downloadFinish); + } else { + throw new Error('Download Error'); + } + } catch (e) { + console.log(e); + downloadFinish(); + } + }; return ( <>
@@ -18,7 +74,6 @@ const Download = ({ downloadFun }: { downloadFun: () => void }) => { onClick={async () => { setLoadingDownLoad(true); await downloadFun(); - setLoadingDownLoad(false); }} > diff --git a/apps/web/src/common/components/Table/index.tsx b/apps/web/src/common/components/Table/index.tsx index 7fd73f23..d735046e 100644 --- a/apps/web/src/common/components/Table/index.tsx +++ b/apps/web/src/common/components/Table/index.tsx @@ -22,7 +22,7 @@ const MyTable = (props) => { (i % 2 === 1 ? '!bg-[#fafafa]' : '')} + // rowClassName={(_record, i) => (i % 2 === 1 ? '!bg-[#fafafa]' : '')} // bordered /> diff --git a/apps/web/src/common/options/gradientRamp.ts b/apps/web/src/common/options/gradientRamp.ts index 0caf5f25..291b4ccd 100644 --- a/apps/web/src/common/options/gradientRamp.ts +++ b/apps/web/src/common/options/gradientRamp.ts @@ -115,7 +115,6 @@ export const gradientRamp = [ '#f8a8d3', '#fccfea', ], - [ '#8c90a1', '#787a8c', diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/ContributorContribution.tsx b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/ContributorContribution.tsx index 64630c57..a3fca750 100644 --- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/ContributorContribution.tsx +++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/ContributorContribution.tsx @@ -96,9 +96,8 @@ const getSeriesFun = (data, onlyIdentity, onlyOrg, getEcologicalText) => { ); let distribution = list.flatMap((i) => i.topContributorDistribution); distribution.sort((a, b) => b.subCount - a.subCount); - const name = getEcologicalText(item); - - const colorList = gradientRamp[i]; + const { name, index } = getEcologicalText(item); + const colorList = gradientRamp[index]; let count = 0; let otherCount = 0; if (item === 'organization') { @@ -155,8 +154,8 @@ const getSeriesFun = (data, onlyIdentity, onlyOrg, getEcologicalText) => { const ecoContributorsOverview = data.ecoContributorsOverview; ecoContributorsOverview.forEach((item, i) => { const { subTypeName, topContributorDistribution } = item; - const name = getEcologicalText(subTypeName); - const colorList = gradientRamp[i]; + const { name, index } = getEcologicalText(subTypeName); + const colorList = gradientRamp[index]; let count = 0; topContributorDistribution.forEach(({ subCount, subName }, index) => { count += subCount; @@ -321,8 +320,8 @@ const OrgContributorContribution: React.FC<{ const orgContributionDistribution = data.orgContributionDistribution; orgContributionDistribution.forEach((item, i) => { const { subTypeName, topContributorDistribution } = item; - const name = getEcologicalText(subTypeName); - const colorList = gradientRamp[i]; + const { name, index } = getEcologicalText(subTypeName); + const colorList = gradientRamp[index]; let count = 0; topContributorDistribution.forEach(({ subCount, subName }, index) => { count += subCount; diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/ContributorTable.tsx b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/ContributorTable.tsx index 220038a5..7465d97a 100644 --- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/ContributorTable.tsx +++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/ContributorTable.tsx @@ -13,12 +13,13 @@ import { useEcologicalType, useMileageOptions, } from './contribution'; +import { Tag } from 'antd'; import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'; import type { FilterValue, SorterResult } from 'antd/es/table/interface'; import { useTranslation } from 'next-i18next'; import Tooltip from '@common/components/Tooltip'; import Download from '@common/components/Table/Download'; -import { contributorDownload } from '../tableDownload'; +import { getContributorPolling, getContributorExport } from '../tableDownload'; import { useRouter } from 'next/router'; import { useHandleQueryParams } from '@modules/analyze/hooks/useHandleQueryParams'; interface TableParams { @@ -58,8 +59,7 @@ const MetricTable: React.FC<{ const defaultFilterOpts = queryFilterOpts ? JSON.parse(queryFilterOpts) : []; const defaultSortOpts = router.query?.sortOpts ? JSON.parse(router.query?.sortOpts as string) - : []; - console.log(1); + : null; const [filterOpts, setFilterOpts] = useState(defaultFilterOpts || []); const filterContributionType = useMemo(() => { return filterOpts.find((i) => i.type === 'contribution_type'); @@ -133,7 +133,6 @@ const MetricTable: React.FC<{ ) => { let sortOpts = null; let filterOpts = []; - console.log(filters); for (const key in filters) { if (filters.hasOwnProperty(key)) { const transformedObj = { @@ -208,28 +207,32 @@ const MetricTable: React.FC<{ let arr = list.map( (item) => contributionTypeMap[item.contributionType] ); - const length = arr.join(', ')?.length; - const str = arr.map((obj, index) => ( - - {arr.length - 1 == index ? obj.text : obj.text + ', '} - - )); - return ( - 27 ? str : ''} placement="right"> -
- {str} + let sortObj = arr.reduce((result, item) => { + (result[item.color] = result[item.color] || []).push(item); + return result; + }, {}); + let newArr = Object.keys(sortObj).sort(); + const str = newArr.map((item) => { + return ( +
+ {sortObj[item]?.map((obj, index) => ( + + {obj.text} + + ))}
- - ); + ); + }); + return str; }, filters: useContributionTypeLsit(), defaultFilteredValue: defaultFilterOpts.find((i) => i.type === 'contribution_type')?.values || null, filterMode: 'tree', - ellipsis: true, + // ellipsis: { showTitle: true }, align: 'left', - width: '250px', + width: '590px', }, { title: t('analyze:metric_detail:organization'), @@ -253,7 +256,7 @@ const MetricTable: React.FC<{ } }, align: 'left', - width: '200px', + width: '120px', sorter: true, }, ]; @@ -261,12 +264,10 @@ const MetricTable: React.FC<{ <>
- contributorDownload( - query, - t('analyze:metric_detail:contributor_data_table') - ) - } + beginFun={getContributorExport} + pollingFun={getContributorPolling} + query={query} + fileName={t('analyze:metric_detail:contributor_data_table')} />
{ ); let distribution = list.flatMap((i) => i.topContributorDistribution); distribution.sort((a, b) => b.subCount - a.subCount); - const name = getEcologicalText(item); - - const colorList = gradientRamp[i]; + const { name, index } = getEcologicalText(item); + const colorList = gradientRamp[index]; let count = 0; let otherCount = 0; if (item === 'organization') { @@ -48,17 +47,17 @@ const getSeriesFun = (data, onlyIdentity, onlyOrg, getEcologicalText) => { }, []); } - distribution.forEach((z, index) => { + distribution.forEach((z, y) => { const { subCount, subName } = z; count += subCount; - if (subName == 'other' || index > 10) { + if (subName == 'other' || y > 10) { otherCount += subCount; } else { contributorsData.push({ parentName: name, name: subName, value: subCount, - itemStyle: { color: colorList[index + 1] }, + itemStyle: { color: colorList[y + 1] }, }); } }); @@ -86,8 +85,8 @@ const getSeriesFun = (data, onlyIdentity, onlyOrg, getEcologicalText) => { const orgContributorsDistribution = data.orgContributorsDistribution; orgContributorsDistribution.forEach((item, i) => { const { subTypeName, topContributorDistribution } = item; - const name = getEcologicalText(subTypeName); - const colorList = gradientRamp[i]; + const { name, index } = getEcologicalText(subTypeName); + const colorList = gradientRamp[index]; let count = 0; topContributorDistribution.forEach(({ subCount, subName }, index) => { count += subCount; @@ -140,40 +139,6 @@ const ContributorContributors: React.FC<{ }); const getSeries = useMemo(() => { - // if (data?.orgContributorsDistribution?.length > 0) { - // const orgContributorsDistribution = data.orgContributorsDistribution; - // orgContributorsDistribution.forEach((item, i) => { - // const { subTypeName, topContributorDistribution } = item; - // const name = getEcologicalText(subTypeName); - // const colorList = gradientRamp[i]; - // let count = 0; - // topContributorDistribution.forEach(({ subCount, subName }, index) => { - // count += subCount; - // contributorsData.push({ - // parentName: name, - // name: subName, - // value: subCount, - // itemStyle: { color: colorList[index + 1] }, - // }); - // }); - // legend.push({ - // name: name, - // itemStyle: { color: colorList[0] }, - // }); - // allCount += count; - // ecoData.push({ - // name: name, - // value: count, - // itemStyle: { color: colorList[0] }, - // }); - // }); - // } - // return { - // legend, - // allCount, - // ecoData, - // contributorsData, - // }; return getSeriesFun(data, onlyIdentity, onlyOrg, getEcologicalText); }, [data, onlyIdentity, onlyOrg, getEcologicalText]); const unit: string = t('analyze:metric_detail:contributor_unit'); diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/contribution.ts b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/contribution.ts index ec07f9eb..679a6c61 100644 --- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/contribution.ts +++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/contribution.ts @@ -139,24 +139,19 @@ export const useContributionTypeLsit = () => { export const useGetContributionTypeI18n = () => { const obj = useContributionTypeMap(); const result = {}; - const colors = [ - '#007ACC', // 蓝色 - '#008000', // 绿色 - '#FFA500', // 橙色 - '#FF1493', // 粉红 - '#800080', // 紫色 - ]; + const colors = ['cyan', 'green', 'geekblue', 'purple', 'orange']; + const defaultColors = 'volcano'; function traverseObject(obj, color) { for (const key in obj) { if (typeof obj[key] === 'object') { - const c = colors.shift() || '#ccc'; + const c = colors.shift() || defaultColors; traverseObject(obj[key], c); } else { result[key] = { text: obj[key], color }; } } } - traverseObject(obj, '#ccc'); + traverseObject(obj, defaultColors); return result; }; @@ -176,14 +171,14 @@ export const useEcologicalType = () => { const { t } = useTranslation(); return [ - { - text: t('analyze:metric_detail:organization_participant'), - value: 'organization participant', - }, { text: t('analyze:metric_detail:organization_manager'), value: 'organization manager', }, + { + text: t('analyze:metric_detail:organization_participant'), + value: 'organization participant', + }, { text: t('analyze:metric_detail:individual_manager'), value: 'individual manager', @@ -207,20 +202,22 @@ export const useGetEcologicalText = () => { text: t('analyze:metric_detail:individual'), value: 'individual', }, - { - text: t('analyze:metric_detail:participant'), - value: 'participant', - }, + { text: t('analyze:metric_detail:manager'), value: 'manager', }, + { + text: t('analyze:metric_detail:participant'), + value: 'participant', + }, ]; + const options = [...ecologicalOptions, ...otherOptions]; return (text) => { - return ( - ecologicalOptions.find((i) => i.value === text)?.text || - otherOptions.find((i) => i.value === text)?.text || - text - ); + const index = options.findIndex((i) => i.value === text); + return { + name: options[index]?.text || text, + index, + }; }; }; diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/IssueTable.tsx b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/IssueTable.tsx index fa5660eb..c57ac6d1 100644 --- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/IssueTable.tsx +++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/IssueTable.tsx @@ -14,7 +14,8 @@ import { format, parseJSON } from 'date-fns'; import { useStateType } from './issue'; import { toUnderline } from '@common/utils/format'; import Download from '@common/components/Table/Download'; -import { issueDownload } from '../tableDownload'; +import { getIssuePolling, getIssueExport } from '../tableDownload'; + interface TableParams { pagination?: TablePaginationConfig; filterOpts?: FilterOptionInput[]; @@ -183,11 +184,12 @@ const MetricTable: React.FC<{ ]; return ( <> -
+
- issueDownload(query, t('analyze:metric_detail:issue_data_table')) - } + beginFun={getIssueExport} + pollingFun={getIssuePolling} + query={query} + fileName={t('analyze:metric_detail:issue_data_table')} />
-
+
- prDownload(query, t('analyze:metric_detail:pr_data_table')) - } + beginFun={getPrExport} + pollingFun={getPrPolling} + query={query} + fileName={t('analyze:metric_detail:pr_data_table')} />
{
{ - const query = window.location.search; - router.push('/analyze/' + slugs + query); + // const query = window.location.search; + router.push('/analyze/' + slugs); }} className="mr-4 cursor-pointer text-[#3f60ef]" /> diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/tableDownload.ts b/apps/web/src/modules/analyze/DataView/MetricDetail/tableDownload.ts index 23eb2715..bcd8cab6 100644 --- a/apps/web/src/modules/analyze/DataView/MetricDetail/tableDownload.ts +++ b/apps/web/src/modules/analyze/DataView/MetricDetail/tableDownload.ts @@ -1,54 +1,48 @@ import axios from 'axios'; -const apiDownloadFiles = (res, fileName) => { - if (res.data.type === 'application/json') { - // this.$message({ - // type: 'error', - // message: '下载失败,文件不存在或权限不足', - // }); - } else { - let blob = new Blob([res.data]); - let link = document.createElement('a'); - link.href = URL.createObjectURL(blob); - link.download = fileName + '.csv'; - link.style.display = 'none'; - document.body.appendChild(link); - link.click(); - window.URL.revokeObjectURL(link.href); - document.body.removeChild(link); - } +export enum Status { + PENDING = 'pending', + PROGRESS = 'progress', + COMPLETE = 'complete', + UNKNOWN = 'unknown', +} +export const apiDownloadFiles = (path, fileName, onFinish) => { + let link = document.createElement('a'); + link.href = path; + link.download = fileName + '.csv'; + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + onFinish && onFinish(); + document.body.removeChild(link); }; - -export const contributorDownload = (query, fileName) => { - return axios - .post('/api/v1/contributor/export', query, { - headers: { - accept: 'application/json', - }, - }) - .then((res) => { - apiDownloadFiles(res, fileName); - }); +export const getContributorPolling = (uuid) => { + return axios.get('/api/v1/contributor/export_state/' + uuid); +}; +export const getContributorExport = (query) => { + return axios.post('/api/v1/contributor/export', query, { + headers: { + accept: 'application/json', + }, + }); +}; +export const getIssuePolling = (uuid) => { + return axios.get('/api/v1/issue/export_state/' + uuid); +}; +export const getIssueExport = (query) => { + return axios.post('/api/v1/issue/export', query, { + headers: { + accept: 'application/json', + }, + }); }; -export const issueDownload = (query, fileName) => { - return axios - .post('/api/v1/contributor/export', query, { - headers: { - accept: 'application/json', - }, - }) - .then((res) => { - apiDownloadFiles(res, fileName); - }); +export const getPrPolling = (uuid) => { + return axios.get('/api/v1/pull/export_state/' + uuid); }; -export const prDownload = (query, fileName) => { - return axios - .post('/api/v1/contributor/export', query, { - headers: { - accept: 'application/json', - }, - }) - .then((res) => { - apiDownloadFiles(res, fileName); - }); +export const getPrExport = (query) => { + return axios.post('/api/v1/pull/export', query, { + headers: { + accept: 'application/json', + }, + }); }; diff --git a/apps/web/src/modules/home/Banner/index.tsx b/apps/web/src/modules/home/Banner/index.tsx index f4a594e1..e00075c3 100644 --- a/apps/web/src/modules/home/Banner/index.tsx +++ b/apps/web/src/modules/home/Banner/index.tsx @@ -7,7 +7,7 @@ import Image from 'next/image'; const Carousel = () => { return ( -
+
{ const SectionBanner = () => { return (
-
+
diff --git a/apps/web/src/modules/settings/profile/ProfileForm.tsx b/apps/web/src/modules/settings/profile/ProfileForm.tsx index 9d137f00..88f856df 100644 --- a/apps/web/src/modules/settings/profile/ProfileForm.tsx +++ b/apps/web/src/modules/settings/profile/ProfileForm.tsx @@ -163,7 +163,7 @@ const ProfileForm = () => {
-
+
{t('setting:profile.language_preferences')}