feat(项目管理): 同步缺陷集成ms-form-create

This commit is contained in:
RubyLiu 2023-10-26 18:20:47 +08:00 committed by 刘瑞斌
parent 0da79cbe4f
commit 1a6e19e418
10 changed files with 311 additions and 36 deletions

View File

@ -1,3 +1,5 @@
import { FormItem } from '@/components/pure/ms-form-create/types';
import MSR from '@/api/http/index';
import * as Url from '@/api/requrls/project-management/menuManagement';
@ -115,9 +117,9 @@ export function getPlatformOptions(organizationId: string, type: MenuEnum) {
*/
export function getPlatformInfo(pluginId: string, type: MenuEnum) {
if (type === MenuEnum.bugManagement) {
return MSR.get<unknown>({ url: `${Url.getPluginInfoByBug}${pluginId}` });
return MSR.get<{ formItems: FormItem[] }>({ url: `${Url.getPluginInfoByBug}${pluginId}` });
}
return MSR.get<unknown>({ url: `${Url.getPluginInfoByCase}${pluginId}` });
return MSR.get<{ formItems: FormItem[] }>({ url: `${Url.getPluginInfoByCase}${pluginId}` });
}
/**

View File

@ -0,0 +1,34 @@
<template>
<a-input
:value="props.modelValue"
:placeholder="t('project.menu.pleaseInputJiraKey')"
@change="(v: string) => emit('update:modelValue', v)"
/>
<div class="flex flex-row items-center gap-[10px] text-[12px] leading-[20px]">
<span class="text-[var(--color-text-4)]">{{ t('project.menu.howGetJiraKey') }}</span>
<a-popover position="rt">
<template #title>
{{ null }}
</template>
<template #content>
<img class="h-[247px] w-[398px]" :src="props.instructionsIcon" />
</template>
<span class="cursor-pointer text-[rgb(var(--primary-5))]">{{ t('project.menu.preview') }}</span>
</a-popover>
</div>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
const props = defineProps<{
modelValue: string;
instructionsIcon: string;
}>();
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void;
}>();
const { t } = useI18n();
</script>
<style scoped></style>

View File

@ -145,6 +145,17 @@ export const TEXTAREA = {
},
},
};
export const JIRAKEY = {
type: 'JiraKey',
field: 'jiraKey',
title: '',
value: '',
props: {
moduleValue: '',
placeholder: '请输入',
instructionsIcon: '',
},
};
export const FieldTypeFormRules: Record<string, FormRule> = {
INPUT,
@ -160,4 +171,5 @@ export const FieldTypeFormRules: Record<string, FormRule> = {
FLOAT,
MULTIPLE_INPUT,
TEXTAREA,
JIRAKEY,
};

View File

@ -6,6 +6,7 @@
import { ref, watch } from 'vue';
import { debounce } from 'lodash-es';
import JiraKey from './comp/jiraKey.vue';
import PassWord from './formcreate-password.vue';
import SearchSelect from './searchSelect.vue';
@ -21,6 +22,7 @@
formCreate.component('PassWord', PassWord);
formCreate.component('SearchSelect', SearchSelect);
formCreate.component('JiraKey', JiraKey);
const FormCreate = formCreate.$form();
const option = {

View File

@ -5,6 +5,7 @@
<script setup lang="ts">
import { ref, watch, watchEffect } from 'vue';
import JiraKey from './comp/jiraKey.vue';
import PassWord from './formcreate-password.vue';
import SearchSelect from './searchSelect.vue';
@ -12,6 +13,7 @@
formCreate.component('PassWord', PassWord);
formCreate.component('SearchSelect', SearchSelect);
formCreate.component('JiraKey', JiraKey);
const FormCreate = formCreate.$form();
const props = defineProps<{

View File

@ -1,6 +1,10 @@
export enum FormCreateKeyEnum {
ORGANIZE_TEMPLATE = 'OrganizeTemplate',
ORGANIZE_TEMPLATE_PREVIEW_TEMPLATE = 'OrganizeTemplatePreview',
// 同步缺陷
PROJECT_DEFECT_SYNC_TEMPLATE = 'ProjectDefectSyncTemplate',
// 关联需求
PROJECT__RELATED_TEMPLATE = 'ProjectRelatedTemplate',
}
export default {};

View File

@ -26,29 +26,13 @@
>
</a-select>
</a-form-item>
<!-- jira start -->
<a-form-item field="projectKey" :label="t('project.menu.projectKey')">
<a-input v-model="form.projectKey" :placeholder="t('project.menu.pleaseInputJiraKey')" />
<template #extra>
<div class="flex flex-row items-center gap-[4px]">
<span class="text-[var(--color-text-4)]">{{ t('project.menu.howGetJiraKey') }}</span>
<span class="cursor-pointer text-[rgb(var(--primary-5))]">{{ t('project.menu.preview') }}</span>
</div>
</template>
</a-form-item>
<a-form-item field="defectType" :label="t('project.menu.defectType')">
<a-select :options="jiraDefectOption" :field-names="{ value: 'id', label: 'name' }"></a-select>
</a-form-item>
<!-- jira end -->
<a-form-item field="organizationId" :label="t('project.menu.organizationId')">
<a-input v-model="form.organizationId" />
</a-form-item>
<a-form-item field="projectId" :label="t('project.menu.projectId')">
<a-input v-model="form.projectId" />
</a-form-item>
<a-form-item field="azureId" :label="t('project.menu.azureId')">
<a-input v-model="form.azureId" />
</a-form-item>
<!-- form-create -->
<MsFormCreate
v-if="platformRules && platformRules.length"
:form-rule="platformRules"
:form-create-key="FormCreateKeyEnum.PROJECT_DEFECT_SYNC_TEMPLATE"
/>
<!-- 同步机制 -->
<a-form-item field="MECHANISM" :label="t('project.menu.syncMechanism')">
<a-space>
<a-radio-group v-model="form.MECHANISM">
@ -75,6 +59,7 @@
</a-radio-group>
</a-space>
</a-form-item>
<!-- 同步频率 -->
<a-form-item field="CRON_EXPRESSION" :label="t('project.menu.CRON_EXPRESSION')">
<a-select v-model="form.CRON_EXPRESSION">
<a-option v-for="data in frequencyOption" :key="data.value" :value="data.value">
@ -117,6 +102,8 @@
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue';
import type { FormItem } from '@/components/pure/ms-form-create/types';
import {
getPlatformInfo,
@ -128,6 +115,7 @@
import { PoolOption, SelectValue } from '@/models/projectManagement/menuManagement';
import { MenuEnum } from '@/enums/commonEnum';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
const { t } = useI18n();
const props = defineProps<{
@ -141,7 +129,6 @@
{ label: '0 0 0/12 * * ?', extra: '每隔12小时', value: '12H' },
{ label: '0 0 0 * * ?', extra: '(每隔一天)', value: '1D' },
]);
const jiraDefectOption = ref<PoolOption[]>([]);
const appStore = useAppStore();
const currentProjectId = computed(() => appStore.currentProjectId);
@ -150,6 +137,7 @@
const okLoading = ref(false);
const formRef = ref<FormInstance>();
const platformRules = ref<FormItem[]>([]);
const form = reactive({
platformKey: '',
@ -158,7 +146,6 @@
CRON_EXPRESSION: '', //
organizationId: '',
projectKey: '',
projectId: '',
azureId: '',
bugType: '',
@ -175,8 +162,12 @@
};
const handlePlatformChange = async (value: SelectValue) => {
try {
// TODO: Pending
await getPlatformInfo(value as string, MenuEnum.bugManagement);
if (value) {
const res = await getPlatformInfo(value as string, MenuEnum.bugManagement);
platformRules.value = res.formItems;
} else {
platformRules.value = [];
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -220,6 +211,9 @@
currentVisible.value = val;
if (val) {
initPlatformOption();
} else {
formRef.value?.resetFields();
platformRules.value = [];
}
}
);

View File

@ -0,0 +1,220 @@
<template>
<MsDrawer
v-model:visible="currentVisible"
:title="t('project.menu.CASE_RELATED')"
:destroy-on-close="true"
:closable="true"
:mask-closable="false"
:get-container="false"
:body-style="{ padding: '0px' }"
:width="680"
:ok-loading="okLoading"
:ok-disabled="okDisabled"
@cancel="handleCancel(false)"
@confirm="handleConfirm"
>
<a-form ref="formRef" class="rounded-[4px]" :model="form" layout="vertical">
<a-form-item field="platformKey" :label="t('project.menu.platformLabel')">
<a-select
v-model="form.platformKey"
allow-clear
:disabled="platformDisabled"
:options="platformOption"
:placeholder="platformDisabled ? t('project.menu.platformPlaceholder') : ''"
:field-names="{ label: 'name', value: 'id' }"
@change="handlePlatformChange"
>
</a-select>
</a-form-item>
<!-- form-create -->
<MsFormCreate
v-if="platformRules && platformRules.length"
:form-rule="platformRules"
:form-create-key="FormCreateKeyEnum.PROJECT_DEFECT_SYNC_TEMPLATE"
/>
<!-- 同步机制 -->
<a-form-item field="MECHANISM" :label="t('project.menu.syncMechanism')">
<a-space>
<a-radio-group v-model="form.MECHANISM">
<a-radio value="increment">
<div class="flex flex-row items-center gap-[4px]">
<span class="text-[var(--color-text-1)]">{{ t('project.menu.incrementalSync') }}</span>
<a-tooltip :content="t('project.menu.incrementalSyncTip')" position="top">
<div>
<MsIcon class="ml-[4px] text-[var(--color-text-4)]" type="icon-icon-maybe_outlined" />
</div>
</a-tooltip>
</div>
</a-radio>
<a-radio value="full">
<div class="flex flex-row items-center gap-[4px]">
<span class="text-[var(--color-text-1)]">{{ t('project.menu.fullSync') }}</span>
<a-tooltip :content="t('project.menu.fullSyncTip')" position="bl">
<div>
<MsIcon class="ml-[4px] text-[var(--color-text-4)]" type="icon-icon-maybe_outlined" />
</div>
</a-tooltip>
</div>
</a-radio>
</a-radio-group>
</a-space>
</a-form-item>
<!-- 同步频率 -->
<a-form-item field="CRON_EXPRESSION" :label="t('project.menu.CRON_EXPRESSION')">
<a-select v-model="form.CRON_EXPRESSION">
<a-option v-for="data in frequencyOption" :key="data.value" :value="data.value">
<span class="text-[var(--color-text-1)]">
{{ data.label }}
</span>
<span v-if="data.extra" class="text-[var(--color-text-4)]"> {{ data.extra }} </span>
</a-option>
<a-option value="custom">
<div class="border-t-1 cursor-pointer text-[rgb(var(--primary-5))]">{{
t('project.menu.defect.customLabel')
}}</div>
</a-option>
</a-select>
</a-form-item>
</a-form>
<template #footerLeft>
<div class="flex flex-row items-center gap-[4px]">
<a-switch size="small" />
<span class="text-[var(--color-text-1)]">
{{ t('project.menu.status') }}
</span>
<a-tooltip position="tl" :content-style="{ maxWidth: '500px' }">
<template #content>
<div class="flex flex-col">
<div>{{ t('project.menu.defect.enableTip') }}</div>
<div class="flex flex-nowrap">{{ t('project.menu.defect.closeTip') }}</div>
</div>
</template>
<div>
<MsIcon class="ml-[4px] text-[var(--color-text-4)]" type="icon-icon-maybe_outlined" />
</div>
</a-tooltip>
</div>
</template>
</MsDrawer>
</template>
<script lang="ts" setup>
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue';
import type { FormItem } from '@/components/pure/ms-form-create/types';
import {
getPlatformInfo,
getPlatformOptions,
postSaveDefectSync,
} from '@/api/modules/project-management/menuManagement';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { PoolOption, SelectValue } from '@/models/projectManagement/menuManagement';
import { MenuEnum } from '@/enums/commonEnum';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
}>();
const currentVisible = ref<boolean>(props.visible);
const platformOption = ref<PoolOption[]>([]);
const frequencyOption = ref([
{ label: '0 0 0/1 * * ?', extra: '每隔1小时', value: '1H' },
{ label: '0 0 0/6 * * ?', extra: '每隔6小时', value: '6H' },
{ label: '0 0 0/12 * * ?', extra: '每隔12小时', value: '12H' },
{ label: '0 0 0 * * ?', extra: '(每隔一天)', value: '1D' },
]);
const appStore = useAppStore();
const currentProjectId = computed(() => appStore.currentProjectId);
const currentOrgId = computed(() => appStore.currentOrgId);
const platformDisabled = computed(() => platformOption.value.length === 0);
const okLoading = ref(false);
const formRef = ref<FormInstance>();
const platformRules = ref<FormItem[]>([]);
const form = reactive({
platformKey: '',
MECHANISM: '', //
SYNC_ENABLE: 'false', //
CRON_EXPRESSION: '', //
organizationId: '',
projectKey: '',
projectId: '',
azureId: '',
bugType: '',
});
const okDisabled = computed(() => !form.platformKey);
const emit = defineEmits<{
(e: 'cancel', shouldSearch: boolean): void;
}>();
const handleCancel = (shouldSearch: boolean) => {
emit('cancel', shouldSearch);
};
const handlePlatformChange = async (value: SelectValue) => {
try {
if (value) {
const res = await getPlatformInfo(value as string, MenuEnum.bugManagement);
platformRules.value = res.formItems;
} else {
platformRules.value = [];
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
};
const handleConfirm = async () => {
await formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (errors) {
return;
}
try {
okLoading.value = true;
await postSaveDefectSync(form, currentProjectId.value);
Message.success(t('common.createSuccess'));
handleCancel(true);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
} finally {
okLoading.value = false;
}
});
};
const initPlatformOption = async () => {
try {
const res = await getPlatformOptions(currentOrgId.value, MenuEnum.bugManagement);
if (res) {
platformOption.value = res;
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
};
watch(
() => props.visible,
(val) => {
currentVisible.value = val;
if (val) {
initPlatformOption();
} else {
formRef.value?.resetFields();
platformRules.value = [];
}
}
);
</script>

View File

@ -35,9 +35,6 @@ export default {
'project.menu.BUG_SYNC': '同步缺陷',
'project.menu.SYNC_ENABLE': '状态',
'project.menu.MECHANISM': '接口测试待更新同步规则',
'project.menu.sd': '同步缺陷',
'project.menu.rr': '关联需求',
'project.menu.far': '误报规则',
'project.menu.row1': '系统根据设定的规则将符合的数据展示在我的待办-待更新列表',
'project.menu.row2': '将平台创建的缺陷同步至第三方项目管理平台',
'project.menu.row3': '可将用例添加至公共用例库共用',

View File

@ -40,7 +40,7 @@
<!-- 同步缺陷 -->
<span>{{ t('project.menu.row2') }}</span>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="showDefectDrawer">{{
t('project.menu.sd')
t('project.menu.BUG_SYNC')
}}</div>
</template>
<div v-if="record.type === 'CASE_PUBLIC'">
@ -50,8 +50,8 @@
<div v-if="record.type === 'CASE_RELATED'" class="flex flex-row">
<!-- 用例 关联需求 -->
<div>{{ t('project.menu.row4') }}</div>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="showDefectDrawer">{{
t('project.menu.rr')
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="showRelatedCaseDrawer">{{
t('project.menu.CASE_RELATED')
}}</div>
</div>
<div v-if="record.type === 'CASE_RE_REVIEW'">
@ -127,7 +127,7 @@
</a-input-number>
</div>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="pushFar">{{
t('project.menu.far')
t('project.menu.API_ERROR_REPORT_RULE')
}}</div>
<a-tooltip :content="t('project.menu.API_ERROR_REPORT_RULE_TIP')" position="right">
<div>
@ -313,6 +313,7 @@
</template>
</MsBaseTable>
<DefectSync v-model:visible="defectDrawerVisible" @cancel="defectDrawerVisible = false" />
<RelatedCase v-model:visible="relatedCaseDrawerVisible" @cancel="relatedCaseDrawerVisible = false" />
</template>
<script setup lang="ts">
@ -327,6 +328,7 @@
import useTable from '@/components/pure/ms-table/useTable';
import MsTimeSelectorVue from '@/components/pure/ms-time-selector/MsTimeSelector.vue';
import DefectSync from './components/defectSync.vue';
import RelatedCase from './components/relatedCase.vue';
import {
getAuditorOptions,
@ -347,6 +349,7 @@
const currentProjectId = computed(() => appStore.currentProjectId);
const { t } = useI18n();
const defectDrawerVisible = ref(false);
const relatedCaseDrawerVisible = ref(false);
const apiPoolOption = ref<PoolOption[]>([]);
const uiPoolOption = ref<PoolOption[]>([]);
const apiAuditorOption = ref<PoolOption[]>([]);
@ -619,9 +622,14 @@
await loadList();
};
//
const showDefectDrawer = () => {
defectDrawerVisible.value = true;
};
//
const showRelatedCaseDrawer = () => {
relatedCaseDrawerVisible.value = true;
};
//
const pushFar = () => {
router.push({ name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_MENU_MANAGEMENT_ERROR_REPORT_RULE });