From 6c1b41717de9b3eb57ba142812db0aca45dfa4d9 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 11:12:24 +0800 Subject: [PATCH 01/16] refactor: migrate application menu to tabs --- .../configurable-filter/config-selector.tsx | 7 +- .../components/configurable-filter/index.tsx | 16 +- .../common/components/erda-alert/index.tsx | 2 +- .../components/markdown-render/index.tsx | 1 - shell/app/common/components/menu/index.tsx | 6 +- .../common/components/radio-tabs/index.tsx | 23 +- shell/app/common/utils/go-to.tsx | 10 +- shell/app/locales/en.json | 9 +- shell/app/locales/zh.json | 11 +- shell/app/menus/application.tsx | 119 ----- shell/app/menus/index.ts | 3 +- shell/app/modules/application/entry.js | 2 - .../application/pages/problem/index.tsx | 114 ----- .../pages/problem/problem-detail.scss | 30 +- .../pages/problem/problem-detail.tsx | 471 +++++++++--------- .../pages/problem/problem-form.tsx | 33 +- .../pages/problem/problem-list.tsx | 225 ++++++--- .../application/pages/quality/entry.tsx | 45 ++ .../application/pages/quality/index.tsx | 4 +- .../application/pages/repo/repo-branch.tsx | 43 +- .../application/pages/repo/repo-mr.tsx | 55 +- .../pages/test/test-detail-container.tsx | 110 ++-- .../application/pages/test/test-detail.tsx | 160 +++--- .../application/pages/test/test-list.tsx | 8 +- shell/app/modules/application/router.ts | 231 +++++---- .../modules/application/services/problem.ts | 85 ++-- .../application/stores/application.tsx | 34 +- .../app/modules/application/stores/problem.ts | 135 ----- shell/app/modules/application/stores/repo.ts | 6 +- shell/app/modules/application/stores/test.ts | 45 +- shell/app/modules/application/tabs.tsx | 188 +++++++ 31 files changed, 1101 insertions(+), 1130 deletions(-) delete mode 100644 shell/app/menus/application.tsx delete mode 100644 shell/app/modules/application/pages/problem/index.tsx create mode 100644 shell/app/modules/application/pages/quality/entry.tsx delete mode 100644 shell/app/modules/application/stores/problem.ts create mode 100644 shell/app/modules/application/tabs.tsx diff --git a/shell/app/common/components/configurable-filter/config-selector.tsx b/shell/app/common/components/configurable-filter/config-selector.tsx index 88a3827e3c..f8cf6b2dbc 100644 --- a/shell/app/common/components/configurable-filter/config-selector.tsx +++ b/shell/app/common/components/configurable-filter/config-selector.tsx @@ -22,9 +22,8 @@ export interface IProps { list: ConfigData[]; value?: number | string; isNew: boolean; - defaultValue: number | string; - onDeleteFilter: (config: ConfigData) => void; - onSaveFilter: (label: string) => void; + defaultValue?: number | string; + onDeleteFilter?: (config: ConfigData) => void; onChange: (config: ConfigData) => void; className?: string; } @@ -50,7 +49,7 @@ const ConfigSelector = ({ className = '', list, defaultValue, value, onChange, o zIndex: 9999, getContainer: configSelectorRef.current, onOk() { - onDeleteFilter(item); + onDeleteFilter?.(item); }, }); }; diff --git a/shell/app/common/components/configurable-filter/index.tsx b/shell/app/common/components/configurable-filter/index.tsx index 55a48faa82..10b066236a 100644 --- a/shell/app/common/components/configurable-filter/index.tsx +++ b/shell/app/common/components/configurable-filter/index.tsx @@ -24,11 +24,11 @@ import './index.scss'; export interface IProps { fieldsList: Field[]; - configList: ConfigData[]; - defaultConfig: number | string; + configList?: ConfigData[]; + defaultConfig?: number | string; onFilter: (data: Obj) => void; - onDeleteFilter: (data: Obj) => void; - onSaveFilter: (label: string, values: Obj) => void; + onDeleteFilter?: (data: Obj) => void; + onSaveFilter?: (label: string, values: Obj) => void; value?: Obj; onConfigChange?: (config: ConfigData) => void; processField?: (field: Field) => IFormItem; @@ -46,11 +46,11 @@ export interface Field { key: string; mode?: string; outside?: boolean; - value: number | string; + value?: number | string; label: string; children?: Field[]; placeholder?: string; - options: Option[]; + options?: Option[]; haveFilter?: boolean; required?: boolean; emptyText?: string; @@ -152,7 +152,7 @@ const convertValue = (value: Obj, fieldList: Field[]) => { const ConfigurableFilter = ({ fieldsList, - configList, + configList = [], defaultConfig, value, onFilter: onFilterProps, @@ -212,7 +212,7 @@ const ConfigurableFilter = ({ }; const saveFilter = (label: string) => { - onSaveFilter(label, form.getFieldsValue()); + onSaveFilter?.(label, form.getFieldsValue()); }; const setAllOpen = () => { diff --git a/shell/app/common/components/erda-alert/index.tsx b/shell/app/common/components/erda-alert/index.tsx index d5dd462628..306dd90421 100644 --- a/shell/app/common/components/erda-alert/index.tsx +++ b/shell/app/common/components/erda-alert/index.tsx @@ -24,7 +24,7 @@ interface IProps { className?: string; type?: AlertProps['type']; theme?: 'light' | 'dark'; - closeable: boolean; + closeable?: boolean; } const iconMap = { diff --git a/shell/app/common/components/markdown-render/index.tsx b/shell/app/common/components/markdown-render/index.tsx index 13379dbc9a..e7aadc48ca 100644 --- a/shell/app/common/components/markdown-render/index.tsx +++ b/shell/app/common/components/markdown-render/index.tsx @@ -21,7 +21,6 @@ import { gfm, gfmHtml } from 'micromark-extension-gfm'; import remarkGfm from 'remark-gfm'; import remarkBreaks from 'remark-breaks'; import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { CodeComponent } from 'react-markdown/lib/ast-to-react'; import github from 'react-syntax-highlighter/dist/esm/styles/hljs/googlecode.js'; import './index.scss'; diff --git a/shell/app/common/components/menu/index.tsx b/shell/app/common/components/menu/index.tsx index 1714968d61..6964e9240c 100644 --- a/shell/app/common/components/menu/index.tsx +++ b/shell/app/common/components/menu/index.tsx @@ -127,10 +127,8 @@ const PureMenu = (props: IMenu) => { key={key} className={menuItemClass} onClick={() => { - if (!disabled && activeKey !== key) { - // 点击当前,不响应 - handleClick(activeKey, key, hrefType); - } + // trigger even click the same key, for back from child route + handleClick(activeKey, key, hrefType); }} > {name} diff --git a/shell/app/common/components/radio-tabs/index.tsx b/shell/app/common/components/radio-tabs/index.tsx index fc631cb4dd..8d90fcbcb7 100644 --- a/shell/app/common/components/radio-tabs/index.tsx +++ b/shell/app/common/components/radio-tabs/index.tsx @@ -24,10 +24,10 @@ export interface RadioTabsProps { className?: string; options: IOption[]; value?: Value; - onChange?: (v: Value) => void; + onChange?: (v: Value, o: IOption) => void; } -type Value = string | number | undefined; +type Value = string | number; interface IOption { label: string | React.ReactElement; @@ -51,7 +51,8 @@ const RadioTabs = (props: RadioTabsProps) => { propsValue && updater.value((prev: string) => (prev !== propsValue ? propsValue : prev)); }, [propsValue, updater]); - const convertValue = (val: Value) => { + const convertValue = (val?: Value) => { + if (val === undefined) return val; if (options.find((o) => o.value === val)) { return val; } else { @@ -65,13 +66,13 @@ const RadioTabs = (props: RadioTabsProps) => { } }; - const valueHandle = (v: Value) => { + const valueHandle = (v: Value): [Value, IOption] => { let curVal = v; - const curOption = options.find((o) => o.value === curVal); + const curOption = options.find((o) => o.value === curVal) as IOption; if (curOption?.children?.length) { curVal = subValues[curOption.value]; } - return curVal; + return [curVal, curOption]; }; return ( { size="middle" value={convertValue(value)} onChange={(e) => { - const curVal = valueHandle(e.target.value); + const [curVal, curOption] = valueHandle(e.target.value); updater.value(curVal); - onChange?.(curVal); + onChange?.(curVal, curOption); }} > {options.map((mItem) => { @@ -91,7 +92,7 @@ const RadioTabs = (props: RadioTabsProps) => { if (isArray(children) && children.length) { const sv = subValues[itemValue] || children[0].value; - const childName = children.find((c) => c.value === sv); + const child = children.find((c) => c.value === sv); const getMenu = () => { return ( { value: e.key, subValues: { ...subValues, [itemValue]: e.key }, }); - onChange?.(e.key); + onChange?.(e.key, child); }} > {children.map((g) => { @@ -119,7 +120,7 @@ const RadioTabs = (props: RadioTabsProps) => {
{icon ? : null} - {childName?.label} + {child?.label}
diff --git a/shell/app/common/utils/go-to.tsx b/shell/app/common/utils/go-to.tsx index e2e0c0aa2b..96b83a4aed 100644 --- a/shell/app/common/utils/go-to.tsx +++ b/shell/app/common/utils/go-to.tsx @@ -194,21 +194,21 @@ export enum pages { repoBackup = '/{orgName}/dop/projects/{projectId}/apps/{appId}/repo/backup', commit = '/{orgName}/dop/projects/{projectId}/apps/{appId}/repo/commit/{commitId}', branches = '/{orgName}/dop/projects/{projectId}/apps/{appId}/repo/branches', - tags = '/{orgName}/dop/projects/{projectId}/apps/{appId}/repo/tags', commits = '/{orgName}/dop/projects/{projectId}/apps/{appId}/repo/commits/{branch}/{path}', pipeline = '/{orgName}/dop/projects/{projectId}/apps/{appId}/pipeline?caseId={caseId}&pipelineID={pipelineID}', dataTask = '/{orgName}/dop/projects/{projectId}/apps/{appId}/dataTask/{pipelineID}', dataTaskRoot = '/{orgName}/dop/projects/{projectId}/apps/{appId}/dataTask', + // TODO: remove deploy = '/{orgName}/dop/projects/{projectId}/apps/{appId}/deploy', - qaTicket = '/{orgName}/dop/projects/{projectId}/apps/{appId}/ticket/open?type={type}', release = '/{orgName}/dop/projects/{projectId}/apps/{appId}/release?q={q}', + // TODO: remove runtimeDetail = '/{orgName}/dop/projects/{projectId}/apps/{appId}/deploy/runtimes/{runtimeId}/overview?serviceName={serviceName}&jumpFrom={jumpFrom}', runtimeDetailRoot = '/{orgName}/dop/projects/{projectId}/apps/{appId}/deploy/runtimes/{runtimeId}/overview', appDataModel = '/{orgName}/dop/projects/{projectId}/apps/{appId}/dataModel', appDataMarket = '/{orgName}/dop/projects/{projectId}/apps/{appId}/dataMarket', - appCodeQuality = '/{orgName}/dop/projects/{projectId}/apps/{appId}/test', - appCodeQualityReports = '/{orgName}/dop/projects/{projectId}/apps/{appId}/test/quality', - appCodeQualityIssueOpen = '/{orgName}/dop/projects/{projectId}/apps/{appId}/ticket/open', + appCodeQuality = '/{orgName}/dop/projects/{projectId}/apps/{appId}/quality', + appCodeQualityReports = '/{orgName}/dop/projects/{projectId}/apps/{appId}/test', + appCodeQualityIssueOpen = '/{orgName}/dop/projects/{projectId}/apps/{appId}/ticket/open?type={type}', appCodeQualityIssue = '/{orgName}/dop/projects/{projectId}/apps/{appId}/ticket', appSetting = '/{orgName}/dop/projects/{projectId}/apps/{appId}/setting', diff --git a/shell/app/locales/en.json b/shell/app/locales/en.json index ecc646ed82..a5d527c046 100644 --- a/shell/app/locales/en.json +++ b/shell/app/locales/en.json @@ -959,7 +959,9 @@ "app release confirmation": "app release confirmation", "app repository address": "app repository address", "app types": "app types", + "app-issues": "Issues", "appMonitor": "appMonitor", + "application": "application", "application / service / instance name": "application / service / instance name", "application branch rule": "application branch rule", "application description": "application description", @@ -1030,6 +1032,7 @@ "branch rule": "branch rule", "branch to commit": "branch to commit", "branch-config-tip": "The platform has strict relationship between branches and the environment. While the system has built-in best practice configuration, it supports users to customize the configuration", + "branches": "branches", "bug description": "bug description", "build cancelled": "build cancelled", "build created successfully": "build created successfully", @@ -1100,7 +1103,6 @@ "code-trigger-CI": "Continuous integration is triggered when the branch code changes", "collapse file": "collapse file", "command": "command", - "comment area": "comment area", "commented at": "commented at", "commit ID": "commit ID", "commit branch": "commit branch", @@ -1109,6 +1111,7 @@ "commit message": "commit message", "commit unchanged": "commit unchanged", "commitID-input-tip": "please enter 40 characters consisting of lowercase letters and numbers", + "commits": "commits", "committed": "opening", "compare": "compare", "comparison result has been folded": "comparison result has been folded", @@ -1235,7 +1238,6 @@ "edit test plan": "edit test plan", "efficiency measure": "efficiency measure", "email": "email", - "emergency level": "emergency level", "empty, not empty": "Empty, Not empty", "empty-markdown-content": "\n### readme\n\n\n*Here you can show your talents on documentation*\n\n*to let members know the background, goals and other information of the project via awesome graph and text*\n\n*to share project documents, specifications and other materials*\n\n*and also...*", "encrypt": "encrypt", @@ -1421,6 +1423,7 @@ "medium": "medium", "memory size": "memory size", "merge description": "merge description", + "merge request": "merge request", "merge request detail": "merge request detail", "merge request has errors": "merge request has errors", "merge requests": "merge requests", @@ -1821,6 +1824,7 @@ "system": "system", "system log": "system log", "system properties": "system properties", + "tags": "tags", "target branch": "target branch", "target branch is protected, you have no permission yet": "target branch is protected, you have no permission yet", "task commands": "task commands", @@ -2015,6 +2019,7 @@ "Provides one-stop service system observation, including service monitoring, tracing, dashboard, and alarm.": "Provides one-stop service system observation, including service monitoring, tracing, dashboard, and alarm.", "RPC call": "RPC call", "RPC transaction": "RPC transaction", + "RegisterCenter": "RegisterCenter", "The selected interface weight will be set to": "The selected interface weight will be set to", "URL path": "URL path", "URL path prefix": "URL path prefix", diff --git a/shell/app/locales/zh.json b/shell/app/locales/zh.json index bf4385cbf3..366cb251f8 100644 --- a/shell/app/locales/zh.json +++ b/shell/app/locales/zh.json @@ -959,7 +959,9 @@ "app release confirmation": "应用发布确认", "app repository address": "应用仓库地址", "app types": "应用类型", + "app-issues": "问题列表", "appMonitor": "应用监控", + "application": "应用", "application / service / instance name": "应用 / 服务 / 实例名", "application branch rule": "应用分支规则", "application description": "应用描述", @@ -1030,6 +1032,7 @@ "branch rule": "分支规则", "branch to commit": "提交分支", "branch-config-tip": "平台对于分支与环境有严格关系管理,系统内置最佳实践配置的同时,支持用户进行自定义编辑配置", + "branches": "分支", "bug description": "缺陷描述", "build cancelled": "构建已取消", "build created successfully": "创建构建成功", @@ -1084,7 +1087,7 @@ "click to expand": "点击展开", "clone application": "克隆应用", "cluster used by the environment": "环境所用的集群", - "code": "代码浏览", + "code": "代码", "code branch": "代码分支", "code content": "代码内容", "code coverage statistics": "代码覆盖率统计", @@ -1100,7 +1103,6 @@ "code-trigger-CI": "该分支代码发生变化时触发持续集成", "collapse file": "收起文件", "command": "命令", - "comment area": "评论区", "commented at": "评论于", "commit ID": "提交ID", "commit branch": "提交分支", @@ -1109,6 +1111,7 @@ "commit message": "提交信息", "commit unchanged": "提交不变", "commitID-input-tip": "请输入40位由小写字母和数字组成的字符", + "commits": "提交历史", "committed": "已提交", "compare": "比较", "comparison result has been folded": "对比结果已被收起", @@ -1235,7 +1238,6 @@ "edit test plan": "编辑测试计划", "efficiency measure": "效能度量", "email": "邮箱", - "emergency level": "紧急程度", "empty, not empty": "为空、不为空", "empty-markdown-content": "\n### readme\n\n\n*此处可以尽情发挥你文档小能手的才华*\n\n*可以通过一段酷炫的图文让项目成员都清楚项目的背景、目标等信息*\n\n*也可以共享项目文档、规范等材料*\n\n*还可以...*", "encrypt": "是否加密", @@ -1421,6 +1423,7 @@ "medium": "严重", "memory size": "内存数", "merge description": "合并描述", + "merge request": "合并请求", "merge request detail": "合并请求详情", "merge request has errors": "mr存在错误", "merge requests": "合并请求", @@ -1821,6 +1824,7 @@ "system": "系统", "system log": "系统日志", "system properties": "系统属性", + "tags": "标签", "target branch": "目标分支", "target branch is protected, you have no permission yet": "目标分支被保护,您暂无权限操作", "task commands": "任务指令", @@ -2015,6 +2019,7 @@ "Provides one-stop service system observation, including service monitoring, tracing, dashboard, and alarm.": "提供一站式的服务系统观测,包括服务监控、链路追踪、仪表盘和告警等功能。", "RPC call": "RPC 调用", "RPC transaction": "RPC事务", + "RegisterCenter": "注册中心", "The selected interface weight will be set to": "所选接口权重将置为", "URL path": "URL 路径", "URL path prefix": "URL 路径前缀", diff --git a/shell/app/menus/application.tsx b/shell/app/menus/application.tsx deleted file mode 100644 index 99fc0dd35c..0000000000 --- a/shell/app/menus/application.tsx +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2021 Terminus, Inc. -// -// This program is free software: you can use, redistribute, and/or modify -// it under the terms of the GNU Affero General Public License, version 3 -// or later ("AGPL"), as published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -import i18n from 'i18n'; -import { filter } from 'lodash'; -import { goTo } from 'common/utils'; -import permStore from 'user/stores/permission'; -import { ErdaIcon } from 'common'; -import { appMode } from 'application/common/config'; - -import React from 'react'; - -interface IMenuItem { - show?: boolean; - key: string; - href: string; - icon: string; - text: string; -} -export const getAppMenu = ({ appDetail }: { appDetail: IApplication }) => { - const { mode } = appDetail; - const perm = permStore.getState((s) => s.app); - const repo = { - show: perm.repo.read.pass, - key: 'repo', - href: goTo.resolve.repo(), // `/dop/projects/${projectId}/apps/${appId}/repo`, - icon: , - text: i18n.t('dop:files'), - subtitle: i18n.t('Code'), - }; - const pipeline = { - show: perm.pipeline.read.pass, - key: 'pipeline', - href: goTo.resolve.pipelineRoot(), // `/dop/projects/${projectId}/apps/${appId}/pipeline`, - icon: , - text: i18n.t('pipeline'), - subtitle: i18n.t('Pipeline'), - }; - const apiDesign = { - show: perm.apiDesign.read.pass, - key: 'apiDesign', - href: goTo.resolve.appApiDesign(), // `/dop/projects/${projectId}/apps/${appId}/apiDesign`, - icon: , - text: i18n.t('dop:API design'), - subtitle: 'API', - }; - - const deploy = { - show: perm.runtime.read.pass, - key: 'deploy', - href: goTo.resolve.deploy(), // `/dop/projects/${projectId}/apps/${appId}/deploy`, - icon: , - text: i18n.t('dop:Environments'), - subtitle: i18n.t('Deploy'), - }; - const dataTask = { - show: perm.dataTask.read.pass, - key: 'dataTask', - href: goTo.resolve.dataTaskRoot(), // `/dop/projects/${projectId}/apps/${appId}/dataTask`, - icon: , - text: `${i18n.t('dop:data task')}`, - subtitle: `${i18n.t('Task')}`, - }; - const dataModel = { - show: perm.dataModel.read.pass, - key: 'dataModel', - href: goTo.resolve.appDataModel(), // `/dop/projects/${projectId}/apps/${appId}/dataModel`, - icon: , - text: `${i18n.t('dop:data model')}`, - subtitle: `${i18n.t('Model')}`, - }; - const dataMarket = { - show: perm.dataMarket.read.pass, - key: 'dataMarket', - href: goTo.resolve.appDataMarket(), // `/dop/projects/${projectId}/apps/${appId}/dataMarket`, - icon: , - text: `${i18n.t('dop:data market')}`, - subtitle: `${i18n.t('Market')}`, - }; - const test = { - show: perm.codeQuality.read.pass, - key: 'test', - href: goTo.resolve.appCodeQuality(), // `/dop/projects/${projectId}/apps/${appId}/test`, - icon: , - text: i18n.t('dop:code quality'), - subtitle: i18n.t('Quality'), - }; - - const setting = { - show: perm.setting.read.pass, - key: 'setting', - href: goTo.resolve.appSetting(), // `/dop/projects/${projectId}/apps/${appId}/setting`, - icon: , - text: i18n.t('dop:application setting'), - subtitle: i18n.t('Setting'), - }; - - // const full = [repo, pipeline, deploy, dataTask, dataModel, dataMarket, test, analysis, setting]; - const modeMap = { - [appMode.SERVICE]: [repo, pipeline, apiDesign, deploy, test, setting], - [appMode.PROJECT_SERVICE]: [repo, pipeline, test, setting], - [appMode.MOBILE]: [repo, pipeline, apiDesign, deploy, test, setting], - [appMode.LIBRARY]: [repo, pipeline, apiDesign, deploy, test, setting], - [appMode.BIGDATA]: [repo, dataTask, dataModel, dataMarket, setting], - [appMode.ABILITY]: [deploy, test, setting], - }; - - return filter(modeMap[mode], (item: IMenuItem) => item.show !== false); -}; diff --git a/shell/app/menus/index.ts b/shell/app/menus/index.ts index 09f8b95086..f64a985825 100644 --- a/shell/app/menus/index.ts +++ b/shell/app/menus/index.ts @@ -16,14 +16,13 @@ import { getCmpMenu } from './cmp'; import { getDopMenu } from './dop'; import { getMspMenu } from './msp'; import { getProjectMenu } from './project'; -import { getAppMenu } from './application'; import { getEcpMenu } from './ecp'; import i18n from 'app/i18n'; import { produce } from 'immer'; import { filter, map } from 'lodash'; import { appList } from './appCenter'; -export { getProjectMenu, getAppMenu, getOrgCenterMenu, getCmpMenu, getDopMenu, getMspMenu, getEcpMenu }; +export { getProjectMenu, getOrgCenterMenu, getCmpMenu, getDopMenu, getMspMenu, getEcpMenu }; export const getAppCenterAppList = appList; diff --git a/shell/app/modules/application/entry.js b/shell/app/modules/application/entry.js index ee373d6c7f..2ca2edffb6 100644 --- a/shell/app/modules/application/entry.js +++ b/shell/app/modules/application/entry.js @@ -24,7 +24,6 @@ import qualityStore from './stores/quality'; import releaseStore from './stores/release'; import repoStore from './stores/repo'; import testStore from './stores/test'; -import problemStore from './stores/problem'; const entry = (registerModule) => { return registerModule({ @@ -43,7 +42,6 @@ const entry = (registerModule) => { releaseStore, repoStore, testStore, - problemStore, ], }); }; diff --git a/shell/app/modules/application/pages/problem/index.tsx b/shell/app/modules/application/pages/problem/index.tsx deleted file mode 100644 index 34b056ffd7..0000000000 --- a/shell/app/modules/application/pages/problem/index.tsx +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2021 Terminus, Inc. -// -// This program is free software: you can use, redistribute, and/or modify -// it under the terms of the GNU Affero General Public License, version 3 -// or later ("AGPL"), as published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -import React from 'react'; -import { Button } from 'antd'; -import { goTo } from 'common/utils'; -import { useSwitch, useMultiFilter } from 'common/use-hooks'; -import { ProblemList } from './problem-list'; -import { ProblemForm } from './problem-form'; -import i18n from 'i18n'; -import problemStore from '../../stores/problem'; -import routeInfoStore from 'core/stores/route'; -import { useUnmount } from 'react-use'; -import orgStore from 'app/org-home/stores/org'; - -export const problemTabs = () => { - const openTotal = problemStore.useStore((s) => s.openTotal); - return [ - { - key: 'all', - name: i18n.t('all'), - }, - { - key: 'open', - name: ( - - {i18n.t('dop:pending')} - {openTotal} - - ), - }, - { - key: 'closed', - name: i18n.t('closed'), - }, - ]; -}; - -interface IProps { - scope?: string; -} - -const Ticket = ({ scope }: IProps) => { - const [visible, openModal, closeModal] = useSwitch(false); - const [params] = routeInfoStore.useStore((s) => [s.params]); - const orgId = orgStore.getState((s) => s.currentOrg.id); - const { ticketType: tabKey } = params; - - const { addTicket } = problemStore.effects; - const { clearTicketList } = problemStore.reducers; - - const { getTicketList } = problemStore.effects; - - const multiFilterProps = useMultiFilter({ - getData: [getTicketList], - extraQueryFunc: (activeGroup) => ({ - targetID: scope === 'org' ? orgId : +params.appId, - targetType: scope === 'org' ? 'org' : 'application', - status: activeGroup === 'all' ? undefined : activeGroup, - }), - shareQuery: true, - multiGroupEnums: ['open', 'all', 'closed'], - groupKey: 'ticketType', - activeKeyInParam: true, - }); - - useUnmount(() => { - clearTicketList(); - }); - - const onOk = (payload: any) => { - openModal(); - addTicket({ - ...payload, - targetID: scope === 'org' ? String(orgId) : String(params.appId), - targetType: scope === 'org' ? 'org' : 'application', - status: tabKey, - }) - .then(() => { - if (tabKey === 'open') { - multiFilterProps.fetchDataWithQuery(1); - } else { - goTo('../open'); - } - }) - .finally(() => { - closeModal(); - }); - }; - - return ( -
-
- - -
- -
- ); -}; - -export default Ticket; diff --git a/shell/app/modules/application/pages/problem/problem-detail.scss b/shell/app/modules/application/pages/problem/problem-detail.scss index 4083dc7502..c91a8964e1 100644 --- a/shell/app/modules/application/pages/problem/problem-detail.scss +++ b/shell/app/modules/application/pages/problem/problem-detail.scss @@ -1,33 +1,5 @@ .comments-container { - .comments-section { - position: relative; - height: 22px; - margin: 16px 0; - color: $color-dark-2; - font-size: 14px; - line-height: 22px; - letter-spacing: 0; - text-align: center; - - .section-line { - position: absolute; - top: 11px; - left: 0; - display: block; - width: 100%; - height: 1px; - border-bottom: 1px dashed $color-dark-1; - } - - .comments-section-text { - position: relative; - z-index: 1; - padding: 0 8px; - background: $white; - } - } - - .selecter-item { + .selector-item { width: 200px; margin-right: 10px; } diff --git a/shell/app/modules/application/pages/problem/problem-detail.tsx b/shell/app/modules/application/pages/problem/problem-detail.tsx index 02561e88c5..b3188f7ad8 100644 --- a/shell/app/modules/application/pages/problem/problem-detail.tsx +++ b/shell/app/modules/application/pages/problem/problem-detail.tsx @@ -11,23 +11,24 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { ProblemPriority, getProblemType } from 'application/pages/problem/problem-form'; -import React from 'react'; -import { Button, Spin, Tabs, Select } from 'antd'; -import { isEmpty, map, toLower } from 'lodash'; -import problemStore from 'application/stores/problem'; -import { useMount } from 'react-use'; -import routeInfoStore from 'core/stores/route'; -import { useLoading } from 'core/stores/loading'; +import { Button, Drawer, Select, Spin, Tabs } from 'antd'; +import { getIssues as getProjectIssues } from 'app/modules/project/services/issue'; +import { getProjectList } from 'app/modules/project/services/project'; import { CommentBox } from 'application/common/components/comment-box'; +import { ProblemPriority, ProblemTypeOptions } from 'application/pages/problem/problem-form'; +import { closeTicket, createTicketComments, getTicketComments, getTicketDetail } from 'application/services/problem'; +import { Avatar, LoadMoreSelector, MarkdownRender } from 'common'; import MarkdownEditor from 'common/components/markdown-editor'; -import { LoadMoreSelector, Avatar, MarkdownRender } from 'common'; import { useUpdate } from 'common/use-hooks'; import { fromNow, goTo } from 'common/utils'; -import { getProjectList } from 'app/modules/project/services/project'; -import { getProjectIterations } from 'project/services/project-iteration'; -import { getIssues as getProjectIssues } from 'app/modules/project/services/issue'; +import routeInfoStore from 'core/stores/route'; +import { useUserMap } from 'core/stores/userMap'; import i18n from 'i18n'; +import { isEmpty, map, toLower } from 'lodash'; +import { getProjectIterations } from 'project/services/project-iteration'; +import React from 'react'; +import userStore from 'user/stores'; +import { getUserInfo } from './problem-list'; import './problem-detail.scss'; interface IProps { @@ -91,27 +92,43 @@ const { TabPane } = Tabs; const { Option } = Select; const initialState = { - activedProject: undefined, - activedIteration: undefined, - activedIssueType: undefined, - activedIssue: undefined, - activedIssueTitle: undefined, + detail: null, + loadingDetail: false, + comments: [] as PROBLEM.Comment[], + loadingComments: false, + activeProject: undefined, + activeIteration: undefined, + activeIssueType: undefined, + activeIssue: undefined, + activeIssueTitle: undefined, }; -const TicketDetail = () => { - const [detail, comments] = problemStore.useStore((s) => [s.detail, s.comments]); - const { getTicketDetail, getTicketComments, createTicketComments, closeTicket } = problemStore.effects; - const [getTicketDetailLoading, getTicketCommentsLoading] = useLoading(problemStore, [ - 'getTicketDetail', - 'getTicketComments', - ]); - const [{ activedProject, activedIteration, activedIssueType, activedIssue, activedIssueTitle }, , update] = - useUpdate(initialState); +const TicketDetail = ({ id, onClose }: { id: number; onClose: () => void }) => { + const [ + { detail, comments, activeProject, activeIteration, activeIssueType, activeIssue, activeIssueTitle }, + , + update, + ] = useUpdate(initialState); + const userMap = useUserMap(); + const loginUser = userStore.useStore((s) => s.loginUser); - useMount(() => { - getTicketDetail(); - getTicketComments(); - }); + React.useEffect(() => { + if (id) { + getTicketDetail({ ticketId: id }).then((res) => { + update({ detail: res.data }); + }); + getTicketComments({ ticketID: id }).then((res) => { + update({ comments: res.data?.comments || [] }); + }); + } else { + update({ + detail: null, + comments: [], + }); + } + }, [id, update]); + + if (!detail) return null; const handleSubmit = (content: string) => { if (!content) { @@ -119,21 +136,18 @@ const TicketDetail = () => { } createTicketComments({ + ticketID: detail.id, + userID: loginUser.id, content, commentType: 'normal', + }).then((res) => { + if (res.success) { + getTicketComments.fetch({ ticketID: detail.id }); + } }); }; - const closedBtn = - detail.status === 'open' ? ( -
- -
- ) : null; - - const type = getProblemType().find((t) => t.value === detail.type); + const type = ProblemTypeOptions.find((t) => t.value === detail.type); const priority = ProblemPriority.find((t: any) => t.value === detail.priority); const getProjects = (q: any) => { @@ -141,208 +155,219 @@ const TicketDetail = () => { }; const getIterations = (q: any) => { - if (!activedProject) return; + if (!activeProject) return; return getProjectIterations({ ...q }).then((res: any) => res.data); }; const getIssues = (q: any) => { - if (!(activedProject && activedIteration && activedIssueType)) return; + if (!(activeProject && activeIteration && activeIssueType)) return; return getProjectIssues({ ...q }).then((res: any) => res.data); }; const handleAssociationIssue = () => { createTicketComments({ + ticketID: detail.id, + userID: loginUser.id, commentType: 'issueRelation', irComment: { - issueID: activedIssue || 0, - issueTitle: activedIssueTitle || '', - projectID: activedProject || 0, - iterationID: activedIteration || 0, - issueType: toLower(activedIssueType) || '', + issueID: activeIssue || 0, + issueTitle: activeIssueTitle || '', + projectID: activeProject || 0, + iterationID: activeIteration || 0, + issueType: toLower(activeIssueType) || '', }, }); }; return ( -
- {closedBtn} - -
-
{detail.title}
-
- - {i18n.t('type')}: - {type ? type.name : '-'} - - - {i18n.t('dop:emergency level')}: - {priority ? priority.name : '-'} - -
+ + {detail.title} + {priority?.name || '-'}
- -
- {i18n.t('dop:comment area')} -
+ } + onClose={() => onClose()} + > +
+
+ + {i18n.t('type')}: + {type ? type.name : '-'} +
- - {comments.map((comment) => - comment.commentType === 'issueRelation' ? ( -
- - {i18n.t('at')} - {fromNow(comment.createdAt)} - {i18n.t('dop:associated issue')} - { - let page = ''; - const { issueType, projectID, issueID } = comment.irComment; - switch (issueType) { - case 'task': - page = goTo.pages.taskList; - break; - case 'bug': - page = goTo.pages.bugList; - break; - default: - break; - } - goTo(page, { projectId: projectID, taskId: issueID, jumpOut: true }); - }} - > - {comment.irComment.issueTitle} - -
- ) : ( - - ), - )} -
- - - handleSubmit(v), - }, - ]} - /> - - -
-
- ({ - total, - list: map(list, (project) => { - const { name, id } = project; - return { - ...project, - label: name, - value: id, - }; - }), - })} - onChange={(val) => { - update({ - ...initialState, - activedProject: val as any, - }); - }} - /> - { - update({ - activedIteration: val as any, - activedIssueType: undefined, - activedIssue: undefined, - activedIssueTitle: undefined, - }); - }} - dataFormatter={({ list, total }: { list: any[]; total: number }) => ({ - total, - list: map(list, (iteration) => { - const { title, id } = iteration; - return { - ...iteration, - label: title, - value: id, - }; - }), - })} - /> - - ({ - total, - list: map(list, (issue) => { - const { title, id } = issue; - return { - ...issue, - label: title, - value: id, - }; - }), - })} - onChange={(val, opts) => { - update({ - activedIssue: val as any, - activedIssueTitle: opts.title as any, - }); - }} +
+ +
+ {(comments || []).map((comment) => { + const user = getUserInfo(userMap, comment.userID); + return comment.commentType === 'issueRelation' ? ( +
+ + {i18n.t('at')} + {fromNow(comment.createdAt)} + {i18n.t('dop:associated issue')} + { + let page = ''; + const { issueType, projectID, issueID } = comment.irComment; + switch (issueType) { + case 'task': + page = goTo.pages.taskList; + break; + case 'bug': + page = goTo.pages.bugList; + break; + default: + break; + } + goTo(page, { projectId: projectID, taskId: issueID, jumpOut: true }); + }} + > + {comment.irComment.issueTitle} + +
+ ) : ( + + ); + })} +
+ + + handleSubmit(v), + }, + ]} + /> + + +
+
+ ({ + total, + list: map(list, (project) => { + const { name, id } = project; + return { + ...project, + label: name, + value: id, + }; + }), + })} + onChange={(val) => { + update({ + ...initialState, + activeProject: val as any, + }); + }} + /> + { + update({ + activeIteration: val as any, + activeIssueType: undefined, + activeIssue: undefined, + activeIssueTitle: undefined, + }); + }} + dataFormatter={({ list, total }: { list: any[]; total: number }) => ({ + total, + list: map(list, (iteration) => { + const { title, id } = iteration; + return { + ...iteration, + label: title, + value: id, + }; + }), + })} + /> + + ({ + total, + list: map(list, (issue) => { + const { title, id } = issue; + return { + ...issue, + label: title, + value: id, + }; + }), + })} + onChange={(val, opts) => { + update({ + activeIssue: val as any, + activeIssueTitle: opts.title as any, + }); + }} + /> +
+
+ + +
-
- - -
-
- - - -
+ + +
+ + {detail.status === 'open' && ( +
+ +
+ )} +
+ ); }; diff --git a/shell/app/modules/application/pages/problem/problem-form.tsx b/shell/app/modules/application/pages/problem/problem-form.tsx index 497f7bb29f..59f7acc647 100644 --- a/shell/app/modules/application/pages/problem/problem-form.tsx +++ b/shell/app/modules/application/pages/problem/problem-form.tsx @@ -16,23 +16,20 @@ import { FormModal } from 'common'; import MarkdownEditor from 'common/components/markdown-editor'; import i18n from 'i18n'; -export const getProblemType = (): PROBLEM.TicketType[] => { - const typeArr = [ - { - value: 'bug', - name: i18n.t('dop:code defect'), - }, - { - value: 'vulnerability', - name: i18n.t('dop:code vulnerability'), - }, - { - value: 'codeSmell', - name: i18n.t('dop:code smell'), - }, - ]; - return typeArr; -}; +export const ProblemTypeOptions = [ + { + value: 'bug', + name: i18n.t('dop:code defect'), + }, + { + value: 'vulnerability', + name: i18n.t('dop:code vulnerability'), + }, + { + value: 'codeSmell', + name: i18n.t('dop:code smell'), + }, +]; export const ProblemPriority = [ { @@ -75,7 +72,7 @@ export const ProblemForm = ({ label: i18n.t('dop:ticket type'), name: 'type', type: 'select', - options: getProblemType(), + options: ProblemTypeOptions, }, { label: i18n.t('dop:priority'), diff --git a/shell/app/modules/application/pages/problem/problem-list.tsx b/shell/app/modules/application/pages/problem/problem-list.tsx index a39c84b22b..b844fdca7c 100644 --- a/shell/app/modules/application/pages/problem/problem-list.tsx +++ b/shell/app/modules/application/pages/problem/problem-list.tsx @@ -11,84 +11,138 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React from 'react'; -import { Input, Spin, Select } from 'antd'; -import { SwitchAutoScroll, CustomFilter, Table } from 'common'; -import { goTo, fromNow, insertWhen } from 'common/utils'; -import { getProblemType, ProblemPriority } from 'application/pages/problem/problem-form'; -import { useLoading } from 'core/stores/loading'; -import problemStore from 'application/stores/problem'; -import i18n from 'i18n'; -import { ColumnProps } from 'core/common/interface'; -import { IUseFilterProps } from 'app/interface/common'; +import { Button, Spin } from 'antd'; +import { useSwitch, useUpdate } from 'app/common/use-hooks'; +import { ProblemForm, ProblemPriority, ProblemTypeOptions } from 'application/pages/problem/problem-form'; +import TicketDetail from './problem-detail'; +import { addTicket, getTicketList } from 'application/services/problem'; +import { ConfigurableFilter, Table } from 'common'; +import { fromNow, getDefaultPaging, insertWhen } from 'common/utils'; import routeInfoStore from 'core/stores/route'; +import { IUserInfo, useUserMap } from 'core/stores/userMap'; +import i18n from 'i18n'; +import React from 'react'; +import { useMount } from 'react-use'; +import userStore from 'user/stores'; import './problem-list.scss'; +import { ColumnProps } from 'antd/lib/table'; -interface IFilter { - onSubmit: (value: Obj) => void; - onReset: () => void; -} - -const Filter = React.memo(({ onReset, onSubmit }: IFilter) => { - const filterConfig = React.useMemo(() => { - return [ - { - type: Select, - name: 'type', - customProps: { - placeholder: i18n.t('filter by {name}', { name: i18n.t('type') }), - options: getProblemType().map(({ name, value }) => ( - - )), - }, - }, - { - type: Select, - name: 'priority', - customProps: { - placeholder: i18n.t('filter by {name}', { name: i18n.t('dop:priority') }), - options: ProblemPriority.map((priorityType: any) => ( - - )), - }, - }, - { - type: Input, - name: 'q', - customProps: { - placeholder: i18n.t('filter by {name}', { name: i18n.t('title') }), - }, - }, - ]; - }, []); - return ; -}); - -const { Option } = Select; const updateKeyMap = { open: 'createdAt', closed: 'closedAt', }; -export const ProblemList = (props: Pick) => { - const [ticketList, paging] = problemStore.useStore((s) => [s.ticketList, s.paging]); - const { onSubmit, onReset, onPageChange, fetchDataWithQuery } = props; - const [loading] = useLoading(problemStore, ['getTicketList']); - const { ticketType } = routeInfoStore.useStore((s) => s.params); +export const getUserInfo = (userMap: Obj, userId: string) => { + const user = userMap[userId]; + return user ? user.nick || user.name || user.email || user.phone || user.id : i18n.t('dop:system'); +}; + +export const ProblemList = () => { + const routeAppId = routeInfoStore.useStore((s) => s.params.appId); + const [visible, openModal, closeModal] = useSwitch(false); + const appId = routeInfoStore.useStore((s) => s.params.appId); + const loginUser = userStore.getState((s) => s.loginUser); + const [state, updater] = useUpdate({ + detailVisibleId: 0, + ...getDefaultPaging(), + hasMore: undefined, + }); - const handleSubmit = React.useCallback(onSubmit, []); - const handleReset = React.useCallback(onReset, []); + const [filterData, setFilterData] = React.useState({ + status: 'open', + }); + const [data, loading] = getTicketList.useState(); + const list = data?.tickets || []; + + const userMap = useUserMap(); + + const filterFn = (values?: Obj) => { + setFilterData((prev) => ({ ...prev, ...values })); + getTicketList + .fetch({ + ...filterData, + ...values, + status: values?.status === 'all' ? undefined : values?.status || filterData.status, + pageNo: state.pageNo, + pageSize: state.pageSize, + targetID: +routeAppId, + targetType: 'application', + }) + .then((resp) => { + updater.total(resp.data?.total || 0); + }); + }; + + useMount(() => { + filterFn(); + }); + + const onOk = (payload: any) => { + addTicket({ + ...payload, + userID: loginUser.id, + targetID: String(appId), + targetType: 'application', + }).then((res) => { + if (res.success) { + closeModal(); + filterFn({ pageNo: 1 }); + } + }); + }; + const fieldsList = [ + { + key: 'status', + type: 'select', + label: i18n.t('status'), + mode: 'single', + options: [ + { + value: 'all', + label: i18n.t('all'), + }, + { + value: 'open', + label: i18n.t('dop:pending'), + }, + { + value: 'closed', + label: i18n.t('closed'), + }, + ], + placeholder: i18n.t('filter by {name}', { name: i18n.t('status') }), + }, + { + key: 'type', + type: 'select', + label: i18n.t('type'), + placeholder: i18n.t('filter by {name}', { name: i18n.t('type') }), + options: ProblemTypeOptions.map((a) => ({ label: a.name, value: a.value })), + mode: 'single', + }, + { + key: 'priority', + type: 'select', + label: i18n.t('dop:priority'), + options: ProblemPriority.map((a) => ({ label: a.name, value: a.value })), + placeholder: i18n.t('filter by {name}', { name: i18n.t('dop:priority') }), + mode: 'single', + }, + { + key: 'q', + outside: true, + label: 'title', + placeholder: '根据标题过滤', + type: 'input', + }, + ]; const columns: Array> = [ { title: 'ID', dataIndex: 'id', width: 80, - render: (text) => `#${text}`, + render: (text: string) => `#${text}`, }, { title: i18n.t('title'), @@ -98,8 +152,8 @@ export const ProblemList = (props: Pick { - const type = getProblemType().find((t) => t.value === text); + render: (text: string) => { + const type = ProblemTypeOptions.find((t) => t.value === text); return type ? type.name : '-'; }, }, @@ -107,7 +161,7 @@ export const ProblemList = (props: Pick { + render: (text: string) => { const priority: any = ProblemPriority.find((t: any) => t.value === text); return {priority.name}; }, @@ -116,59 +170,68 @@ export const ProblemList = (props: Pick getUserInfo(userMap, userId), }, { title: i18n.t('create time'), dataIndex: 'createdAt', width: 176, - render: (text) => fromNow(text), + render: (text: string) => fromNow(text), }, - ...insertWhen(ticketType !== 'open', [ + ...insertWhen(filterData.status !== 'open', [ { title: i18n.t('close person'), dataIndex: 'content', width: 120, - render: (_text, record) => { - return record.status === 'closed' ? record.lastOperatorUser : ''; + render: (_text: string, record: PROBLEM.Ticket) => { + return record.status === 'closed' ? getUserInfo(userMap, record.lastOperatorUser) : '-'; }, }, { title: i18n.t('close time'), dataIndex: 'status', width: 176, - render: (text, record) => (text === 'closed' ? fromNow(record[updateKeyMap[text]]) : ''), + render: (text: string, record: PROBLEM.Ticket) => + text === 'closed' ? fromNow(record[updateKeyMap[text]]) : '-', }, ] as Array>), ]; return ( - { - fetchDataWithQuery(pageNo); + filterFn({ pageNo }); }} pagination={{ - current: paging.pageNo, - total: paging.total, - pageSize: paging.pageSize, - onChange: onPageChange, + current: state.pageNo, + total: state.total, + pageSize: state.pageSize, + onChange: (pageNo, pageSize) => { + filterFn({ pageNo, pageSize }); + }, }} onRow={(record: PROBLEM.Ticket) => { return { onClick: () => { - goTo(`./${record.id}`); + updater.detailVisibleId(record.id); }, }; }} - slot={} + slot={} /> +
+ + + updater.detailVisibleId(0)} /> +
); }; diff --git a/shell/app/modules/application/pages/quality/entry.tsx b/shell/app/modules/application/pages/quality/entry.tsx new file mode 100644 index 0000000000..2c803319df --- /dev/null +++ b/shell/app/modules/application/pages/quality/entry.tsx @@ -0,0 +1,45 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import { RadioTabs } from 'common'; +import i18n from 'i18n'; +import React from 'react'; +import CodeQuality from '.'; +import TestList from '../test/test-list'; +import { ProblemList } from '../problem/problem-list'; + +const AppQuality = () => { + const tabs = [ + { value: 'quality', label: i18n.t('dop:quality reports'), Comp: CodeQuality }, + { value: 'issues', label: i18n.t('dop:app-issues'), Comp: ProblemList }, + { value: 'test', label: i18n.t('dop:lists'), Comp: TestList }, + ]; + const [tab, setTab] = React.useState(tabs[0]); + const Content = tab.Comp; + + return ( + <> + { + setTab(newTab as typeof tabs[0]); + }} + className="mb-2" + /> + + + ); +}; + +export default AppQuality; diff --git a/shell/app/modules/application/pages/quality/index.tsx b/shell/app/modules/application/pages/quality/index.tsx index e545d0abb7..000c0f0287 100644 --- a/shell/app/modules/application/pages/quality/index.tsx +++ b/shell/app/modules/application/pages/quality/index.tsx @@ -125,7 +125,7 @@ const CodeQuality = () => {
{ - goTo(goTo.pages.qaTicket, { + goTo(goTo.pages.appCodeQualityIssueOpen, { projectId, appId, type: query, @@ -332,4 +332,4 @@ const CodeQuality = () => { ); }; -export { CodeQuality as CodeQualityWrap }; +export default CodeQuality; diff --git a/shell/app/modules/application/pages/repo/repo-branch.tsx b/shell/app/modules/application/pages/repo/repo-branch.tsx index 52bb70fb7e..07e23c8909 100644 --- a/shell/app/modules/application/pages/repo/repo-branch.tsx +++ b/shell/app/modules/application/pages/repo/repo-branch.tsx @@ -11,8 +11,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { Spin, Button, Tooltip, Dropdown, Menu, Input } from 'antd'; -import { EmptyHolder, Avatar, DeleteConfirm, IF, ErdaIcon, ErdaAlert } from 'common'; +import { Spin, Button, Tooltip, Dropdown, Menu, Input, Card } from 'antd'; +import { EmptyHolder, Avatar, DeleteConfirm, IF, ErdaIcon, ErdaAlert, RadioTabs } from 'common'; import React from 'react'; import { fromNow, replaceEmoji, goTo } from 'common/utils'; import { mergeRepoPathWith } from './util'; @@ -26,20 +26,10 @@ import repoStore from 'application/stores/repo'; import { useLoading } from 'core/stores/loading'; import appStore from 'application/stores/application'; import DOMPurify from 'dompurify'; +import RepoTag from './repo-tag'; const { Search } = Input; -export const BRANCH_TABS = [ - { - key: 'branches', - name: i18n.t('dop:branch'), - }, - { - key: 'tags', - name: i18n.t('tag'), - }, -]; - const RepoBranch = () => { const permMap = usePerm((s) => s.app.repo.branch); const [info, list] = repoStore.useStore((s) => [s.info, s.branch]); @@ -170,4 +160,29 @@ const RepoBranch = () => { ); }; -export default RepoBranch; +const BranchTagEntry = () => { + const tabs = [ + { value: 'branches', label: i18n.t('dop:branches'), Comp: RepoBranch }, + { value: 'tags', label: i18n.t('dop:tags'), Comp: RepoTag }, + ]; + const [tab, setTab] = React.useState(tabs[0]); + const Content = tab.Comp; + + return ( + <> + { + setTab(newTab as typeof tabs[0]); + }} + className="mb-2" + /> + + + + + ); +}; + +export default BranchTagEntry; diff --git a/shell/app/modules/application/pages/repo/repo-mr.tsx b/shell/app/modules/application/pages/repo/repo-mr.tsx index c9fdd94299..616e285296 100644 --- a/shell/app/modules/application/pages/repo/repo-mr.tsx +++ b/shell/app/modules/application/pages/repo/repo-mr.tsx @@ -11,49 +11,43 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { Button } from 'antd'; +import { Button, Card } from 'antd'; import React from 'react'; import { goTo } from 'common/utils'; import { RepoMrTable } from './components/repo-mr-table'; import i18n from 'i18n'; import repoStore from 'application/stores/repo'; -import routeInfoStore from 'core/stores/route'; import { WithAuth, usePerm } from 'user/common'; -import { IF, ErdaAlert } from 'common'; +import { ErdaAlert, RadioTabs } from 'common'; -export const mrTabs = () => { +const PureRepoMR = () => { const info = repoStore.useStore((s) => s.info); - return [ + const permObj = usePerm((s) => s.app.repo.mr); + const [mrType, setMrType] = React.useState('open'); + + const options = [ { - key: 'all', - name: i18n.t('all'), + value: 'all', + label: i18n.t('all'), }, { - key: 'open', - name: ( + value: 'open', + label: ( {i18n.t('dop:committed')} - {info ? info.mergeRequestCount : 0} + ({info ? info.mergeRequestCount : 0}) ), }, { - key: 'merged', - name: i18n.t('dop:have merged'), + value: 'merged', + label: i18n.t('dop:have merged'), }, { - key: 'closed', - name: i18n.t('closed'), + value: 'closed', + label: i18n.t('closed'), }, ]; -}; - -const PureRepoMR = () => { - const info = repoStore.useStore((s) => s.info); - const params = routeInfoStore.useStore((s) => s.params); - const permObj = usePerm((s) => s.app.repo.mr); - const { mrType = 'open' } = params; - return (
@@ -67,10 +61,19 @@ const PureRepoMR = () => {
- - - - + + setMrType(v as string)} + className="mb-2" + /> + + + + + +
); }; diff --git a/shell/app/modules/application/pages/test/test-detail-container.tsx b/shell/app/modules/application/pages/test/test-detail-container.tsx index 9715efd942..be5a4aea00 100644 --- a/shell/app/modules/application/pages/test/test-detail-container.tsx +++ b/shell/app/modules/application/pages/test/test-detail-container.tsx @@ -11,7 +11,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { Select, Spin } from 'antd'; +import { Drawer, Select, Spin } from 'antd'; import { Holder, IF } from 'common'; import { connectCube } from 'common/utils'; import { map, isEmpty, forEach } from 'lodash'; @@ -24,9 +24,11 @@ import applicationTestStore from 'application/stores/test'; import { useLoading } from 'core/stores/loading'; interface IProps { + testId: number; testDetail: ITestDetail; isFetching: boolean; - getTestDetail: () => Promise; + getTestDetail: typeof applicationTestStore.effects.getTestDetail; + onClose: () => void; } interface IState { chosenSuiteIndex: number; @@ -63,6 +65,14 @@ const getValidSuites = (suites: ISuite[]): ISuite[] => { }; class TestDetailContainer extends React.Component { + static getDerivedStateFromProps(nextProps: IProps, prevState: IState) { + const { testDetail } = nextProps; + if (testDetail !== prevState.preProps.testDetail) { + return { suites: getValidSuites(testDetail.suites || []) }; + } + return null; + } + constructor(props: IProps) { super(props); const { testDetail } = props; @@ -74,16 +84,52 @@ class TestDetailContainer extends React.Component { }; } - static getDerivedStateFromProps(nextProps: IProps, prevState: IState) { - const { testDetail } = nextProps; - if (testDetail !== prevState.preProps.testDetail) { - return { suites: getValidSuites(testDetail.suites || []) }; - } - return null; + render() { + const { suites, chosenSuiteIndex, logVisible } = this.state; + const detailOptions = map(suites, (suite, index) => ({ value: index, text: suite.name || index })); + const currentSuite = suites[chosenSuiteIndex] || null; + const { testId, testDetail, isFetching, onClose } = this.props; + return ( + + +
+ + + + + + + {i18n.t('log')} + + +
+ + + + +
+
+ ); } componentDidMount(): void { - this.props.getTestDetail(); + this.props.testId && this.props.getTestDetail(this.props.testId); } changeDetail = (chosenSuiteIndex: number) => { @@ -91,52 +137,8 @@ class TestDetailContainer extends React.Component { }; toggleLog = () => { - this.setState({ - logVisible: !this.state.logVisible, - }); + this.setState((prev) => ({ ...prev, logVisible: !prev.logVisible })); }; - - render() { - const { suites, chosenSuiteIndex, logVisible } = this.state; - const detailOptions = map(suites, (suite, index) => ({ value: index, text: suite.name || index })); - const currentSuite = suites[chosenSuiteIndex] || null; - const { testDetail, isFetching } = this.props; - return ( - -
- - - - - - - {i18n.t('log')} - - -
- - - - -
- ); - } } const Mapper = () => { diff --git a/shell/app/modules/application/pages/test/test-detail.tsx b/shell/app/modules/application/pages/test/test-detail.tsx index 9109ebf71e..a99445590e 100644 --- a/shell/app/modules/application/pages/test/test-detail.tsx +++ b/shell/app/modules/application/pages/test/test-detail.tsx @@ -52,13 +52,14 @@ const { ChartContainer } = CardContainer; const convertTestCases = (tests: ITest[]) => map(tests, (item) => ({ ...item, key: uniqueId() })); class TestDetail extends React.Component { - onSearchKeyChange = debounce( - (value: string) => - this.setState({ searchKey: value }, () => { - this.changeFilterList(); - }), - 300, - ); + static getDerivedStateFromProps(nextProps: IProps, prevState: IState) { + const { suite } = nextProps; + if (suite !== prevState.preProps.suite) { + suite.tests = convertTestCases(suite.tests || []); + return { suite, checkedCaseKey: get(suite.tests, '[0].key') || '', filterList: suite.tests }; + } + return null; + } constructor(props: IProps) { super(props); @@ -76,79 +77,6 @@ class TestDetail extends React.Component { }; } - static getDerivedStateFromProps(nextProps: IProps, prevState: IState) { - const { suite } = nextProps; - if (suite !== prevState.preProps.suite) { - suite.tests = convertTestCases(suite.tests || []); - return { suite, checkedCaseKey: get(suite.tests, '[0].key') || '', filterList: suite.tests }; - } - return null; - } - - showTestInfo = (key: string | undefined, e?: React.MouseEvent | undefined) => { - if (e) e.stopPropagation(); - const { checkedCaseKey } = this.state; - if (key && key !== checkedCaseKey) { - this.setState({ - checkedCaseKey: key, - }); - } - }; - - toggleErrorInfo = () => { - // this.setState({ isShowing: !this.state.isShowing }); - }; - - renderTestInfo = (test?: ITest | null) => { - const { isShowing } = this.state; - if (!test) return
; - const { error, stdout } = test; - const { message = '', body = '', type = '' } = error || {}; - const errorClass = classNames({ block: isShowing }); - const hasLog = stdout || message || body || type; - - return ( -
- -
-            {stdout}
-            

- {type || body ? ( - this.toggleErrorInfo()}> - {type || 'error'} - - ) : null} -

-

{message ? `Error: ${message}` : ''}

-

{`${body}`}

-
-
-
- ); - }; - - changeFilterKey = (filterKey: string) => { - this.setState({ filterKey }, () => { - this.changeFilterList(); - }); - }; - - changeFilterList = () => { - const { suite, filterKey, searchKey, checkedCaseKey } = this.state; - const { tests } = suite; - const filterList = filter(tests, (item) => { - const { status, name } = item; - return name.toLowerCase().includes(searchKey.toLowerCase()) && (filterKey === 'all' || status === filterKey); - }); - const checkedTestCase = find(filterList, { key: checkedCaseKey }); - const forUpdate: any = { filterList }; - if (!checkedTestCase) { - // 当前选中的没匹配数据 - forUpdate.checkedCaseKey = get(filterList, '[0].key') || ''; - } - this.setState({ ...forUpdate }); - }; - render() { const { checkedCaseKey, filterKey, suite, filterList } = this.state; const { totals, extra } = suite; @@ -256,5 +184,77 @@ class TestDetail extends React.Component {
); } + + onSearchKeyChange = debounce( + (value: string) => + this.setState({ searchKey: value }, () => { + this.changeFilterList(); + }), + 300, + ); + + showTestInfo = (key: string | undefined, e?: React.MouseEvent | undefined) => { + if (e) e.stopPropagation(); + const { checkedCaseKey } = this.state; + if (key && key !== checkedCaseKey) { + this.setState({ + checkedCaseKey: key, + }); + } + }; + + toggleErrorInfo = () => { + // this.setState({ isShowing: !this.state.isShowing }); + }; + + renderTestInfo = (test?: ITest | null) => { + const { isShowing } = this.state; + if (!test) return
; + const { error, stdout } = test; + const { message = '', body = '', type = '' } = error || {}; + const errorClass = classNames({ block: isShowing }); + const hasLog = stdout || message || body || type; + + return ( +
+ +
+            {stdout}
+            

+ {type || body ? ( + this.toggleErrorInfo()}> + {type || 'error'} + + ) : null} +

+

{message ? `Error: ${message}` : ''}

+

{`${body}`}

+
+
+
+ ); + }; + + changeFilterKey = (filterKey: string) => { + this.setState({ filterKey }, () => { + this.changeFilterList(); + }); + }; + + changeFilterList = () => { + const { suite, filterKey, searchKey, checkedCaseKey } = this.state; + const { tests } = suite; + const filterList = filter(tests, (item) => { + const { status, name } = item; + return name.toLowerCase().includes(searchKey.toLowerCase()) && (filterKey === 'all' || status === filterKey); + }); + const checkedTestCase = find(filterList, { key: checkedCaseKey }); + const forUpdate: any = { filterList }; + if (!checkedTestCase) { + // 当前选中的没匹配数据 + forUpdate.checkedCaseKey = get(filterList, '[0].key') || ''; + } + this.setState({ ...forUpdate }); + }; } export default TestDetail; diff --git a/shell/app/modules/application/pages/test/test-list.tsx b/shell/app/modules/application/pages/test/test-list.tsx index 914ecce525..51fdd85324 100644 --- a/shell/app/modules/application/pages/test/test-list.tsx +++ b/shell/app/modules/application/pages/test/test-list.tsx @@ -21,7 +21,8 @@ import i18n from 'i18n'; import './test-list.scss'; import applicationTestStore from 'application/stores/test'; import { useLoading } from 'core/stores/loading'; -import { ColumnProps } from 'core/common/interface'; +import TestDetailContainer from './test-detail-container'; +import { ColumnProps } from 'antd/lib/table'; const getTestDuration = (duration: any) => { const seconds = floor(parseInt(duration, 10) / 10 ** 9, 3); // 时间为纳秒 @@ -107,6 +108,7 @@ const columns: Array> = [ ]; const TestList = () => { + const [activeId, setActiveId] = React.useState(0); const [list, testListPaging] = applicationTestStore.useStore((s) => [s.list, s.testListPaging]); const [isFetching] = useLoading(applicationTestStore, ['getTestList']); const { getTestTypes, getTestList } = applicationTestStore.effects; @@ -131,7 +133,7 @@ const TestList = () => { onRow={({ id }: TEST.RunTestItem) => { return { onClick: () => { - goTo(`./${id}`); + setActiveId(id); }, }; }} @@ -142,6 +144,8 @@ const TestList = () => { }} /> + + setActiveId(0)} />
); }; diff --git a/shell/app/modules/application/router.ts b/shell/app/modules/application/router.ts index d162a57867..72afece4bc 100644 --- a/shell/app/modules/application/router.ts +++ b/shell/app/modules/application/router.ts @@ -13,93 +13,93 @@ import getRuntimeRouter from 'runtime/router'; import i18n from 'i18n'; -import { mrTabs } from './pages/repo/repo-mr'; -import { problemTabs } from './pages/problem'; -import { BRANCH_TABS } from './pages/repo/repo-branch'; +import { APP_TABS, getQualityTabs, prependTabs } from './tabs'; function getAppRouter(): RouteConfigItem { return { path: 'apps/:appId', mark: 'application', breadcrumbName: '{appName}', + // tabs: APP_TABS, routes: [ - { - path: 'deploy', - mark: 'deploy', - breadcrumbName: i18n.t('dop:Environments'), - routes: [ - ...getRuntimeRouter(), - { - layout: { noWrapper: true }, - getComp: (cb) => cb(import('application/pages/deploy/deploy'), 'DeployWrap'), - }, - ], - }, - { - path: 'ticket/:ticketType', - breadcrumbName: i18n.t('dop:issues'), - tabs: problemTabs, - routes: [ - { - layout: { noWrapper: true }, - getComp: (cb) => cb(import('application/pages/problem')), - }, - { - path: ':ticketId', - breadcrumbName: i18n.t('dop:issue detail'), - getComp: (cb) => cb(import('application/pages/problem/problem-detail')), - }, - ], - }, + // TODO: remove + // { + // path: 'deploy', + // mark: 'deploy', + // breadcrumbName: i18n.t('dop:Environments'), + // routes: [ + // ...getRuntimeRouter(), + // { + // layout: { noWrapper: true }, + // getComp: (cb) => cb(import('application/pages/deploy/deploy'), 'DeployWrap'), + // }, + // ], + // }, { path: 'repo', mark: 'repo', breadcrumbName: i18n.t('dop:code'), pageName: i18n.t('dop:files'), + tabs: APP_TABS, + alwaysShowTabKey: 'repo', + showTabInChildren: (currentRoute, topRoute) => topRoute.mark === 'repo', routes: [ { + tabs: APP_TABS, + alwaysShowTabKey: 'repo', + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/repo-tree')), }, { path: 'tree/(.*)', mark: 'repoTree', breadcrumbName: i18n.t('dop:code'), + tabs: APP_TABS, + alwaysShowTabKey: 'repo', + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/repo-tree')), }, { path: 'branches', - tabs: BRANCH_TABS, + tabs: APP_TABS, + alwaysShowTabKey: 'repo/branches', + ignoreTabQuery: true, breadcrumbName: i18n.t('dop:branch management'), routes: [ { path: 'compare/:branches*', mark: 'repoCompare', breadcrumbName: i18n.t('dop:branch comparison'), + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/branch-compare-detail'), 'BranchCompareDetail'), + layout: { noWrapper: true }, }, { - tabs: BRANCH_TABS, + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/repo-branch')), + layout: { noWrapper: true }, }, ], }, - { - path: 'tags', - tabs: BRANCH_TABS, - breadcrumbName: i18n.t('dop:branch management'), - routes: [ - { - tabs: BRANCH_TABS, - getComp: (cb) => cb(import('application/pages/repo/repo-tag')), - }, - ], - }, + // { + // path: 'tags', + // tabs: APP_TABS, + // alwaysShowTabKey: 'repo/branches', + // ignoreTabQuery: true, + // breadcrumbName: i18n.t('dop:branch management'), + // getComp: (cb) => cb(import('application/pages/repo/repo-tag')), + // layout: { noWrapper: true }, + // }, { path: 'commit', + tabs: APP_TABS, + alwaysShowTabKey: 'repo/commits', + ignoreTabQuery: true, routes: [ { path: ':commitId', breadcrumbName: i18n.t('dop:commit details'), + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/commit-detail')), }, ], @@ -107,65 +107,69 @@ function getAppRouter(): RouteConfigItem { { path: 'commits/(.*)', // commits后面可能有分支(包含/),commit后面只有commitId breadcrumbName: i18n.t('dop:commit history'), + tabs: APP_TABS, + alwaysShowTabKey: 'repo/commits', + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/repo-commit')), }, { path: 'mr/:mrType', breadcrumbName: i18n.t('dop:merge requests'), - tabs: mrTabs, + tabs: APP_TABS, + alwaysShowTabKey: 'repo/mr/open', + ignoreTabQuery: true, routes: [ { path: 'createMR', breadcrumbName: i18n.t('dop:new merge request'), + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/repo-mr-creation'), 'RepoMRCreation'), }, { path: ':mergeId', breadcrumbName: i18n.t('dop:merge request detail'), + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/mr-detail')), }, { - tabs: mrTabs, + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/repo-mr')), + layout: { noWrapper: true }, }, ], }, - { - path: 'backup', - breadcrumbName: i18n.t('dop:repo backup'), - layout: { noWrapper: true }, - getComp: (cb) => cb(import('app/modules/application/pages/repo/repo-backup')), - }, - ], - }, - { - path: 'release', - breadcrumbName: i18n.t('artifact management'), - layout: { fullHeight: true, noWrapper: true }, - getComp: (cb) => cb(import('app/modules/application/pages/release/release-list')), - }, - { - path: 'apiDesign', - mark: 'apiDesign', - breadcrumbName: i18n.t('dop:API design'), - routes: [ - { - layout: { fullHeight: true }, - getComp: (cb) => cb(import('apiManagePlatform/pages/api-market/design')), - }, + // entry is closed + // { + // path: 'backup', + // breadcrumbName: i18n.t('dop:repo backup'), + // tabs: APP_TABS, + // layout: { noWrapper: true }, + // getComp: (cb) => cb(import('app/modules/application/pages/repo/repo-backup')), + // }, ], }, + // TODO: remove + // { + // path: 'release', + // breadcrumbName: i18n.t('artifact management'), + // tabs: APP_TABS, + // layout: { fullHeight: true, noWrapper: true }, + // getComp: (cb) => cb(import('app/modules/application/pages/release/release-list')), + // }, { path: 'pipeline', mark: 'pipeline', breadcrumbName: i18n.t('pipeline'), pageName: i18n.t('pipeline'), + tabs: APP_TABS, + ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/pipeline')), layout: { fullHeight: true, noWrapper: true }, }, { path: 'dataTask', mark: 'dataTask', + tabs: APP_TABS, pageName: `${i18n.t('dop:data task')}`, routes: [ { @@ -181,6 +185,7 @@ function getAppRouter(): RouteConfigItem { { path: 'dataModel', breadcrumbName: i18n.t('dop:data model'), + tabs: APP_TABS, routes: [ { path: 'starChart/:filePath', @@ -195,32 +200,90 @@ function getAppRouter(): RouteConfigItem { { path: 'dataMarket', breadcrumbName: i18n.t('dop:data market'), + tabs: APP_TABS, getComp: (cb) => cb(import('application/pages/data-market/data-market'), 'DataMarket'), }, { - path: 'test', + path: 'quality', + breadcrumbName: i18n.t('dop:quality reports'), + tabs: APP_TABS, + ignoreTabQuery: true, + getComp: (cb) => cb(import('application/pages/quality/entry')), + // wrapper: prependTabs(getQualityTabs, 'quality'), + layout: { noWrapper: true }, + // routes: [ + // { + // breadcrumbName: i18n.t('dop:lists'), + // getComp: (cb) => cb(import('application/pages/test/test-list')), + // wrapper: prependTabs(getQualityTabs, 'test'), + // layout: { noWrapper: true }, + // }, + // { + // path: ':testId', + // breadcrumbName: i18n.t('dop:test detail'), + // getComp: (cb) => cb(import('application/pages/test/test-detail-container')), + // wrapper: prependTabs(getQualityTabs, 'test'), + // layout: { noWrapper: true }, + // }, + // ], + }, + // { + // path: 'ticket/:ticketType', + // breadcrumbName: i18n.t('dop:issues'), + // tabs: APP_TABS, + // alwaysShowTabKey: 'quality', + // routes: [ + // { + // getComp: (cb) => cb(import('application/pages/problem')), + // wrapper: prependTabs(getQualityTabs, 'ticket'), + // layout: { noWrapper: true }, + // }, + // { + // path: ':ticketId', + // breadcrumbName: i18n.t('dop:issue detail'), + // getComp: (cb) => cb(import('application/pages/problem/problem-detail')), + // wrapper: prependTabs(getQualityTabs, 'ticket'), + // layout: { noWrapper: true }, + // }, + // ], + // }, + // { + // path: 'test', + // tabs: APP_TABS, + // routes: [ + // { + // breadcrumbName: i18n.t('dop:lists'), + // getComp: (cb) => cb(import('application/pages/test/test-list')), + // wrapper: prependTabs(getQualityTabs, 'test'), + // layout: { noWrapper: true }, + // }, + // { + // path: ':testId', + // breadcrumbName: i18n.t('dop:test detail'), + // getComp: (cb) => cb(import('application/pages/test/test-detail-container')), + // wrapper: prependTabs(getQualityTabs, 'test'), + // layout: { noWrapper: true }, + // }, + // ], + // }, + { + path: 'apiDesign', + mark: 'apiDesign', + breadcrumbName: i18n.t('dop:API design'), + tabs: APP_TABS, + ignoreTabQuery: true, routes: [ { - listKey: 'apps', - breadcrumbName: i18n.t('dop:lists'), - getComp: (cb) => cb(import('application/pages/test/test-list')), - layout: { noWrapper: true }, - }, - { - path: 'quality', - breadcrumbName: i18n.t('dop:quality reports'), - getComp: (cb) => cb(import('application/pages/quality'), 'CodeQualityWrap'), - }, - { - path: ':testId', - breadcrumbName: i18n.t('dop:test detail'), - getComp: (cb) => cb(import('application/pages/test/test-detail-container')), + layout: { fullHeight: true }, + getComp: (cb) => cb(import('apiManagePlatform/pages/api-market/design')), }, ], }, { path: 'setting', breadcrumbName: i18n.t('dop:application setting'), + tabs: APP_TABS, + ignoreTabQuery: true, layout: { fullHeight: true }, getComp: (cb) => cb(import('application/pages/settings/app-settings')), }, diff --git a/shell/app/modules/application/services/problem.ts b/shell/app/modules/application/services/problem.ts index 7480904dab..2d8bc6fad6 100644 --- a/shell/app/modules/application/services/problem.ts +++ b/shell/app/modules/application/services/problem.ts @@ -11,51 +11,44 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import agent from 'agent'; - -export const getTicketList = (params: PROBLEM.ListQuery): IPagingResp => { - return agent - .get('/api/tickets') - .query(params) - .then((response: any) => response.body); -}; - -export const getTicketDetail = (ticketId: number): PROBLEM.Ticket => { - return agent.get(`/api/tickets/${ticketId}`).then((response: any) => response.body); +import { apiCreator } from 'core/service'; + +const apis = { + getTicketList: { + api: '/api/tickets', + }, + addTicket: { + api: 'post@/api/tickets', + }, + closeTicket: { + api: 'put@/api/tickets/:ticketId/actions/close', + }, + reopenTicket: { + api: 'put@/api/tickets/:ticketId/actions/reopen', + }, + getTicketDetail: { + api: '/api/tickets/:ticketId', + }, + getTicketComments: { + api: '/api/comments', + }, + createTicketComments: { + api: 'post@/api/comments', + }, }; -export const addTicket = (data: PROBLEM.CreateBody) => { - return agent - .post('/api/tickets') - .send(data) - .then((response: any) => response.body); -}; - -// export const updateTicket = (data: PROBLEM.CreateBody) => { -// return agent.put('/api/tickets') -// .send(data) -// .then((response: any) => response.body); -// }; - -export const closeTicket = (ticketId: number) => { - return agent.put(`/api/tickets/${ticketId}/actions/close`).then((response: any) => response.body); -}; - -export const reopenTicket = (ticketId: number) => { - return agent.put(`/api/tickets/${ticketId}/actions/reopen`).then((response: any) => response.body); -}; - -// 这个接口不用传分页参数,返回类型需自定义 -export const getComments = (ticketId: number): { comments: PROBLEM.Comment[]; total: number } => { - return agent - .get('/api/comments') - .query({ ticketID: ticketId }) - .then((response: any) => response.body); -}; - -export const createComment = (payload: PROBLEM.CommentBody) => { - return agent - .post('/api/comments') - .send(payload) - .then((response: any) => response.body); -}; +interface TicketListResp { + tickets: PROBLEM.Ticket[]; + total: number; +} +export const getTicketList = apiCreator<(p: PROBLEM.ListQuery) => TicketListResp>(apis.getTicketList); +export const addTicket = apiCreator<(data: PROBLEM.CreateBody) => number>(apis.addTicket); +export const closeTicket = apiCreator<(query: { ticketId: number | string }) => void>(apis.closeTicket); +export const reopenTicket = apiCreator<(query: { ticketId: number | string }) => void>(apis.reopenTicket); +export const getTicketDetail = apiCreator<(query: { ticketId: number | string }) => PROBLEM.Ticket>( + apis.getTicketDetail, +); +export const getTicketComments = apiCreator< + (query: { ticketID: number }) => { comments: PROBLEM.Comment[]; total: number } +>(apis.getTicketComments); +export const createTicketComments = apiCreator<(data: PROBLEM.CommentBody) => void>(apis.createTicketComments); diff --git a/shell/app/modules/application/stores/application.tsx b/shell/app/modules/application/stores/application.tsx index 80a7403042..c4727bd4ac 100644 --- a/shell/app/modules/application/stores/application.tsx +++ b/shell/app/modules/application/stores/application.tsx @@ -24,7 +24,6 @@ import layoutStore from 'layout/stores/layout'; import { HeadAppSelector } from 'application/common/app-selector'; import userStore from 'app/user/stores'; import { theme } from 'app/themes'; -import { getAppMenu } from 'app/menus'; import { emit } from 'core/event-hub'; import i18n from 'i18n'; import { goTo } from 'common/utils'; @@ -71,15 +70,16 @@ const appStore = createStore({ cb() { appStore.effects.getBranchInfo({ appId }); // 请求app下全量branch appStore.effects.getAppDetail(appId).then((detail: IApplication) => { - const curAppMenu = getAppMenu({ appDetail: detail }); + // const curAppTabs = getAppMenu({ appDetail: detail }); loadingInApp = false; - layoutStore.reducers.setSubSiderInfoMap({ - menu: curAppMenu, - key: 'application', - detail: { ...detail, icon: theme.appIcon }, - getHeadName: () => , - }); - checkIsAppIndex(curAppMenu); // 设置app的menu后,重定向 + // layoutStore.reducers.setAppTabs(curAppTabs); + // layoutStore.reducers.setSubSiderInfoMap({ + // menu: curAppMenu, + // key: 'application', + // detail: { ...detail, icon: theme.appIcon }, + // getHeadName: () => , + // }); + checkIsAppIndex(); // 设置app的menu后,重定向 }); }, }); @@ -89,7 +89,7 @@ const appStore = createStore({ } if (isLeaving('application')) { - layoutStore.reducers.setSubSiderInfoMap({ key: 'application', menu: [] }); + // layoutStore.reducers.setSubSiderInfoMap({ key: 'application', menu: [] }); userStore.reducers.clearAppList(); appStore.reducers.cleanAppDetail(); appStore.reducers.updateCurAppId(); @@ -194,14 +194,14 @@ const appStore = createStore({ state.curAppId = appId; }, onAppIndexEnter(_state, appMenu?: any[]) { - let curAppMenu: any = appMenu; - if (!curAppMenu) { - const subSiderInfoMap = layoutStore.getState((s) => s.subSiderInfoMap); - curAppMenu = get(subSiderInfoMap, 'application.menu'); - } + // let curAppMenu: any = appMenu; + // if (!curAppMenu) { + // const subSiderInfoMap = layoutStore.getState((s) => s.subSiderInfoMap); + // curAppMenu = get(subSiderInfoMap, 'application.menu'); + // } // app首页重定向到第一个菜单链接 - const rePathname = get(curAppMenu, '[0].href'); - rePathname && goTo(rePathname, { replace: true }); + // const rePathname = get(curAppMenu, '[0].href'); + goTo(goTo.pages.repo, { replace: true }); }, }, }); diff --git a/shell/app/modules/application/stores/problem.ts b/shell/app/modules/application/stores/problem.ts deleted file mode 100644 index d79af5b4e6..0000000000 --- a/shell/app/modules/application/stores/problem.ts +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2021 Terminus, Inc. -// -// This program is free software: you can use, redistribute, and/or modify -// it under the terms of the GNU Affero General Public License, version 3 -// or later ("AGPL"), as published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -import { createStore } from 'core/cube'; -import { getUserMap } from 'core/stores/userMap'; -import { getDefaultPaging } from 'common/utils'; -import { - getTicketList, - getTicketDetail, - addTicket, - closeTicket, - reopenTicket, - getComments, - createComment, -} from '../services/problem'; -import i18n from 'i18n'; -import { map } from 'lodash'; -import userStore from 'app/user/stores'; - -const getUser = (user: ILoginUser) => - user ? user.nick || user.name || user.email || user.phone || user.id : i18n.t('dop:system'); - -interface IState { - ticketList: PROBLEM.Ticket[]; - paging: IPaging; - comments: PROBLEM.Comment[]; - commentsTotal: number; - openTotal: number; - detail: PROBLEM.Ticket; -} - -const initState: IState = { - ticketList: [], - paging: getDefaultPaging(), - comments: [], - commentsTotal: 0, - openTotal: 0, - detail: {} as PROBLEM.Ticket, -}; - -const ticketStore = createStore({ - name: 'appTicket', - state: initState, - effects: { - async getTicketList({ call, update }, payload: Merge) { - const { list = [], total } = await call(getTicketList, payload, { - paging: { key: 'paging', listKey: 'tickets' }, - }); - - const userMap = getUserMap(); - const ticketList = map(list, (ticket) => { - // 在userInfo中获取对应的用户名 - const { creator, lastOperator, ...other } = ticket; - return { - ...other, - lastOperator, - creator: getUser(userMap[creator]), - lastOperatorUser: getUser(userMap[lastOperator]), - }; - }); - - if (payload.status === 'open') { - update({ openTotal: total }); - } - update({ ticketList }); - return { list, total }; - }, - async getTicketDetail({ call, update, getParams }) { - const { ticketId } = getParams(); - const detail = await call(getTicketDetail, ticketId); - const userMap = getUserMap(); - detail.author = getUser(userMap[detail.creator]); - detail.lastOperator = getUser(userMap[detail.lastOperator]); - - update({ detail }); - }, - async getTicketComments({ call, update, getParams }) { - const { ticketId } = getParams(); - const { comments: list, total } = await call(getComments, ticketId); - const userMap = getUserMap(); - - list.forEach((comment) => { - if (userMap[comment.userID]) { - // eslint-disable-next-line no-param-reassign - comment.author = getUser(userMap[comment.userID]); - } - }); - update({ comments: list, commentsTotal: total }); - }, - async createTicketComments({ call, getParams }, payload: Omit) { - const { ticketId } = getParams(); - const { loginUser } = userStore.getState((s) => s); - await call(createComment, { - ...payload, - ticketID: parseInt(ticketId, 10), - userID: loginUser.id, - }); - - await ticketStore.effects.getTicketComments(); - }, - async closeTicket({ call, getParams }) { - const { ticketId } = getParams(); - await call(closeTicket, parseInt(ticketId, 10)); - await ticketStore.effects.getTicketDetail(); - }, - async addTicket({ call }, payload: PROBLEM.CreateBody) { - const { loginUser } = userStore.getState((s) => s); - await call(addTicket, { ...payload, userID: loginUser.id }); - }, - async reopenTicket({ call, getParams }) { - const { ticketId } = getParams(); - await call(reopenTicket, ticketId); - }, - }, - reducers: { - clearTicketDetail(state) { - state.detail = {} as PROBLEM.Ticket; - }, - clearTicketList(state) { - state.ticketList = []; - }, - }, -}); - -export default ticketStore; diff --git a/shell/app/modules/application/stores/repo.ts b/shell/app/modules/application/stores/repo.ts index 2404423666..a1c572cab5 100644 --- a/shell/app/modules/application/stores/repo.ts +++ b/shell/app/modules/application/stores/repo.ts @@ -78,7 +78,7 @@ const buildIdParams = (appId: string, info: REPOSITORY.IInfo) => { return { appId, commitId: info.commitId, branch: currentBranch }; }; -const getSubList = (info: Obj, { projectId, appId }: { projectId: string; appId: string }) => { +export const getSubList = (info: Obj, { projectId, appId }: { projectId: string; appId: string }) => { const location = window.location as any; const repoRoot = goTo.resolve.repo({ projectId, appId }); const branchInQuery = location.query && location.query.cb; @@ -94,22 +94,26 @@ const getSubList = (info: Obj, { projectId, appId }: { projectId: string; appId: return [ { text: i18n.t('dop:code'), + tabKey: currentBranch ? `repo/tree/${currentBranch}` : 'repo', href: getHref(currentBranch ? `/tree/${currentBranch}` : ''), isActive: (key: string) => key === repoRoot || key.startsWith(`${repoRoot}/tree`) || key.startsWith(`${repoRoot}/backup`), }, { text: i18n.t('dop:commit history'), + tabKey: currentBranch ? `repo/commits/${currentBranch}` : 'repo/commits', href: getHref(currentBranch ? `/commits/${currentBranch}` : '/commits/'), prefix: getHref('/commit'), }, { text: i18n.t('dop:branch management'), + tabKey: currentBranch ? `repo/branches?cb=${currentBranch}` : 'repo/branches', href: getHref(currentBranch ? `/branches?cb=${currentBranch}` : '/branches'), isActive: (key: string) => key.startsWith(`${repoRoot}/branches`) || key.startsWith(`${repoRoot}/tags`), }, { text: i18n.t('dop:merge requests'), + tabKey: currentBranch ? `repo/mr/open?cb=${currentBranch}` : 'repo/mr/open', href: getHref(currentBranch ? `/mr/open?cb=${currentBranch}` : '/mr/open'), isActive: (key: string) => key.startsWith(`${repoRoot}/mr`), }, diff --git a/shell/app/modules/application/stores/test.ts b/shell/app/modules/application/stores/test.ts index 9ba1370628..c653bc49dc 100644 --- a/shell/app/modules/application/stores/test.ts +++ b/shell/app/modules/application/stores/test.ts @@ -12,33 +12,8 @@ // along with this program. If not, see . import * as TestServices from '../services/test'; -import layoutStore from 'layout/stores/layout'; -import i18n from 'i18n'; import { createStore } from 'core/cube'; -import { getDefaultPaging, goTo } from 'common/utils'; - -interface IParams { - projectId: string; - appId: string; -} - -const getSubList = (params: IParams) => { - return [ - { - text: i18n.t('dop:quality reports'), - href: goTo.resolve.appCodeQualityReports(params), - }, - { - text: i18n.t('dop:issues'), - href: goTo.resolve.appCodeQualityIssueOpen(params), - prefix: `${goTo.resolve.appCodeQualityIssue(params)}/`, - }, - { - text: i18n.t('dop:lists'), - href: goTo.resolve.appCodeQuality(params), - }, - ]; -}; +import { getDefaultPaging } from 'common/utils'; const initState = { testTypes: [], @@ -53,17 +28,6 @@ const initState = { const test = createStore({ name: 'applicationTest', - subscriptions({ listenRoute }: IStoreSubs) { - const setSubList = (params: IParams) => - layoutStore.reducers.setSubSiderSubList({ - test: getSubList(params), - }); - listenRoute(({ isIn, params }: IRouteInfo) => { - if (isIn('application')) { - setSubList(params as any); - } - }); - }, state: initState, effects: { async getTestTypes({ call, update }) { @@ -77,11 +41,9 @@ const test = createStore({ { applicationId, ...payload }, { paging: { key: 'testListPaging' } }, ); - const preList = select((state) => state.list); - const newList = preList.concat(list || []); update({ - list: newList, + list, testListQueryParams: { applicationId, ...payload }, }); @@ -90,9 +52,8 @@ const test = createStore({ total, }; }, - async getTestDetail({ call, select, update, getParams }) { + async getTestDetail({ call, select, update }, testId: number) { test.reducers.clearTestDetail(); - const { testId } = getParams(); const testDetailCache = select((state) => state.testDetailCache); let testDetail = testDetailCache[testId]; if (testDetail) { diff --git a/shell/app/modules/application/tabs.tsx b/shell/app/modules/application/tabs.tsx new file mode 100644 index 0000000000..e7db7dbc03 --- /dev/null +++ b/shell/app/modules/application/tabs.tsx @@ -0,0 +1,188 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React from 'react'; +import i18n from 'i18n'; +import { Icon as CustomIcon, RadioTabs } from 'common'; +import permStore from 'user/stores/permission'; +import layoutStore from 'layout/stores/layout'; +import { Tooltip } from 'antd'; +import { goTo } from 'app/common/utils'; +import appStore from './stores/application'; +import routeInfoStore from 'core/stores/route'; +import { appMode } from './common/config'; +import { filter } from 'lodash'; +import { getSubList } from './stores/repo'; + +interface ITab { + show?: boolean; + key: string; + href: string; + icon: string; + text: string; +} +export const APP_TABS = () => { + const repoMenu = layoutStore.useStore((s) => s.subList.repo); + const [repoKey, commitKey, branchKey, mrKey] = repoMenu?.length + ? repoMenu.map((a: { tabKey: string }) => a.tabKey) + : ['repo', 'repo/commits', 'repo/branches', 'repo/mr/open']; + const appDetail = appStore.useStore((s) => s.detail); + + const { mode, displayName } = appDetail; + const perm = permStore.getState((s) => s.app); + const back = { + key: '../', + hrefType: 'back', + name: ( + + + {i18n.t('dop:application')} + {displayName ? ( + displayName.length > 16 ? ( + + ({displayName.slice(0, 16)}...) + + ) : ( + `(${displayName})` + ) + ) : ( + '' + )} + + ), + }; + const repo = { + show: perm.repo.read.pass, + key: repoKey, + // href: goTo.resolve.repo(), + name: i18n.t('dop:code'), + isActive: (activeKey: string) => { + return activeKey === 'repo' || activeKey.startsWith('repo/tree'); + }, + }; + const commit = { + show: perm.repo.read.pass, + key: commitKey, + // href: goTo.resolve.commits(), + name: i18n.t('dop:commits'), + isActive: (activeKey: string) => activeKey.startsWith('repo/commits'), + }; + const branch = { + show: perm.repo.read.pass, + key: branchKey, + // href: goTo.resolve.repo(), + name: i18n.t('dop:branch'), + isActive: (activeKey: string) => activeKey.startsWith('repo/branches'), + }; + const mr = { + show: perm.repo.read.pass, + key: mrKey, + // href: goTo.resolve.appOpenMr(), + name: i18n.t('dop:merge request'), + isActive: (activeKey: string) => activeKey.startsWith('repo/mr'), + }; + const pipeline = { + show: perm.pipeline.read.pass, + key: 'pipeline', + // href: goTo.resolve.pipelineRoot(), + name: i18n.t('pipeline'), + }; + const dataTask = { + show: perm.dataTask.read.pass, + key: 'dataTask', + // href: goTo.resolve.dataTaskRoot(), + name: `${i18n.t('dop:data task')}`, + }; + const dataModel = { + show: perm.dataModel.read.pass, + key: 'dataModel', + // href: goTo.resolve.appDataModel(), + name: `${i18n.t('dop:data model')}`, + }; + const dataMarket = { + show: perm.dataMarket.read.pass, + key: 'dataMarket', + // href: goTo.resolve.appDataMarket(), + name: `${i18n.t('dop:data market')}`, + }; + const quality = { + show: perm.codeQuality.read.pass, + key: 'quality', + // href: goTo.resolve.appCodeQuality(), + name: i18n.t('dop:code quality'), + }; + + const apiDesign = { + show: perm.apiDesign.read.pass, + key: 'apiDesign', + // href: goTo.resolve.appApiDesign(), + name: i18n.t('dop:API design'), + }; + + const setting = { + show: perm.setting.read.pass, + key: 'setting', + // href: goTo.resolve.appSetting(), + name: i18n.t('dop:application setting'), + }; + + const modeMap = { + [appMode.SERVICE]: [repo, commit, branch, mr, pipeline, apiDesign, quality, setting], + [appMode.PROJECT_SERVICE]: [repo, commit, branch, mr, pipeline, quality, setting], + [appMode.MOBILE]: [repo, commit, branch, mr, pipeline, apiDesign, quality, setting], + [appMode.LIBRARY]: [repo, commit, branch, mr, pipeline, apiDesign, quality, setting], + [appMode.BIGDATA]: [repo, commit, branch, mr, dataTask, dataModel, dataMarket, setting], + [appMode.ABILITY]: [quality, setting], + }; + + const tabs = filter(modeMap[mode], (item: ITab) => item.show !== false); + console.log('tabs', tabs); + return [back, ...tabs]; + // const currentRoute = routeInfoStore.useStore(s => s.currentRoute); + // if (currentRoute.mark === "application") { + // const firstAvailableTab = tabs.find(t => t.show && t.href); + // // appStore.reducers.onAppIndexEnter(appMenu); + // if (firstAvailableTab?.href) { + // console.log('currentRoute', currentRoute); + // goTo(firstAvailableTab.href, { replace: true }) + // return; + // } + // } + // return tabs; +}; + +// export const getQualityTabs = (params: Obj) => { +// return [ +// { value: 'quality', label: i18n.t('dop:code quality'), onClick: () => goTo(goTo.resolve.appCodeQualityReports(params)) }, +// { value: 'ticket', label: i18n.t('dop:ticket'), onClick: () => goTo(goTo.resolve.appCodeQualityIssueOpen(params)) }, +// { value: 'test', label: i18n.t('dop:report'), onClick: () => goTo(goTo.resolve.appCodeQuality(params)) }, +// ] +// } + +// export const prependTabs = (tabs: typeof getQualityTabs, activeTabKey: string) => (Comp: React.ElementType) => (props: any) => { +// const _tabs = tabs(props.match.params); + +// return ( +// <> +// { +// tab?.onClick(); +// }} +// className="mb-2" +// /> +// +// +// ) +// } From c52b4a7452d224785ee9fbcead9a6c6ada463a21 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 11:19:01 +0800 Subject: [PATCH 02/16] fix: locale --- locales/en.json | 8 ++------ locales/zh.json | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/locales/en.json b/locales/en.json index 8d3a8e513a..91a6162b50 100644 --- a/locales/en.json +++ b/locales/en.json @@ -13,6 +13,7 @@ "API strategy": "API strategy", "API test": "API test", "API version": "API version", + "APIGateway": "APIGateway", "Alarm": "Alarm", "Alibaba Cloud": "Alibaba Cloud", "All members of the organization can view": "all members of the current company can view", @@ -37,14 +38,12 @@ "Chengdu": "Chengdu", "China": "China", "Cloud management": "Cloud management", - "Code": "Code", "Config": "Config", "Contact your organization administrator to invite you to join": "Contact your organization administrator to invite you to join", "DB error": "DB error", "Dashboard": "Dashboard", "Data source: API call management": "Data source: API call management", "December": "December", - "Deploy": "Deploy", "Deployment not allowed in {env} in network block period.": "during the period of network closure, deployment is prohibited in the {env}!", "DingTalk": "DingTalk", "DingTalk address": "DingTalk address", @@ -78,16 +77,15 @@ "July": "July", "June": "June", "Kuala lumpur": "Kuala lumpur", + "LogAnalyze": "LogAnalyze", "London": "London", "Malaysia": "Malaysia", "March": "March", - "Market": "Market", "May": "May", "Members under the project/application associated with the current API can view it.": "members under the project/application associated with the current API can view", "Memory quota": "Memory quota", "Middle-east & India": "Middle-east & India", "Mobile": "Mobile", - "Model": "Model", "Mon": "Mon", "Mumbai": "Mumbai", "New version available": "New version available", @@ -116,7 +114,6 @@ "Please enter version number, such as x.y.z.": "please enter version number, such as x.y.z", "Project": "Project", "Qingdao": "Qingdao", - "Quality": "Quality", "Release": "Release", "Report": "Report", "Resource": "Resource", @@ -145,7 +142,6 @@ "Supported Android package versions": "Supported Android package versions", "Supported iOS package versions": "Supported iOS package versions", "Sydney": "Sydney", - "Task": "Task", "Test": "Test", "Test Management": "Test Management", "The administrator has received your request. Please go to": "the administrator has received your request. please come to ", diff --git a/locales/zh.json b/locales/zh.json index 76e6f00031..32dd45afc2 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -13,6 +13,7 @@ "API strategy": "API 策略", "API test": "API 测试", "API version": "API 版本", + "APIGateway": "API 网关", "Alarm": "告警", "Alibaba Cloud": "阿里云", "All members of the organization can view": "当前组织下所有成员都可以查看", @@ -37,14 +38,12 @@ "Chengdu": "成都", "China": "中国", "Cloud management": "多云管理平台", - "Code": "代码", "Config": "配置", "Contact your organization administrator to invite you to join": "联系组织管理员,邀请您加入", "DB error": "数据库错误", "Dashboard": "大盘", "Data source: API call management": "数据来源:我的 API 申请调用管理", "December": "12月", - "Deploy": "部署", "Deployment not allowed in {env} in network block period.": "组织处于封网期间,{env}禁止部署!", "DingTalk": "钉钉群", "DingTalk address": "钉钉地址", @@ -78,16 +77,15 @@ "July": "7月", "June": "6月", "Kuala lumpur": "吉隆坡", + "LogAnalyze": "日志分析", "London": "伦敦", "Malaysia": "马来西亚", "March": "3月", - "Market": "集市", "May": "5月", "Members under the project/application associated with the current API can view it.": "当前API关联的项目/应用下的成员可以查看", "Memory quota": "内存配额", "Middle-east & India": "中东与印度", "Mobile": "移动", - "Model": "模型", "Mon": "一", "Mumbai": "孟买", "New version available": "新版本可用", @@ -116,7 +114,6 @@ "Please enter version number, such as x.y.z.": "请输入版本号,如:x.y.z", "Project": "项目", "Qingdao": "青岛", - "Quality": "质量", "Release": "发布", "Report": "报告", "Resource": "资源", @@ -145,7 +142,6 @@ "Supported Android package versions": "支持的 Android 包版本", "Supported iOS package versions": "支持的 iOS 包版本", "Sydney": "悉尼", - "Task": "任务", "Test": "测试", "Test Management": "测试管理", "The administrator has received your request. Please go to": "管理员已收到您的请求。请稍后到", From 7ef1e63208163ab319f0e75c69544d777ed7c42d Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 14:41:22 +0800 Subject: [PATCH 03/16] fix: i18n --- shell/app/locales/en.json | 2 +- shell/app/locales/zh.json | 2 +- shell/app/modules/application/pages/problem/problem-list.tsx | 2 +- shell/app/modules/application/tabs.tsx | 2 +- shell/app/user/stores/_perm-app.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/app/locales/en.json b/shell/app/locales/en.json index a5d527c046..26d99371ef 100644 --- a/shell/app/locales/en.json +++ b/shell/app/locales/en.json @@ -917,6 +917,7 @@ "add configuration": "add configuration", "add data source": "add data source", "add enum value": "add enum value", + "add issue": "add issue", "add label": "add label", "add pipeline": "add pipeline", "add relation to MR": "add relation to MR", @@ -2812,7 +2813,6 @@ "waiting for deployment": "waiting for deployment" }, "user": { - "API design": "API design", "API management": "API management", "API resource editing": "API resource editing", "Assignee": "Assignee", diff --git a/shell/app/locales/zh.json b/shell/app/locales/zh.json index 366cb251f8..de00bd0343 100644 --- a/shell/app/locales/zh.json +++ b/shell/app/locales/zh.json @@ -917,6 +917,7 @@ "add configuration": "添加配置", "add data source": "添加数据源", "add enum value": "添加枚举值", + "add issue": "新建问题", "add label": "添加标签", "add pipeline": "新建流水线", "add relation to MR": "关联了MR", @@ -2812,7 +2813,6 @@ "waiting for deployment": "等待部署" }, "user": { - "API design": "API设计", "API management": "API管理", "API resource editing": "API 资源编辑", "Assignee": "处理者", diff --git a/shell/app/modules/application/pages/problem/problem-list.tsx b/shell/app/modules/application/pages/problem/problem-list.tsx index b844fdca7c..98111bef2b 100644 --- a/shell/app/modules/application/pages/problem/problem-list.tsx +++ b/shell/app/modules/application/pages/problem/problem-list.tsx @@ -227,7 +227,7 @@ export const ProblemList = () => {
updater.detailVisibleId(0)} /> diff --git a/shell/app/modules/application/tabs.tsx b/shell/app/modules/application/tabs.tsx index e7db7dbc03..e50f9ec407 100644 --- a/shell/app/modules/application/tabs.tsx +++ b/shell/app/modules/application/tabs.tsx @@ -126,7 +126,7 @@ export const APP_TABS = () => { show: perm.apiDesign.read.pass, key: 'apiDesign', // href: goTo.resolve.appApiDesign(), - name: i18n.t('dop:API design'), + name: 'API', }; const setting = { diff --git a/shell/app/user/stores/_perm-app.ts b/shell/app/user/stores/_perm-app.ts index 42032bd38a..6bf561eeef 100644 --- a/shell/app/user/stores/_perm-app.ts +++ b/shell/app/user/stores/_perm-app.ts @@ -306,7 +306,7 @@ export const appPerm = { }, }, apiDesign: { - name: i18n.t('user:API design'), + name: 'API', read: { pass: false, role: ['Owner', 'Lead', 'Dev', 'QA', 'Support', 'Ops'], From 5235a556fa80a921fe32afbebfda0abe5fd7e269 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 14:42:53 +0800 Subject: [PATCH 04/16] feat: add app switch in tabs --- shell/app/common/components/menu/index.tsx | 4 ++- .../page-container/components/header.scss | 5 +++- .../application/common/app-selector.tsx | 6 ++-- shell/app/modules/application/tabs.tsx | 30 ++++++++----------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/shell/app/common/components/menu/index.tsx b/shell/app/common/components/menu/index.tsx index 6964e9240c..dee4a95028 100644 --- a/shell/app/common/components/menu/index.tsx +++ b/shell/app/common/components/menu/index.tsx @@ -24,6 +24,7 @@ interface IMenuItem { disabled?: boolean; split?: boolean; isActive?: (v: string) => boolean; + className?: string; } interface IMenu { @@ -115,12 +116,13 @@ const PureMenu = (props: IMenu) => {
    {finalMenus.map((menu: Merge) => { - const { disabled, key, name, hrefType, split, isActive } = menu; + const { disabled, key, name, hrefType, split, isActive, className: itemClass = '' } = menu; const menuItemClass = classnames({ 'tab-menu-item': true, 'tab-menu-disabled': disabled, 'tab-split-line-before': split, active: isActive ? isActive(activeKey) : activeKey === key, + [itemClass]: !!itemClass, }); return (
  • { const curApp = appStore.getState((s) => s.detail); const name = val.displayName || val.name || curApp.displayName || curApp.name || ''; return ( -
    +
    - +
    @@ -120,7 +120,7 @@ const headAppRender = (val: any = {}) => { export const HeadAppSelector = () => { const { appId, projectId } = routeInfoStore.useStore((s) => s.params); return ( -
    +
    { : ['repo', 'repo/commits', 'repo/branches', 'repo/mr/open']; const appDetail = appStore.useStore((s) => s.detail); - const { mode, displayName } = appDetail; + const { mode } = appDetail; const perm = permStore.getState((s) => s.app); const back = { key: '../', hrefType: 'back', name: ( - - + + {i18n.t('dop:application')} - {displayName ? ( - displayName.length > 16 ? ( - - ({displayName.slice(0, 16)}...) - - ) : ( - `(${displayName})` - ) - ) : ( - '' - )} ), }; + const appSwitch = { + key: '_', + className: 'mr-4', + name: , + }; const repo = { show: perm.repo.read.pass, key: repoKey, // href: goTo.resolve.repo(), + split: true, name: i18n.t('dop:code'), isActive: (activeKey: string) => { return activeKey === 'repo' || activeKey.startsWith('repo/tree'); @@ -147,7 +141,7 @@ export const APP_TABS = () => { const tabs = filter(modeMap[mode], (item: ITab) => item.show !== false); console.log('tabs', tabs); - return [back, ...tabs]; + return [back, appSwitch, ...tabs]; // const currentRoute = routeInfoStore.useStore(s => s.currentRoute); // if (currentRoute.mark === "application") { // const firstAvailableTab = tabs.find(t => t.show && t.href); From 573024807209d682d4a771614615cabb850dfbb3 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 15:10:57 +0800 Subject: [PATCH 05/16] fix: unify tab text and breadcrumb text --- shell/app/layout/pages/tab/tab.tsx | 2 +- shell/app/modules/application/router.ts | 72 ++----------------------- shell/app/modules/application/tabs.tsx | 1 - 3 files changed, 5 insertions(+), 70 deletions(-) diff --git a/shell/app/layout/pages/tab/tab.tsx b/shell/app/layout/pages/tab/tab.tsx index bd5ecf24f8..8994c4f794 100644 --- a/shell/app/layout/pages/tab/tab.tsx +++ b/shell/app/layout/pages/tab/tab.tsx @@ -31,7 +31,7 @@ interface IProps { path: string; } -export class PureTab extends React.PureComponent { +class PureTab extends React.PureComponent { render() { const { routes, path } = this.props; const lastRouteWithPath = find(routes, 'relativePath'); diff --git a/shell/app/modules/application/router.ts b/shell/app/modules/application/router.ts index 72afece4bc..073e2d9c30 100644 --- a/shell/app/modules/application/router.ts +++ b/shell/app/modules/application/router.ts @@ -13,7 +13,7 @@ import getRuntimeRouter from 'runtime/router'; import i18n from 'i18n'; -import { APP_TABS, getQualityTabs, prependTabs } from './tabs'; +import { APP_TABS } from './tabs'; function getAppRouter(): RouteConfigItem { return { @@ -64,7 +64,7 @@ function getAppRouter(): RouteConfigItem { tabs: APP_TABS, alwaysShowTabKey: 'repo/branches', ignoreTabQuery: true, - breadcrumbName: i18n.t('dop:branch management'), + breadcrumbName: i18n.t('dop:branch'), routes: [ { path: 'compare/:branches*', @@ -81,15 +81,6 @@ function getAppRouter(): RouteConfigItem { }, ], }, - // { - // path: 'tags', - // tabs: APP_TABS, - // alwaysShowTabKey: 'repo/branches', - // ignoreTabQuery: true, - // breadcrumbName: i18n.t('dop:branch management'), - // getComp: (cb) => cb(import('application/pages/repo/repo-tag')), - // layout: { noWrapper: true }, - // }, { path: 'commit', tabs: APP_TABS, @@ -205,71 +196,16 @@ function getAppRouter(): RouteConfigItem { }, { path: 'quality', - breadcrumbName: i18n.t('dop:quality reports'), + breadcrumbName: i18n.t('dop:code quality'), tabs: APP_TABS, ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/quality/entry')), - // wrapper: prependTabs(getQualityTabs, 'quality'), layout: { noWrapper: true }, - // routes: [ - // { - // breadcrumbName: i18n.t('dop:lists'), - // getComp: (cb) => cb(import('application/pages/test/test-list')), - // wrapper: prependTabs(getQualityTabs, 'test'), - // layout: { noWrapper: true }, - // }, - // { - // path: ':testId', - // breadcrumbName: i18n.t('dop:test detail'), - // getComp: (cb) => cb(import('application/pages/test/test-detail-container')), - // wrapper: prependTabs(getQualityTabs, 'test'), - // layout: { noWrapper: true }, - // }, - // ], }, - // { - // path: 'ticket/:ticketType', - // breadcrumbName: i18n.t('dop:issues'), - // tabs: APP_TABS, - // alwaysShowTabKey: 'quality', - // routes: [ - // { - // getComp: (cb) => cb(import('application/pages/problem')), - // wrapper: prependTabs(getQualityTabs, 'ticket'), - // layout: { noWrapper: true }, - // }, - // { - // path: ':ticketId', - // breadcrumbName: i18n.t('dop:issue detail'), - // getComp: (cb) => cb(import('application/pages/problem/problem-detail')), - // wrapper: prependTabs(getQualityTabs, 'ticket'), - // layout: { noWrapper: true }, - // }, - // ], - // }, - // { - // path: 'test', - // tabs: APP_TABS, - // routes: [ - // { - // breadcrumbName: i18n.t('dop:lists'), - // getComp: (cb) => cb(import('application/pages/test/test-list')), - // wrapper: prependTabs(getQualityTabs, 'test'), - // layout: { noWrapper: true }, - // }, - // { - // path: ':testId', - // breadcrumbName: i18n.t('dop:test detail'), - // getComp: (cb) => cb(import('application/pages/test/test-detail-container')), - // wrapper: prependTabs(getQualityTabs, 'test'), - // layout: { noWrapper: true }, - // }, - // ], - // }, { path: 'apiDesign', mark: 'apiDesign', - breadcrumbName: i18n.t('dop:API design'), + breadcrumbName: 'API', tabs: APP_TABS, ignoreTabQuery: true, routes: [ diff --git a/shell/app/modules/application/tabs.tsx b/shell/app/modules/application/tabs.tsx index ed5e2ba050..28c476168a 100644 --- a/shell/app/modules/application/tabs.tsx +++ b/shell/app/modules/application/tabs.tsx @@ -140,7 +140,6 @@ export const APP_TABS = () => { }; const tabs = filter(modeMap[mode], (item: ITab) => item.show !== false); - console.log('tabs', tabs); return [back, appSwitch, ...tabs]; // const currentRoute = routeInfoStore.useStore(s => s.currentRoute); // if (currentRoute.mark === "application") { From b06c40f9e4ed16d458aacbdc76e03490fdb63773 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 15:27:00 +0800 Subject: [PATCH 06/16] fix: update i18n --- shell/app/locales/en.json | 4 ---- shell/app/locales/zh.json | 6 +----- shell/app/modules/application/router.ts | 2 +- shell/app/modules/application/stores/repo.ts | 2 +- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/shell/app/locales/en.json b/shell/app/locales/en.json index 26d99371ef..e795e8fd84 100644 --- a/shell/app/locales/en.json +++ b/shell/app/locales/en.json @@ -1108,7 +1108,6 @@ "commit ID": "commit ID", "commit branch": "commit branch", "commit details": "commit details", - "commit history": "commits", "commit message": "commit message", "commit unchanged": "commit unchanged", "commitID-input-tip": "please enter 40 characters consisting of lowercase letters and numbers", @@ -1378,13 +1377,11 @@ "is empty": "is empty", "issue": "issue", "issue custom fields": "issue custom fields", - "issue detail": "issue detail", "issue field": "issue field", "issue list": "issue list", "issue type": "issue type", "issue workflow": "issue workflow", "issue-workflow-config-tip": "Project issues include milestone, requirement, bug and task. And you can set up workflow state for each issue if you want", - "issues": "issues", "issues associated label will be deleted, confirm to delete?": "issues associated label will be deleted, confirm to delete?", "iteration": "iteration", "iteration goal": "iteration goal", @@ -1836,7 +1833,6 @@ "terminate": "terminate", "test case": "test case", "test days": "test days", - "test detail": "test detail", "test leader": "test leader", "test output": "test output", "test participant": "test participant", diff --git a/shell/app/locales/zh.json b/shell/app/locales/zh.json index de00bd0343..630de28737 100644 --- a/shell/app/locales/zh.json +++ b/shell/app/locales/zh.json @@ -1108,11 +1108,10 @@ "commit ID": "提交ID", "commit branch": "提交分支", "commit details": "提交详情", - "commit history": "提交历史", "commit message": "提交信息", "commit unchanged": "提交不变", "commitID-input-tip": "请输入40位由小写字母和数字组成的字符", - "commits": "提交历史", + "commits": "提交", "committed": "已提交", "compare": "比较", "comparison result has been folded": "对比结果已被收起", @@ -1378,13 +1377,11 @@ "is empty": "为空", "issue": "事项", "issue custom fields": "事项自定义字段", - "issue detail": "问题详情", "issue field": "事项字段", "issue list": "事项列表", "issue type": "事项类型", "issue workflow": "事项工作流", "issue-workflow-config-tip": "事项类型覆盖里程碑、需求、缺陷和任务,可以对每个事项类型进行不同配置。如需改变工作流类型,您可以选择具体的事项类型进行修改。", - "issues": "问题列表", "issues associated label will be deleted, confirm to delete?": "将会删除事项中关联的标签,确认删除?", "iteration": "迭代", "iteration goal": "迭代目标", @@ -1836,7 +1833,6 @@ "terminate": "终止", "test case": "测试用例", "test days": "测试天数", - "test detail": "测试详情", "test leader": "测试负责人", "test output": "测试输出", "test participant": "测试参与人", diff --git a/shell/app/modules/application/router.ts b/shell/app/modules/application/router.ts index 073e2d9c30..bfa4f2ba62 100644 --- a/shell/app/modules/application/router.ts +++ b/shell/app/modules/application/router.ts @@ -97,7 +97,7 @@ function getAppRouter(): RouteConfigItem { }, { path: 'commits/(.*)', // commits后面可能有分支(包含/),commit后面只有commitId - breadcrumbName: i18n.t('dop:commit history'), + breadcrumbName: i18n.t('dop:commits'), tabs: APP_TABS, alwaysShowTabKey: 'repo/commits', ignoreTabQuery: true, diff --git a/shell/app/modules/application/stores/repo.ts b/shell/app/modules/application/stores/repo.ts index a1c572cab5..b72891c410 100644 --- a/shell/app/modules/application/stores/repo.ts +++ b/shell/app/modules/application/stores/repo.ts @@ -100,7 +100,7 @@ export const getSubList = (info: Obj, { projectId, appId }: { projectId: string; key === repoRoot || key.startsWith(`${repoRoot}/tree`) || key.startsWith(`${repoRoot}/backup`), }, { - text: i18n.t('dop:commit history'), + text: i18n.t('dop:commits'), tabKey: currentBranch ? `repo/commits/${currentBranch}` : 'repo/commits', href: getHref(currentBranch ? `/commits/${currentBranch}` : '/commits/'), prefix: getHref('/commit'), From c637d06144287efbb894b776387dee7244fd5af8 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 15:43:40 +0800 Subject: [PATCH 07/16] fix: update setting text --- shell/app/interface/global.d.ts | 2 ++ shell/app/locales/en.json | 1 + shell/app/locales/zh.json | 1 + shell/app/modules/application/router.ts | 2 +- shell/app/modules/application/tabs.tsx | 4 ++-- 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/shell/app/interface/global.d.ts b/shell/app/interface/global.d.ts index 8d69d3d495..25b10cfc74 100644 --- a/shell/app/interface/global.d.ts +++ b/shell/app/interface/global.d.ts @@ -354,6 +354,8 @@ interface ROUTE_TABS { name: string | JSX.Element; show?: boolean; hrefType?: string; + isActive: () => boolean; + [key: string]: any; } interface RouteConfigItem { diff --git a/shell/app/locales/en.json b/shell/app/locales/en.json index e795e8fd84..c54ca1b885 100644 --- a/shell/app/locales/en.json +++ b/shell/app/locales/en.json @@ -1757,6 +1757,7 @@ "set as default": "set as default", "set default branch successfully": "set default branch successfully", "set the default merge request description template": "set the default merge request description template", + "setting": "setting", "settings updated successfully": "settings updated successfully", "severity": "severity", "severity-fatal": "fatal", diff --git a/shell/app/locales/zh.json b/shell/app/locales/zh.json index 630de28737..16cc344f77 100644 --- a/shell/app/locales/zh.json +++ b/shell/app/locales/zh.json @@ -1757,6 +1757,7 @@ "set as default": "设为默认", "set default branch successfully": "设置默认分支成功", "set the default merge request description template": "设置默认的合并请求描述模板", + "setting": "设置", "settings updated successfully": "更新设置成功", "severity": "严重程度", "severity-fatal": "致命", diff --git a/shell/app/modules/application/router.ts b/shell/app/modules/application/router.ts index bfa4f2ba62..d72e3d2e0a 100644 --- a/shell/app/modules/application/router.ts +++ b/shell/app/modules/application/router.ts @@ -217,7 +217,7 @@ function getAppRouter(): RouteConfigItem { }, { path: 'setting', - breadcrumbName: i18n.t('dop:application setting'), + breadcrumbName: i18n.t('dop:setting'), tabs: APP_TABS, ignoreTabQuery: true, layout: { fullHeight: true }, diff --git a/shell/app/modules/application/tabs.tsx b/shell/app/modules/application/tabs.tsx index 28c476168a..b9f6112689 100644 --- a/shell/app/modules/application/tabs.tsx +++ b/shell/app/modules/application/tabs.tsx @@ -127,7 +127,7 @@ export const APP_TABS = () => { show: perm.setting.read.pass, key: 'setting', // href: goTo.resolve.appSetting(), - name: i18n.t('dop:application setting'), + name: i18n.t('dop:setting'), }; const modeMap = { @@ -140,7 +140,7 @@ export const APP_TABS = () => { }; const tabs = filter(modeMap[mode], (item: ITab) => item.show !== false); - return [back, appSwitch, ...tabs]; + return [back, appSwitch, ...tabs] as ROUTE_TABS[]; // const currentRoute = routeInfoStore.useStore(s => s.currentRoute); // if (currentRoute.mark === "application") { // const firstAvailableTab = tabs.find(t => t.show && t.href); From aa6b1c67a548f58d76871333083e9763aad87eac Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 16:13:59 +0800 Subject: [PATCH 08/16] feat: update check default app tab --- shell/app/modules/application/router.ts | 2 +- .../application/stores/application.tsx | 34 +-------------- shell/app/modules/application/tabs.tsx | 41 ++++++++++--------- 3 files changed, 23 insertions(+), 54 deletions(-) diff --git a/shell/app/modules/application/router.ts b/shell/app/modules/application/router.ts index d72e3d2e0a..22e256d8f0 100644 --- a/shell/app/modules/application/router.ts +++ b/shell/app/modules/application/router.ts @@ -20,7 +20,7 @@ function getAppRouter(): RouteConfigItem { path: 'apps/:appId', mark: 'application', breadcrumbName: '{appName}', - // tabs: APP_TABS, + tabs: APP_TABS, routes: [ // TODO: remove // { diff --git a/shell/app/modules/application/stores/application.tsx b/shell/app/modules/application/stores/application.tsx index c4727bd4ac..896ef541a3 100644 --- a/shell/app/modules/application/stores/application.tsx +++ b/shell/app/modules/application/stores/application.tsx @@ -42,14 +42,6 @@ const initState: IState = { branchInfo: [], }; -// 检测是否是app的首页,重定向 -const checkIsAppIndex = (appMenu?: any) => { - const appEnterRegex = /\/dop\/projects\/\d+\/apps\/\d+$/; - if (appEnterRegex.test(location.pathname)) { - appStore.reducers.onAppIndexEnter(appMenu); - } -}; -let loadingInApp = false; const appStore = createStore({ name: 'application', state: initState, @@ -60,7 +52,6 @@ const appStore = createStore({ if (isIn('application')) { if (`${curAppId}` !== `${appId}`) { // 应用切换后才重新checkRouteAuth - loadingInApp = true; appStore.reducers.updateCurAppId(appId); breadcrumbStore.reducers.setInfo('appName', ''); permStore.effects.checkRouteAuth({ @@ -69,22 +60,9 @@ const appStore = createStore({ routeMark: 'application', cb() { appStore.effects.getBranchInfo({ appId }); // 请求app下全量branch - appStore.effects.getAppDetail(appId).then((detail: IApplication) => { - // const curAppTabs = getAppMenu({ appDetail: detail }); - loadingInApp = false; - // layoutStore.reducers.setAppTabs(curAppTabs); - // layoutStore.reducers.setSubSiderInfoMap({ - // menu: curAppMenu, - // key: 'application', - // detail: { ...detail, icon: theme.appIcon }, - // getHeadName: () => , - // }); - checkIsAppIndex(); // 设置app的menu后,重定向 - }); + appStore.effects.getAppDetail(appId); }, }); - } else { - !loadingInApp && checkIsAppIndex(); } } @@ -193,16 +171,6 @@ const appStore = createStore({ updateCurAppId(state, appId = '') { state.curAppId = appId; }, - onAppIndexEnter(_state, appMenu?: any[]) { - // let curAppMenu: any = appMenu; - // if (!curAppMenu) { - // const subSiderInfoMap = layoutStore.getState((s) => s.subSiderInfoMap); - // curAppMenu = get(subSiderInfoMap, 'application.menu'); - // } - // app首页重定向到第一个菜单链接 - // const rePathname = get(curAppMenu, '[0].href'); - goTo(goTo.pages.repo, { replace: true }); - }, }, }); diff --git a/shell/app/modules/application/tabs.tsx b/shell/app/modules/application/tabs.tsx index b9f6112689..b0940cdcc6 100644 --- a/shell/app/modules/application/tabs.tsx +++ b/shell/app/modules/application/tabs.tsx @@ -22,6 +22,7 @@ import { appMode } from './common/config'; import { filter } from 'lodash'; import { getSubList } from './stores/repo'; import { HeadAppSelector } from './common/app-selector'; +import { goTo } from 'app/common/utils'; interface ITab { show?: boolean; @@ -57,7 +58,7 @@ export const APP_TABS = () => { const repo = { show: perm.repo.read.pass, key: repoKey, - // href: goTo.resolve.repo(), + href: goTo.resolve.repo(), split: true, name: i18n.t('dop:code'), isActive: (activeKey: string) => { @@ -88,45 +89,45 @@ export const APP_TABS = () => { const pipeline = { show: perm.pipeline.read.pass, key: 'pipeline', - // href: goTo.resolve.pipelineRoot(), + href: goTo.resolve.pipelineRoot(), name: i18n.t('pipeline'), }; const dataTask = { show: perm.dataTask.read.pass, key: 'dataTask', - // href: goTo.resolve.dataTaskRoot(), + href: goTo.resolve.dataTaskRoot(), name: `${i18n.t('dop:data task')}`, }; const dataModel = { show: perm.dataModel.read.pass, key: 'dataModel', - // href: goTo.resolve.appDataModel(), + href: goTo.resolve.appDataModel(), name: `${i18n.t('dop:data model')}`, }; const dataMarket = { show: perm.dataMarket.read.pass, key: 'dataMarket', - // href: goTo.resolve.appDataMarket(), + href: goTo.resolve.appDataMarket(), name: `${i18n.t('dop:data market')}`, }; const quality = { show: perm.codeQuality.read.pass, key: 'quality', - // href: goTo.resolve.appCodeQuality(), + href: goTo.resolve.appCodeQuality(), name: i18n.t('dop:code quality'), }; const apiDesign = { show: perm.apiDesign.read.pass, key: 'apiDesign', - // href: goTo.resolve.appApiDesign(), + href: goTo.resolve.appApiDesign(), name: 'API', }; const setting = { show: perm.setting.read.pass, key: 'setting', - // href: goTo.resolve.appSetting(), + href: goTo.resolve.appSetting(), name: i18n.t('dop:setting'), }; @@ -139,19 +140,19 @@ export const APP_TABS = () => { [appMode.ABILITY]: [quality, setting], }; - const tabs = filter(modeMap[mode], (item: ITab) => item.show !== false); + const tabs = filter(modeMap[mode], (item: ITab) => item.show !== false) as ROUTE_TABS[]; + // console.log('tabs:', tabs); + const currentRoute = routeInfoStore.useStore((s) => s.currentRoute); + React.useEffect(() => { + if (currentRoute.mark === 'application') { + const firstAvailableTab = tabs.find((t) => t.show && t.href); + if (firstAvailableTab?.href) { + console.log('currentRoute', currentRoute); + goTo(firstAvailableTab.href, { replace: true }); + } + } + }, [currentRoute, tabs]); return [back, appSwitch, ...tabs] as ROUTE_TABS[]; - // const currentRoute = routeInfoStore.useStore(s => s.currentRoute); - // if (currentRoute.mark === "application") { - // const firstAvailableTab = tabs.find(t => t.show && t.href); - // // appStore.reducers.onAppIndexEnter(appMenu); - // if (firstAvailableTab?.href) { - // console.log('currentRoute', currentRoute); - // goTo(firstAvailableTab.href, { replace: true }) - // return; - // } - // } - // return tabs; }; // export const getQualityTabs = (params: Obj) => { From 9e30dd0c593c52a070464b69f01fa1988be0c863 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 21:30:18 +0800 Subject: [PATCH 09/16] feat: support back to up in tabs --- shell/app/common/types/route.d.ts | 3 +- shell/app/interface/global.d.ts | 6 +++- .../page-container/components/header.tsx | 26 +++++++++++++-- shell/app/modules/application/router.ts | 32 ++++++++++++++----- shell/app/modules/application/tabs.tsx | 15 +-------- shell/app/modules/project/router.tsx | 23 +++++++------ 6 files changed, 70 insertions(+), 35 deletions(-) diff --git a/shell/app/common/types/route.d.ts b/shell/app/common/types/route.d.ts index 849877bfce..aea117e52c 100644 --- a/shell/app/common/types/route.d.ts +++ b/shell/app/common/types/route.d.ts @@ -23,7 +23,8 @@ interface IRoute { relativePath: string; breadcrumbName?: string | Function; _parent: IRoute; - mark?: string; + mark?: ROUTE_MARK; + backToUp?: string; layout: { [k: string]: any; }; diff --git a/shell/app/interface/global.d.ts b/shell/app/interface/global.d.ts index 25b10cfc74..31068e6b3e 100644 --- a/shell/app/interface/global.d.ts +++ b/shell/app/interface/global.d.ts @@ -309,6 +309,7 @@ type ROUTE_MARK = | 'addonsManage' | 'publisher' | 'project' + | 'projectAppList' | 'issues' | 'iterationDetail' | 'apiManage' @@ -319,6 +320,8 @@ type ROUTE_MARK = | 'deploy' | 'repo' | 'repoTree' + | 'repoBranches' + | 'repoMr' | 'repoCompare' | 'apiDesign' | 'pipeline' @@ -354,7 +357,7 @@ interface ROUTE_TABS { name: string | JSX.Element; show?: boolean; hrefType?: string; - isActive: () => boolean; + isActive?: () => boolean; [key: string]: any; } @@ -378,6 +381,7 @@ interface RouteConfigItem { ignoreTabQuery?: boolean; alwaysShowTabKey?: string; mark?: ROUTE_MARK; + backToUp?: ROUTE_MARK; toMark?: ROUTE_TO_MARK; routes?: RouteConfigItem[]; wrapper?: any; diff --git a/shell/app/layout/pages/page-container/components/header.tsx b/shell/app/layout/pages/page-container/components/header.tsx index b285a6fb4d..4ad79c3f76 100644 --- a/shell/app/layout/pages/page-container/components/header.tsx +++ b/shell/app/layout/pages/page-container/components/header.tsx @@ -18,13 +18,15 @@ import routeInfoStore from 'core/stores/route'; import breadcrumbStore from 'layout/stores/breadcrumb'; import { isEmpty, isFunction } from 'lodash'; import { matchPath } from 'react-router-dom'; -import { Ellipsis } from 'common'; +import { Ellipsis, ErdaIcon } from 'common'; import './header.scss'; +import { goTo } from 'app/common/utils'; const Header = () => { const [currentApp] = layoutStore.useStore((s) => [s.currentApp]); const routes: IRoute[] = routeInfoStore.useStore((s) => s.routes); const [pageName, setPageName] = React.useState(); + const [backToUp, setBackToUp] = React.useState(0); const infoMap = breadcrumbStore.useStore((s) => s.infoMap); const [query] = routeInfoStore.useStore((s) => [s.query]); @@ -103,6 +105,15 @@ const Header = () => { _params = match.params; setParams(_params); } + let backToUpLevel = 0; + const currentRoute = routes[0]; + if (currentRoute.backToUp) { + const targetRoute = routes.find((a) => a.mark === currentRoute.backToUp); + if (targetRoute) { + backToUpLevel = location.pathname.split('/').length - targetRoute.path.split('/').length; + } + } + setBackToUp(backToUpLevel); } const filteredRoutes = routes.filter((route) => { return route.path && (route.breadcrumbName || route.pageName || typeof route.breadcrumbName === 'function'); @@ -124,11 +135,22 @@ const Header = () => { const Comp = pageNameInfo; return ; } + if (backToUp) { + return ( +
    goTo('../'.repeat(backToUp))} + > + + {pageName} +
    + ); + } return
    {pageName}
    ; }; return (
    -
    {pageName && displayPageName()}
    +
    {pageName && displayPageName()}
    ); diff --git a/shell/app/modules/application/router.ts b/shell/app/modules/application/router.ts index 22e256d8f0..ee9032b4ec 100644 --- a/shell/app/modules/application/router.ts +++ b/shell/app/modules/application/router.ts @@ -17,10 +17,11 @@ import { APP_TABS } from './tabs'; function getAppRouter(): RouteConfigItem { return { - path: 'apps/:appId', + path: ':appId', mark: 'application', breadcrumbName: '{appName}', tabs: APP_TABS, + backToUp: 'projectAppList', routes: [ // TODO: remove // { @@ -41,11 +42,12 @@ function getAppRouter(): RouteConfigItem { breadcrumbName: i18n.t('dop:code'), pageName: i18n.t('dop:files'), tabs: APP_TABS, + backToUp: 'projectAppList', alwaysShowTabKey: 'repo', - showTabInChildren: (currentRoute, topRoute) => topRoute.mark === 'repo', routes: [ { tabs: APP_TABS, + backToUp: 'projectAppList', alwaysShowTabKey: 'repo', ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/repo-tree')), @@ -55,6 +57,7 @@ function getAppRouter(): RouteConfigItem { mark: 'repoTree', breadcrumbName: i18n.t('dop:code'), tabs: APP_TABS, + backToUp: 'projectAppList', alwaysShowTabKey: 'repo', ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/repo-tree')), @@ -63,12 +66,14 @@ function getAppRouter(): RouteConfigItem { path: 'branches', tabs: APP_TABS, alwaysShowTabKey: 'repo/branches', + mark: 'repoBranches', ignoreTabQuery: true, breadcrumbName: i18n.t('dop:branch'), routes: [ { path: 'compare/:branches*', mark: 'repoCompare', + backToUp: 'repoBranches', breadcrumbName: i18n.t('dop:branch comparison'), ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/branch-compare-detail'), 'BranchCompareDetail'), @@ -76,6 +81,7 @@ function getAppRouter(): RouteConfigItem { }, { ignoreTabQuery: true, + backToUp: 'projectAppList', getComp: (cb) => cb(import('application/pages/repo/repo-branch')), layout: { noWrapper: true }, }, @@ -84,12 +90,14 @@ function getAppRouter(): RouteConfigItem { { path: 'commit', tabs: APP_TABS, + backToUp: 'projectAppList', alwaysShowTabKey: 'repo/commits', ignoreTabQuery: true, routes: [ { path: ':commitId', breadcrumbName: i18n.t('dop:commit details'), + backToUp: 'projectAppList', ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/commit-detail')), }, @@ -99,6 +107,7 @@ function getAppRouter(): RouteConfigItem { path: 'commits/(.*)', // commits后面可能有分支(包含/),commit后面只有commitId breadcrumbName: i18n.t('dop:commits'), tabs: APP_TABS, + backToUp: 'projectAppList', alwaysShowTabKey: 'repo/commits', ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/repo-commit')), @@ -107,6 +116,7 @@ function getAppRouter(): RouteConfigItem { path: 'mr/:mrType', breadcrumbName: i18n.t('dop:merge requests'), tabs: APP_TABS, + mark: 'repoMr', alwaysShowTabKey: 'repo/mr/open', ignoreTabQuery: true, routes: [ @@ -114,16 +124,19 @@ function getAppRouter(): RouteConfigItem { path: 'createMR', breadcrumbName: i18n.t('dop:new merge request'), ignoreTabQuery: true, + backToUp: 'repoMr', getComp: (cb) => cb(import('application/pages/repo/repo-mr-creation'), 'RepoMRCreation'), }, { path: ':mergeId', breadcrumbName: i18n.t('dop:merge request detail'), + backToUp: 'repoMr', ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/repo/mr-detail')), }, { ignoreTabQuery: true, + backToUp: 'projectAppList', getComp: (cb) => cb(import('application/pages/repo/repo-mr')), layout: { noWrapper: true }, }, @@ -152,6 +165,7 @@ function getAppRouter(): RouteConfigItem { mark: 'pipeline', breadcrumbName: i18n.t('pipeline'), pageName: i18n.t('pipeline'), + backToUp: 'projectAppList', tabs: APP_TABS, ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/pipeline')), @@ -160,6 +174,7 @@ function getAppRouter(): RouteConfigItem { { path: 'dataTask', mark: 'dataTask', + backToUp: 'projectAppList', tabs: APP_TABS, pageName: `${i18n.t('dop:data task')}`, routes: [ @@ -176,6 +191,7 @@ function getAppRouter(): RouteConfigItem { { path: 'dataModel', breadcrumbName: i18n.t('dop:data model'), + backToUp: 'projectAppList', tabs: APP_TABS, routes: [ { @@ -191,12 +207,14 @@ function getAppRouter(): RouteConfigItem { { path: 'dataMarket', breadcrumbName: i18n.t('dop:data market'), + backToUp: 'projectAppList', tabs: APP_TABS, getComp: (cb) => cb(import('application/pages/data-market/data-market'), 'DataMarket'), }, { path: 'quality', breadcrumbName: i18n.t('dop:code quality'), + backToUp: 'projectAppList', tabs: APP_TABS, ignoreTabQuery: true, getComp: (cb) => cb(import('application/pages/quality/entry')), @@ -207,18 +225,16 @@ function getAppRouter(): RouteConfigItem { mark: 'apiDesign', breadcrumbName: 'API', tabs: APP_TABS, + backToUp: 'projectAppList', ignoreTabQuery: true, - routes: [ - { - layout: { fullHeight: true }, - getComp: (cb) => cb(import('apiManagePlatform/pages/api-market/design')), - }, - ], + layout: { fullHeight: true }, + getComp: (cb) => cb(import('apiManagePlatform/pages/api-market/design')), }, { path: 'setting', breadcrumbName: i18n.t('dop:setting'), tabs: APP_TABS, + backToUp: 'projectAppList', ignoreTabQuery: true, layout: { fullHeight: true }, getComp: (cb) => cb(import('application/pages/settings/app-settings')), diff --git a/shell/app/modules/application/tabs.tsx b/shell/app/modules/application/tabs.tsx index b0940cdcc6..5a5d622068 100644 --- a/shell/app/modules/application/tabs.tsx +++ b/shell/app/modules/application/tabs.tsx @@ -13,14 +13,12 @@ import React from 'react'; import i18n from 'i18n'; -import { ErdaIcon } from 'common'; import permStore from 'user/stores/permission'; import layoutStore from 'layout/stores/layout'; import appStore from './stores/application'; import routeInfoStore from 'core/stores/route'; import { appMode } from './common/config'; import { filter } from 'lodash'; -import { getSubList } from './stores/repo'; import { HeadAppSelector } from './common/app-selector'; import { goTo } from 'app/common/utils'; @@ -40,16 +38,6 @@ export const APP_TABS = () => { const { mode } = appDetail; const perm = permStore.getState((s) => s.app); - const back = { - key: '../', - hrefType: 'back', - name: ( - - - {i18n.t('dop:application')} - - ), - }; const appSwitch = { key: '_', className: 'mr-4', @@ -147,12 +135,11 @@ export const APP_TABS = () => { if (currentRoute.mark === 'application') { const firstAvailableTab = tabs.find((t) => t.show && t.href); if (firstAvailableTab?.href) { - console.log('currentRoute', currentRoute); goTo(firstAvailableTab.href, { replace: true }); } } }, [currentRoute, tabs]); - return [back, appSwitch, ...tabs] as ROUTE_TABS[]; + return [appSwitch, ...tabs] as ROUTE_TABS[]; }; // export const getQualityTabs = (params: Obj) => { diff --git a/shell/app/modules/project/router.tsx b/shell/app/modules/project/router.tsx index 4beabb36c5..5690d7a7fb 100755 --- a/shell/app/modules/project/router.tsx +++ b/shell/app/modules/project/router.tsx @@ -43,14 +43,20 @@ function getProjectRouter(): RouteConfigItem[] { }, { path: 'apps', - breadcrumbName: i18n.t('App'), - layout: { noWrapper: true }, - getComp: (cb) => cb(import('project/pages/apps/app-list'), 'ProjectAppList'), - }, - { - path: 'apps/createApp', - breadcrumbName: i18n.t('add application'), - getComp: (cb) => cb(import('project/pages/apps/app-form')), + mark: 'projectAppList', + routes: [ + { + path: 'createApp', + breadcrumbName: i18n.t('add application'), + getComp: (cb) => cb(import('project/pages/apps/app-form')), + }, + getAppRouter(), + { + breadcrumbName: i18n.t('App'), + layout: { noWrapper: true }, + getComp: (cb) => cb(import('project/pages/apps/app-list'), 'ProjectAppList'), + }, + ], }, { path: 'issues', @@ -398,7 +404,6 @@ function getProjectRouter(): RouteConfigItem[] { layout: { fullHeight: true }, getComp: (cb) => cb(import('project/pages/settings')), }, - getAppRouter(), { path: 'perm', pageName: i18n.t('role permissions description'), From 2f6f3b643099fc806846990443fd0fe1acc169e5 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 21:31:57 +0800 Subject: [PATCH 10/16] feat: update tab switch text size --- shell/app/modules/application/common/app-selector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/app/modules/application/common/app-selector.tsx b/shell/app/modules/application/common/app-selector.tsx index 19c6e35c1d..4effb472cb 100644 --- a/shell/app/modules/application/common/app-selector.tsx +++ b/shell/app/modules/application/common/app-selector.tsx @@ -108,7 +108,7 @@ const headAppRender = (val: any = {}) => { const curApp = appStore.getState((s) => s.detail); const name = val.displayName || val.name || curApp.displayName || curApp.name || ''; return ( -
    +
    From 60ecac3509b0efa689c4e65d2aa6b37de37ca72b Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 22:46:02 +0800 Subject: [PATCH 11/16] fix: not trigger when click current tab --- shell/app/common/components/menu/index.tsx | 6 ++++-- shell/app/layout/pages/page-container/components/header.tsx | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/shell/app/common/components/menu/index.tsx b/shell/app/common/components/menu/index.tsx index dee4a95028..15fbc75d3c 100644 --- a/shell/app/common/components/menu/index.tsx +++ b/shell/app/common/components/menu/index.tsx @@ -129,8 +129,10 @@ const PureMenu = (props: IMenu) => { key={key} className={menuItemClass} onClick={() => { - // trigger even click the same key, for back from child route - handleClick(activeKey, key, hrefType); + if (!disabled && activeKey !== key) { + // click current not trigger + handleClick(activeKey, key, hrefType); + } }} > {name} diff --git a/shell/app/layout/pages/page-container/components/header.tsx b/shell/app/layout/pages/page-container/components/header.tsx index 4ad79c3f76..dad84967e8 100644 --- a/shell/app/layout/pages/page-container/components/header.tsx +++ b/shell/app/layout/pages/page-container/components/header.tsx @@ -18,9 +18,9 @@ import routeInfoStore from 'core/stores/route'; import breadcrumbStore from 'layout/stores/breadcrumb'; import { isEmpty, isFunction } from 'lodash'; import { matchPath } from 'react-router-dom'; -import { Ellipsis, ErdaIcon } from 'common'; -import './header.scss'; +import { ErdaIcon } from 'common'; import { goTo } from 'app/common/utils'; +import './header.scss'; const Header = () => { const [currentApp] = layoutStore.useStore((s) => [s.currentApp]); From ca13c79f0645246235131b7c956c0212c35a68d7 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 23:07:24 +0800 Subject: [PATCH 12/16] fix: i18n --- shell/app/locales/en.json | 1 - shell/app/locales/zh.json | 1 - shell/app/modules/application/pages/problem/problem-list.tsx | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/shell/app/locales/en.json b/shell/app/locales/en.json index c54ca1b885..3cb9c7fba5 100644 --- a/shell/app/locales/en.json +++ b/shell/app/locales/en.json @@ -962,7 +962,6 @@ "app types": "app types", "app-issues": "Issues", "appMonitor": "appMonitor", - "application": "application", "application / service / instance name": "application / service / instance name", "application branch rule": "application branch rule", "application description": "application description", diff --git a/shell/app/locales/zh.json b/shell/app/locales/zh.json index 16cc344f77..2c8f1d3d8e 100644 --- a/shell/app/locales/zh.json +++ b/shell/app/locales/zh.json @@ -962,7 +962,6 @@ "app types": "应用类型", "app-issues": "问题列表", "appMonitor": "应用监控", - "application": "应用", "application / service / instance name": "应用 / 服务 / 实例名", "application branch rule": "应用分支规则", "application description": "应用描述", diff --git a/shell/app/modules/application/pages/problem/problem-list.tsx b/shell/app/modules/application/pages/problem/problem-list.tsx index 98111bef2b..be4762a707 100644 --- a/shell/app/modules/application/pages/problem/problem-list.tsx +++ b/shell/app/modules/application/pages/problem/problem-list.tsx @@ -132,7 +132,7 @@ export const ProblemList = () => { key: 'q', outside: true, label: 'title', - placeholder: '根据标题过滤', + placeholder: i18n.t('filter by {name}', { name: i18n.t('title') }), type: 'input', }, ]; From bd2f48b11e621db8f5b1ffcc940ca5538c2be3c7 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 23:08:42 +0800 Subject: [PATCH 13/16] fix: watch perm store change --- shell/app/modules/application/tabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/app/modules/application/tabs.tsx b/shell/app/modules/application/tabs.tsx index 5a5d622068..9852894864 100644 --- a/shell/app/modules/application/tabs.tsx +++ b/shell/app/modules/application/tabs.tsx @@ -37,7 +37,7 @@ export const APP_TABS = () => { const appDetail = appStore.useStore((s) => s.detail); const { mode } = appDetail; - const perm = permStore.getState((s) => s.app); + const perm = permStore.useStore((s) => s.app); const appSwitch = { key: '_', className: 'mr-4', From 8823f27c8d84107e999e8bb3c17c6dfd5bf0d07f Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 23:09:16 +0800 Subject: [PATCH 14/16] feat: update i18n --- locales/en.json | 2 -- locales/zh.json | 2 -- shell/app/locales/en.json | 1 - shell/app/locales/zh.json | 1 - 4 files changed, 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 91a6162b50..1eecce1598 100644 --- a/locales/en.json +++ b/locales/en.json @@ -13,7 +13,6 @@ "API strategy": "API strategy", "API test": "API test", "API version": "API version", - "APIGateway": "APIGateway", "Alarm": "Alarm", "Alibaba Cloud": "Alibaba Cloud", "All members of the organization can view": "all members of the current company can view", @@ -77,7 +76,6 @@ "July": "July", "June": "June", "Kuala lumpur": "Kuala lumpur", - "LogAnalyze": "LogAnalyze", "London": "London", "Malaysia": "Malaysia", "March": "March", diff --git a/locales/zh.json b/locales/zh.json index 32dd45afc2..c0880a538e 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -13,7 +13,6 @@ "API strategy": "API 策略", "API test": "API 测试", "API version": "API 版本", - "APIGateway": "API 网关", "Alarm": "告警", "Alibaba Cloud": "阿里云", "All members of the organization can view": "当前组织下所有成员都可以查看", @@ -77,7 +76,6 @@ "July": "7月", "June": "6月", "Kuala lumpur": "吉隆坡", - "LogAnalyze": "日志分析", "London": "伦敦", "Malaysia": "马来西亚", "March": "3月", diff --git a/shell/app/locales/en.json b/shell/app/locales/en.json index 3cb9c7fba5..c233d1470c 100644 --- a/shell/app/locales/en.json +++ b/shell/app/locales/en.json @@ -2016,7 +2016,6 @@ "Provides one-stop service system observation, including service monitoring, tracing, dashboard, and alarm.": "Provides one-stop service system observation, including service monitoring, tracing, dashboard, and alarm.", "RPC call": "RPC call", "RPC transaction": "RPC transaction", - "RegisterCenter": "RegisterCenter", "The selected interface weight will be set to": "The selected interface weight will be set to", "URL path": "URL path", "URL path prefix": "URL path prefix", diff --git a/shell/app/locales/zh.json b/shell/app/locales/zh.json index 2c8f1d3d8e..6475696d2c 100644 --- a/shell/app/locales/zh.json +++ b/shell/app/locales/zh.json @@ -2016,7 +2016,6 @@ "Provides one-stop service system observation, including service monitoring, tracing, dashboard, and alarm.": "提供一站式的服务系统观测,包括服务监控、链路追踪、仪表盘和告警等功能。", "RPC call": "RPC 调用", "RPC transaction": "RPC事务", - "RegisterCenter": "注册中心", "The selected interface weight will be set to": "所选接口权重将置为", "URL path": "URL 路径", "URL path prefix": "URL 路径前缀", From 6c904ba001db0a6884f30843682ce2d584b6b38d Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 23:10:19 +0800 Subject: [PATCH 15/16] fix: update problem list paging state --- .../application/pages/problem/problem-list.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/shell/app/modules/application/pages/problem/problem-list.tsx b/shell/app/modules/application/pages/problem/problem-list.tsx index be4762a707..acc1a9eaee 100644 --- a/shell/app/modules/application/pages/problem/problem-list.tsx +++ b/shell/app/modules/application/pages/problem/problem-list.tsx @@ -42,7 +42,7 @@ export const ProblemList = () => { const [visible, openModal, closeModal] = useSwitch(false); const appId = routeInfoStore.useStore((s) => s.params.appId); const loginUser = userStore.getState((s) => s.loginUser); - const [state, updater] = useUpdate({ + const [state, updater, update] = useUpdate({ detailVisibleId: 0, ...getDefaultPaging(), hasMore: undefined, @@ -56,15 +56,16 @@ export const ProblemList = () => { const userMap = useUserMap(); - const filterFn = (values?: Obj) => { + const filterFn = (paging?: Obj, values?: Obj) => { setFilterData((prev) => ({ ...prev, ...values })); + paging && update(paging); getTicketList .fetch({ ...filterData, - ...values, - status: values?.status === 'all' ? undefined : values?.status || filterData.status, pageNo: state.pageNo, pageSize: state.pageSize, + ...values, + status: values?.status === 'all' ? undefined : values?.status || filterData.status, targetID: +routeAppId, targetType: 'application', }) @@ -222,7 +223,14 @@ export const ProblemList = () => { }, }; }} - slot={} + slot={ + filterFn(undefined, values)} + /> + } />
    From e07256d6f69d4ac72334a71b1874ca2ed8a374e7 Mon Sep 17 00:00:00 2001 From: daskyrk Date: Thu, 17 Feb 2022 23:19:24 +0800 Subject: [PATCH 16/16] feat: change to usePerm --- shell/app/modules/application/tabs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/app/modules/application/tabs.tsx b/shell/app/modules/application/tabs.tsx index 9852894864..5d470910a2 100644 --- a/shell/app/modules/application/tabs.tsx +++ b/shell/app/modules/application/tabs.tsx @@ -13,7 +13,7 @@ import React from 'react'; import i18n from 'i18n'; -import permStore from 'user/stores/permission'; +import { usePerm } from 'app/user/common'; import layoutStore from 'layout/stores/layout'; import appStore from './stores/application'; import routeInfoStore from 'core/stores/route'; @@ -37,7 +37,7 @@ export const APP_TABS = () => { const appDetail = appStore.useStore((s) => s.detail); const { mode } = appDetail; - const perm = permStore.useStore((s) => s.app); + const perm = usePerm((s) => s.app); const appSwitch = { key: '_', className: 'mr-4',