feat(sj_map_reduce): 任务执行列表支持属性结构展示

This commit is contained in:
xlsea 2024-06-22 15:56:00 +08:00
parent 0a3ea03720
commit 5e0ed799a2
7 changed files with 5875 additions and 5974 deletions

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,15 @@ export function fetchGetJobTaskList(params?: Api.Job.jobTaskSearchParams) {
}); });
} }
/** get Job Task Tree */
export function fetchGetJobTaskTree(params?: Api.Job.jobTaskSearchParams) {
return request<Api.Job.JobTaskTreeList>({
url: '/job/task/tree/list',
method: 'get',
params
});
}
/** add Job */ /** add Job */
export function fetchAddJob(data: Api.Job.Job) { export function fetchAddJob(data: Api.Job.Job) {
return request<boolean>({ return request<boolean>({

View File

@ -1043,8 +1043,15 @@ declare namespace Api {
taskBatchId: string; taskBatchId: string;
/** 任务状态 ID */ /** 任务状态 ID */
taskStatus: Common.TaskStatus; taskStatus: Common.TaskStatus;
/** 任务类型 */
taskType: Common.TaskType;
}>; }>;
type JobTaskTree = {
parentId: string;
children: JobTaskTree[];
} & JobTask;
/** jobTask search params */ /** jobTask search params */
type jobTaskSearchParams = CommonType.RecordNullable< type jobTaskSearchParams = CommonType.RecordNullable<
Pick<Api.Job.JobTask, 'groupName' | 'taskBatchId' | 'taskStatus'> & Pick<Api.Job.JobTask, 'groupName' | 'taskBatchId' | 'taskStatus'> &
@ -1053,6 +1060,8 @@ declare namespace Api {
/** jobTask list */ /** jobTask list */
type JobTaskList = Common.PaginatingQueryRecord<JobTask>; type JobTaskList = Common.PaginatingQueryRecord<JobTask>;
/** jobTask tree list */
type JobTaskTreeList = Common.PaginatingQueryRecord<JobTaskTree>;
} }
/** /**

View File

@ -126,40 +126,8 @@ export function parseArgsJson(value: string) {
let argsJson; let argsJson;
try { try {
argsJson = JSON.parse(value); argsJson = JSON.stringify(JSON.parse(value), null, 4);
// A helper function to safely parse JSON strings that might contain escaped quotes
const safelyParseJson = (jsonString: string) => {
try {
return JSON.parse(jsonString.replaceAll('\\"', '"'));
} catch (error) {
return jsonString; // Return original string if it's not valid JSON
}
};
// Process jobParams if it exists
if (typeof argsJson.jobParams === 'string') {
argsJson.jobParams = safelyParseJson(argsJson.jobParams);
}
// Process mapResult if it exists
if (typeof argsJson.mapResult === 'string') {
argsJson.mapResult = safelyParseJson(argsJson.mapResult);
}
// Process maps if it exists
if (typeof argsJson.maps === 'string') {
argsJson.maps = safelyParseJson(argsJson.maps);
// If maps is a string that represents an array, parse it as JSON
if (argsJson.maps.startsWith('"[') && argsJson.maps.endsWith('"]')) {
argsJson.maps = safelyParseJson(argsJson.maps.slice(1, -1));
}
}
} catch {} } catch {}
// Convert the object back to a pretty-printed JSON string
argsJson = JSON.stringify(argsJson, null, 4);
return argsJson; return argsJson;
} }

View File

@ -1,168 +1,37 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { NButton, NCode, NPopover, NTag } from 'naive-ui';
import hljs from 'highlight.js/lib/core';
import json from 'highlight.js/lib/languages/json';
import { onBeforeUnmount, ref } from 'vue'; import { onBeforeUnmount, ref } from 'vue';
import { import { executorTypeRecord, operationReasonRecord, taskBatchStatusRecord } from '@/constants/business';
executorTypeRecord,
operationReasonRecord,
taskBatchStatusRecord,
taskStatusRecord
} from '@/constants/business';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { parseArgsJson, tagColor } from '@/utils/common'; import { tagColor } from '@/utils/common';
import { useTable } from '@/hooks/common/table';
import { fetchGetJobTaskList } from '@/service/api';
import { fetchJobLogList } from '@/service/api/log'; import { fetchJobLogList } from '@/service/api/log';
import JobTaskListTable from './job-task-list-table.vue';
import JobTaskTreeTable from './job-task-tree-table.vue';
defineOptions({ defineOptions({
name: 'JobBatchDetailDrawer' name: 'JobBatchDetailDrawer'
}); });
hljs.registerLanguage('json', json);
interface Props { interface Props {
/** row data */ /** row data */
rowData?: Api.JobBatch.JobBatch | null; rowData?: Api.JobBatch.JobBatch | null;
} }
const props = defineProps<Props>(); defineProps<Props>();
const taskData = ref<Api.Job.JobTask>();
const visible = defineModel<boolean>('visible', { const visible = defineModel<boolean>('visible', {
default: false default: false
}); });
const taskData = ref<Api.Job.JobTask>();
const logShow = defineModel<boolean>('logShow', { const logShow = defineModel<boolean>('logShow', {
default: false default: false
}); });
const { columns, data, loading, mobilePagination } = useTable({ async function openLog(row: Api.Job.JobTask) {
apiFn: fetchGetJobTaskList, logShow.value = true;
apiParams: { taskData.value = row;
page: 1, await getLogList();
size: 10, }
groupName: props.rowData?.groupName,
taskBatchId: props.rowData?.id,
startId: 0,
fromIndex: 0
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
},
columns: () => [
{
key: 'index',
title: $t('common.index'),
align: 'center',
width: 64,
render: row => {
async function openLog() {
logShow.value = true;
taskData.value = row;
await getLogList();
}
return (
<NButton type="info" text onClick={openLog}>
<span class="w-28px ws-break-spaces">{$t('page.log.view')}</span>
</NButton>
);
}
},
{
key: 'id',
title: $t('page.jobBatch.jobTask.id'),
align: 'left',
minWidth: 64
},
{
key: 'groupName',
title: $t('page.jobBatch.jobTask.groupName'),
align: 'left',
minWidth: 180
},
{
key: 'taskStatus',
title: $t('page.jobBatch.jobTask.taskStatus'),
align: 'left',
minWidth: 80,
render: row => {
if (row.taskStatus === null) {
return null;
}
const label = $t(taskStatusRecord[row.taskStatus!]);
const tagMap: Record<number, NaiveUI.ThemeColor> = {
1: 'info',
2: 'info',
3: 'info',
4: 'error',
5: 'error',
6: 'error'
};
return <NTag type={tagMap[row.taskStatus!]}>{label}</NTag>;
}
},
{
key: 'clientInfo',
title: $t('page.jobBatch.jobTask.clientInfo'),
align: 'left',
minWidth: 150,
render: row => {
if (row.clientInfo) {
const parts = row.clientInfo?.split('@');
const result = parts.length > 1 ? parts[1] : '';
return <div>{result}</div>;
}
return <div>{row.clientInfo}</div>;
}
},
{
key: 'argsStr',
title: $t('page.jobBatch.jobTask.argsStr'),
align: 'center',
titleAlign: 'center',
minWidth: 120,
render: row => {
return (
<NPopover trigger="click">
{{
trigger: () => (
<NButton type="primary" text>
<span class="w-28px ws-break-spaces">{`查看\n参数`}</span>
</NButton>
),
default: () => (
<NCode
class="max-h-300px overflow-auto"
hljs={hljs}
code={parseArgsJson(row.argsStr)}
language="json"
show-line-numbers
/>
)
}}
</NPopover>
);
}
},
{
key: 'resultMessage',
title: $t('page.jobBatch.jobTask.resultMessage'),
align: 'left',
minWidth: 120
},
{
key: 'retryCount',
title: $t('page.jobBatch.jobTask.retryCount'),
align: 'left',
minWidth: 64
},
{
key: 'createDt',
title: $t('page.jobBatch.jobTask.createDt'),
align: 'left',
minWidth: 120
}
]
});
const logList = ref<Api.JobLog.JobMessage[]>([]); const logList = ref<Api.JobLog.JobMessage[]>([]);
const interval = ref<NodeJS.Timeout>(); const interval = ref<NodeJS.Timeout>();
@ -237,19 +106,16 @@ onBeforeUnmount(() => {
</NDescriptions> </NDescriptions>
</NTabPane> </NTabPane>
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if"> <NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
<NDataTable <JobTaskTreeTable
:columns="columns" v-if="rowData?.taskType && [4, 5].includes(Number(rowData.taskType))"
:data="data" :row-data="rowData"
:loading="loading" @show-log="openLog"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/> />
<JobTaskListTable v-else :row-data="rowData" @show-log="openLog" />
</NTabPane> </NTabPane>
</NTabs> </NTabs>
<LogDrawer v-model="logList" v-model:show="logShow" :title="$t('page.log.title')" />
</DetailDrawer> </DetailDrawer>
<LogDrawer v-model="logList" v-model:show="logShow" :title="$t('page.log.title')" />
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -0,0 +1,165 @@
<script setup lang="tsx">
import { NButton, NCode, NPopover, NTag } from 'naive-ui';
import hljs from 'highlight.js/lib/core';
import json from 'highlight.js/lib/languages/json';
import { taskStatusRecord } from '@/constants/business';
import { $t } from '@/locales';
import { parseArgsJson } from '@/utils/common';
import { useTable } from '@/hooks/common/table';
import { fetchGetJobTaskList } from '@/service/api';
defineOptions({
name: 'JobTaskListTable'
});
hljs.registerLanguage('json', json);
interface Props {
/** row data */
rowData?: Api.JobBatch.JobBatch | null;
}
const props = defineProps<Props>();
interface Emits {
(e: 'showLog', rowData: Api.Job.JobTask): void;
}
const emit = defineEmits<Emits>();
const { columns, data, loading, mobilePagination } = useTable({
apiFn: fetchGetJobTaskList,
apiParams: {
page: 1,
size: 10,
groupName: props.rowData?.groupName,
taskBatchId: props.rowData?.id,
startId: 0,
fromIndex: 0
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
},
columns: () => [
{
key: 'index',
title: $t('common.index'),
align: 'center',
width: 64,
render: row => (
<NButton type="info" text onClick={() => emit('showLog', row)}>
<span class="w-28px ws-break-spaces">{$t('page.log.view')}</span>
</NButton>
)
},
{
key: 'id',
title: $t('page.jobBatch.jobTask.id'),
align: 'left',
minWidth: 64
},
{
key: 'groupName',
title: $t('page.jobBatch.jobTask.groupName'),
align: 'left',
minWidth: 180
},
{
key: 'taskStatus',
title: $t('page.jobBatch.jobTask.taskStatus'),
align: 'left',
minWidth: 80,
render: row => {
if (row.taskStatus === null) {
return null;
}
const label = $t(taskStatusRecord[row.taskStatus!]);
const tagMap: Record<number, NaiveUI.ThemeColor> = {
1: 'info',
2: 'info',
3: 'info',
4: 'error',
5: 'error',
6: 'error'
};
return <NTag type={tagMap[row.taskStatus!]}>{label}</NTag>;
}
},
{
key: 'clientInfo',
title: $t('page.jobBatch.jobTask.clientInfo'),
align: 'left',
minWidth: 150,
render: row => {
if (row.clientInfo) {
const parts = row.clientInfo?.split('@');
const result = parts.length > 1 ? parts[1] : '';
return <div>{result}</div>;
}
return <div>{row.clientInfo}</div>;
}
},
{
key: 'argsStr',
title: $t('page.jobBatch.jobTask.argsStr'),
align: 'center',
titleAlign: 'center',
minWidth: 120,
render: row => {
return (
<NPopover trigger="click">
{{
trigger: () => (
<NButton type="primary" text>
<span class="w-28px ws-break-spaces">{`查看\n参数`}</span>
</NButton>
),
default: () => (
<NCode
class="max-h-300px overflow-auto"
hljs={hljs}
code={parseArgsJson(row.argsStr)}
language="json"
show-line-numbers
/>
)
}}
</NPopover>
);
}
},
{
key: 'resultMessage',
title: $t('page.jobBatch.jobTask.resultMessage'),
align: 'left',
minWidth: 120
},
{
key: 'retryCount',
title: $t('page.jobBatch.jobTask.retryCount'),
align: 'left',
minWidth: 64
},
{
key: 'createDt',
title: $t('page.jobBatch.jobTask.createDt'),
align: 'left',
minWidth: 120
}
]
});
</script>
<template>
<NDataTable
:columns="columns"
:data="data"
:loading="loading"
remote
:scroll-x="962"
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>
</template>
<style scoped></style>

View File

@ -0,0 +1,170 @@
<script setup lang="tsx">
import { NButton, NCode, NPopover, NTag } from 'naive-ui';
import hljs from 'highlight.js/lib/core';
import json from 'highlight.js/lib/languages/json';
import { taskStatusRecord } from '@/constants/business';
import { $t } from '@/locales';
import { parseArgsJson } from '@/utils/common';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { fetchGetJobTaskTree } from '@/service/api';
defineOptions({
name: 'JobTaskTreeTable'
});
hljs.registerLanguage('json', json);
interface Props {
/** row data */
rowData?: Api.JobBatch.JobBatch | null;
}
const props = defineProps<Props>();
interface Emits {
(e: 'showLog', rowData: Api.Job.JobTask): void;
}
const emit = defineEmits<Emits>();
const { columns, data, loading, getData } = useTable({
apiFn: fetchGetJobTaskTree,
apiParams: {
page: 1,
size: 10,
groupName: props.rowData?.groupName,
taskBatchId: props.rowData?.id,
startId: 0,
fromIndex: 0
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
},
columns: () => [
{
key: 'id',
title: $t('page.jobBatch.jobTask.id'),
align: 'left',
minWidth: 100
},
{
key: 'index',
title: '日志',
align: 'center',
width: 64,
render: row => (
<NButton type="info" text onClick={() => emit('showLog', row)}>
<span class="w-28px ws-break-spaces">{$t('page.log.view')}</span>
</NButton>
)
},
{
key: 'groupName',
title: $t('page.jobBatch.jobTask.groupName'),
align: 'left',
minWidth: 180
},
{
key: 'taskStatus',
title: $t('page.jobBatch.jobTask.taskStatus'),
align: 'left',
minWidth: 80,
render: row => {
if (row.taskStatus === null) {
return null;
}
const label = $t(taskStatusRecord[row.taskStatus!]);
const tagMap: Record<number, NaiveUI.ThemeColor> = {
1: 'info',
2: 'info',
3: 'info',
4: 'error',
5: 'error',
6: 'error'
};
return <NTag type={tagMap[row.taskStatus!]}>{label}</NTag>;
}
},
{
key: 'clientInfo',
title: $t('page.jobBatch.jobTask.clientInfo'),
align: 'left',
minWidth: 150,
render: row => {
if (row.clientInfo) {
const parts = row.clientInfo?.split('@');
const result = parts.length > 1 ? parts[1] : '';
return <div>{result}</div>;
}
return <div>{row.clientInfo}</div>;
}
},
{
key: 'argsStr',
title: $t('page.jobBatch.jobTask.argsStr'),
align: 'center',
titleAlign: 'center',
minWidth: 120,
render: row => {
return (
<NPopover trigger="click">
{{
trigger: () => (
<NButton type="primary" text>
<span class="w-28px ws-break-spaces">{`查看\n参数`}</span>
</NButton>
),
default: () => (
<NCode
class="max-h-300px overflow-auto"
hljs={hljs}
code={parseArgsJson(row.argsStr)}
language="json"
show-line-numbers
/>
)
}}
</NPopover>
);
}
},
{
key: 'resultMessage',
title: $t('page.jobBatch.jobTask.resultMessage'),
align: 'left',
minWidth: 120
},
{
key: 'retryCount',
title: $t('page.jobBatch.jobTask.retryCount'),
align: 'left',
minWidth: 64
},
{
key: 'createDt',
title: $t('page.jobBatch.jobTask.createDt'),
align: 'left',
minWidth: 120
}
]
});
const { checkedRowKeys } = useTableOperate(data, getData);
</script>
<template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
size="small"
:loading="loading"
:row-key="row => row.id"
:scroll-x="962"
virtual-scroll
max-height="calc(100vh - 180px)"
:indent="32"
class="sm:h-full"
/>
</template>
<style scoped></style>