feat: 优化定时任务管理与日志体验
- 增加工作流选项、时间范围筛选和日志详情展示 - 支持可配置自动刷新、轻量局部更新与响应式布局
This commit is contained in:
@@ -122,6 +122,109 @@ describe('page data recovery', () => {
|
||||
expect(get).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('does not let a lightweight refresh supersede an active page query', async () => {
|
||||
let resolveInitialRequest: (value: {
|
||||
data: { records: { id: string }[]; totalRow: number };
|
||||
}) => void = () => {};
|
||||
const initialRequest = new Promise<{
|
||||
data: { records: { id: string }[]; totalRow: number };
|
||||
}>((resolve) => {
|
||||
resolveInitialRequest = resolve;
|
||||
});
|
||||
const get = vi.fn().mockReturnValueOnce(initialRequest);
|
||||
const wrapper = mount(PageData, {
|
||||
global: {
|
||||
directives: { loading: {} },
|
||||
},
|
||||
props: {
|
||||
pageUrl: '/page',
|
||||
refreshUrl: '/refresh',
|
||||
requestClient: { get },
|
||||
},
|
||||
});
|
||||
|
||||
const refreshRequest = (wrapper.vm as any).reload({
|
||||
lightweight: true,
|
||||
silent: true,
|
||||
});
|
||||
expect(get).toHaveBeenCalledTimes(1);
|
||||
|
||||
resolveInitialRequest({
|
||||
data: { records: [{ id: 'counted-result' }], totalRow: 21 },
|
||||
});
|
||||
await refreshRequest;
|
||||
await flushPromises();
|
||||
|
||||
expect(get).toHaveBeenCalledTimes(1);
|
||||
expect(wrapper.emitted('loadSuccess')?.at(-1)?.[0]).toMatchObject({
|
||||
lightweight: false,
|
||||
recordCount: 1,
|
||||
});
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('uses the lightweight refresh endpoint without replacing the total', async () => {
|
||||
const get = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: { records: [{ id: 'initial' }], totalRow: 35 },
|
||||
})
|
||||
.mockResolvedValueOnce({ data: [{ id: 'latest' }] });
|
||||
const wrapper = mount(PageData, {
|
||||
global: {
|
||||
directives: { loading: {} },
|
||||
},
|
||||
props: {
|
||||
pageUrl: '/page',
|
||||
refreshUrl: '/refresh',
|
||||
requestClient: { get },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
await (wrapper.vm as any).reload({ lightweight: true, silent: true });
|
||||
await flushPromises();
|
||||
|
||||
expect(get).toHaveBeenLastCalledWith('/refresh', {
|
||||
params: { pageNumber: 1, pageSize: 10 },
|
||||
});
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(true);
|
||||
expect(wrapper.emitted('loadSuccess')?.at(-1)?.[0]).toMatchObject({
|
||||
lightweight: true,
|
||||
pageNumber: 1,
|
||||
recordCount: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to the counted page endpoint outside the first page', async () => {
|
||||
const get = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { records: [], totalRow: 30 } });
|
||||
const wrapper = mount(PageData, {
|
||||
global: {
|
||||
directives: { loading: {} },
|
||||
},
|
||||
props: {
|
||||
initialPageNumber: 2,
|
||||
pageUrl: '/page',
|
||||
refreshUrl: '/refresh',
|
||||
requestClient: { get },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
await (wrapper.vm as any).reload({ lightweight: true, silent: true });
|
||||
await flushPromises();
|
||||
|
||||
expect(get).toHaveBeenLastCalledWith('/page', {
|
||||
params: { pageNumber: 2, pageSize: 10 },
|
||||
});
|
||||
expect(wrapper.emitted('loadSuccess')?.at(-1)?.[0]).toMatchObject({
|
||||
lightweight: false,
|
||||
pageNumber: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('restores a mounted list to a new route state with one target request', async () => {
|
||||
const get = vi
|
||||
.fn()
|
||||
|
||||
@@ -10,6 +10,7 @@ import { getEmptyStateImageUrl } from '#/utils/assets';
|
||||
|
||||
interface PageDataProps {
|
||||
pageUrl: string;
|
||||
refreshUrl?: string;
|
||||
pageSize?: number;
|
||||
pageSizes?: number[];
|
||||
extraQueryParams?: Record<string, any>;
|
||||
@@ -29,23 +30,38 @@ interface PageDataRestoreState extends PageDataState {
|
||||
}
|
||||
|
||||
interface PageDataReloadOptions {
|
||||
lightweight?: boolean;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
interface PageDataRequest {
|
||||
lightweight: boolean;
|
||||
silent: boolean;
|
||||
version: number;
|
||||
}
|
||||
|
||||
interface PageDataLoadEvent {
|
||||
lightweight: boolean;
|
||||
pageNumber: number;
|
||||
recordCount: number;
|
||||
}
|
||||
|
||||
interface PageDataLoadErrorEvent extends PageDataLoadEvent {
|
||||
error: unknown;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<PageDataProps>(), {
|
||||
pageSize: 10,
|
||||
pageSizes: () => [10, 20, 50, 100],
|
||||
refreshUrl: undefined,
|
||||
extraQueryParams: () => ({}),
|
||||
initialPageNumber: 1,
|
||||
initialQueryParams: () => ({}),
|
||||
requestClient: () => api,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(e: 'loadError', event: PageDataLoadErrorEvent): void;
|
||||
(e: 'loadSuccess', event: PageDataLoadEvent): void;
|
||||
(e: 'stateChange', state: PageDataState): void;
|
||||
}>();
|
||||
|
||||
@@ -67,10 +83,10 @@ const pageInfo = reactive({
|
||||
});
|
||||
|
||||
// 模拟 API 调用 - 这里需要根据你的实际 API 调用方式调整
|
||||
const doGet = async (params: Record<string, any>) => {
|
||||
const doGet = async (url: string, params: Record<string, any>) => {
|
||||
// 这里替换为你的实际 API 调用
|
||||
// 例如:return await api.get(props.pageUrl, { params })
|
||||
const response = await props.requestClient.get(`${props.pageUrl}`, {
|
||||
const response = await props.requestClient.get(url, {
|
||||
params,
|
||||
});
|
||||
const data = await response.data;
|
||||
@@ -78,14 +94,28 @@ const doGet = async (params: Record<string, any>) => {
|
||||
};
|
||||
|
||||
const loadPageListOnce = async (request: PageDataRequest) => {
|
||||
const lightweight = Boolean(
|
||||
request.lightweight && props.refreshUrl && pageInfo.pageNumber === 1,
|
||||
);
|
||||
try {
|
||||
const res = await doGet({
|
||||
const res = await doGet(lightweight ? props.refreshUrl! : props.pageUrl, {
|
||||
pageNumber: pageInfo.pageNumber,
|
||||
pageSize: pageInfo.pageSize,
|
||||
...props.extraQueryParams,
|
||||
...queryParams.value,
|
||||
});
|
||||
if (request.version === pageRequestVersion) {
|
||||
if (lightweight) {
|
||||
pageList.value = Array.isArray(res.data)
|
||||
? res.data
|
||||
: res.data?.records || [];
|
||||
emit('loadSuccess', {
|
||||
lightweight: true,
|
||||
pageNumber: pageInfo.pageNumber,
|
||||
recordCount: pageList.value.length,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const rawTotal = Number(res.data?.totalRow || 0);
|
||||
const total = Number.isFinite(rawTotal) ? Math.max(0, rawTotal) : 0;
|
||||
const lastPage = Math.max(1, Math.ceil(total / pageInfo.pageSize));
|
||||
@@ -96,6 +126,11 @@ const loadPageListOnce = async (request: PageDataRequest) => {
|
||||
return;
|
||||
}
|
||||
pageList.value = res.data?.records || [];
|
||||
emit('loadSuccess', {
|
||||
lightweight: false,
|
||||
pageNumber: pageInfo.pageNumber,
|
||||
recordCount: pageList.value.length,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (request.version === pageRequestVersion) {
|
||||
@@ -104,12 +139,24 @@ const loadPageListOnce = async (request: PageDataRequest) => {
|
||||
pageList.value = [];
|
||||
pageInfo.total = 0;
|
||||
}
|
||||
emit('loadError', {
|
||||
error,
|
||||
lightweight,
|
||||
pageNumber: pageInfo.pageNumber,
|
||||
recordCount: pageList.value.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const requestPageList = (silent: boolean) => {
|
||||
const requestPageList = (silent: boolean, lightweight = false) => {
|
||||
// 自动刷新只补充最新行;已有请求能够提供同等或更完整的数据时直接复用,
|
||||
// 避免它使正在进行的分页查询失效或额外排队。
|
||||
if (activePageRequest && lightweight) {
|
||||
return activePageRequest;
|
||||
}
|
||||
const request: PageDataRequest = {
|
||||
lightweight,
|
||||
silent: silent && pageList.value.length > 0,
|
||||
version: ++pageRequestVersion,
|
||||
};
|
||||
@@ -120,6 +167,7 @@ const requestPageList = (silent: boolean) => {
|
||||
if (activePageRequest) {
|
||||
pendingPageRequest = pendingPageRequest
|
||||
? {
|
||||
lightweight: pendingPageRequest.lightweight && request.lightweight,
|
||||
silent: pendingPageRequest.silent && request.silent,
|
||||
version: request.version,
|
||||
}
|
||||
@@ -143,10 +191,10 @@ const requestPageList = (silent: boolean) => {
|
||||
};
|
||||
|
||||
// 获取页面数据
|
||||
const getPageList = () => requestPageList(false);
|
||||
const getPageList = () => requestPageList(false, false);
|
||||
|
||||
const reload = (options: PageDataReloadOptions = {}) =>
|
||||
requestPageList(Boolean(options.silent));
|
||||
requestPageList(Boolean(options.silent), Boolean(options.lightweight));
|
||||
|
||||
// 分页事件处理
|
||||
const handleSizeChange = (newSize: number) => {
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
"workflow": "Workflow",
|
||||
"beanMethod": "BeanMethod",
|
||||
"javaMethod": "JavaMethod",
|
||||
"example": "example"
|
||||
"example": "example",
|
||||
"triggerAccepted": "The job has been submitted"
|
||||
}
|
||||
|
||||
@@ -7,6 +7,37 @@
|
||||
"jobResult": "JobResult",
|
||||
"errorInfo": "ErrorInfo",
|
||||
"status": "Status",
|
||||
"jobInfo": "Job information",
|
||||
"triggerSource": "Trigger source",
|
||||
"timeField": "Fire time type",
|
||||
"scheduledFireTime": "Scheduled fire time",
|
||||
"actualFireTime": "Actual fire time",
|
||||
"attemptCount": "Claim attempts",
|
||||
"manual": "Manual",
|
||||
"scheduled": "Scheduled",
|
||||
"rangeStart": "Start time",
|
||||
"rangeEnd": "End time",
|
||||
"autoRefresh": "Auto refresh",
|
||||
"refreshInterval": "Refresh interval",
|
||||
"seconds": "s",
|
||||
"manualRefresh": "Refresh now",
|
||||
"refreshing": "Refreshing",
|
||||
"updated": "updated",
|
||||
"notRefreshed": "Waiting for refresh",
|
||||
"historyPagePaused": "Paused on history page",
|
||||
"refreshFailedPaused": "Refresh failed and paused",
|
||||
"unknownStatus": "Unknown status",
|
||||
"unknownSource": "Unknown source",
|
||||
"executionWindow": "Execution window",
|
||||
"executionSummary": "Execution summary",
|
||||
"duration": "Duration",
|
||||
"noExecutionOutput": "No execution output",
|
||||
"detailTitle": "Execution details",
|
||||
"executionNode": "Execution node",
|
||||
"jobOptions": "Job option snapshot",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
"copyFailed": "Copy failed; select the content manually",
|
||||
"startTime": "StartTime",
|
||||
"endTime": "EndTime",
|
||||
"created": "Created",
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
"workflow": "工作流",
|
||||
"beanMethod": "bean方法",
|
||||
"javaMethod": "java方法",
|
||||
"example": "示例"
|
||||
"example": "示例",
|
||||
"triggerAccepted": "任务已提交执行"
|
||||
}
|
||||
|
||||
@@ -7,6 +7,37 @@
|
||||
"jobResult": "执行结果",
|
||||
"errorInfo": "错误信息",
|
||||
"status": "执行状态",
|
||||
"jobInfo": "任务信息",
|
||||
"triggerSource": "触发来源",
|
||||
"timeField": "触发时间类型",
|
||||
"scheduledFireTime": "计划触发时间",
|
||||
"actualFireTime": "实际触发时间",
|
||||
"attemptCount": "认领次数",
|
||||
"manual": "手动",
|
||||
"scheduled": "计划",
|
||||
"rangeStart": "开始时间",
|
||||
"rangeEnd": "结束时间",
|
||||
"autoRefresh": "自动刷新",
|
||||
"refreshInterval": "刷新间隔",
|
||||
"seconds": "秒",
|
||||
"manualRefresh": "立即刷新",
|
||||
"refreshing": "正在刷新",
|
||||
"updated": "已更新",
|
||||
"notRefreshed": "等待刷新",
|
||||
"historyPagePaused": "历史页已暂停",
|
||||
"refreshFailedPaused": "刷新失败,已暂停",
|
||||
"unknownStatus": "未知状态",
|
||||
"unknownSource": "未知来源",
|
||||
"executionWindow": "执行窗口",
|
||||
"executionSummary": "执行摘要",
|
||||
"duration": "耗时",
|
||||
"noExecutionOutput": "暂无执行输出",
|
||||
"detailTitle": "执行详情",
|
||||
"executionNode": "执行节点",
|
||||
"jobOptions": "任务配置快照",
|
||||
"copy": "复制",
|
||||
"copied": "已复制",
|
||||
"copyFailed": "复制失败,请手动选择内容",
|
||||
"startTime": "开始时间",
|
||||
"endTime": "结束时间",
|
||||
"created": "创建时间",
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
MoreFilled,
|
||||
Plus,
|
||||
Tickets,
|
||||
VideoPlay,
|
||||
} from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElButton,
|
||||
@@ -66,6 +67,7 @@ watchListRouteState(route, sysJobListRouteSchema, (state) => {
|
||||
});
|
||||
});
|
||||
const saveDialog = ref();
|
||||
const triggeringJobId = ref<string | undefined>();
|
||||
const dictStore = useDictStore();
|
||||
const headerButtons = [
|
||||
{
|
||||
@@ -131,19 +133,24 @@ function start(row: any) {
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true;
|
||||
api.get(`/api/v1/sysJob/start?id=${row.id}`).then((res) => {
|
||||
instance.confirmButtonLoading = false;
|
||||
api
|
||||
.get(`/api/v1/sysJob/start?id=${row.id}`)
|
||||
.then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message);
|
||||
reloadCurrentList();
|
||||
done();
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
instance.confirmButtonLoading = false;
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
},
|
||||
});
|
||||
}).catch(() => {});
|
||||
}
|
||||
function stop(row: any) {
|
||||
ElMessageBox.confirm($t('message.stopAlert'), $t('message.noticeTitle'), {
|
||||
@@ -153,19 +160,36 @@ function stop(row: any) {
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true;
|
||||
api.get(`/api/v1/sysJob/stop?id=${row.id}`).then((res) => {
|
||||
instance.confirmButtonLoading = false;
|
||||
api
|
||||
.get(`/api/v1/sysJob/stop?id=${row.id}`)
|
||||
.then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message);
|
||||
reloadCurrentList();
|
||||
done();
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
instance.confirmButtonLoading = false;
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
},
|
||||
});
|
||||
}).catch(() => {});
|
||||
}
|
||||
async function trigger(row: any) {
|
||||
if (triggeringJobId.value) return;
|
||||
triggeringJobId.value = String(row.id);
|
||||
try {
|
||||
const res = await api.get(`/api/v1/sysJob/trigger?id=${row.id}`);
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success($t('sysJob.triggerAccepted'));
|
||||
}
|
||||
} finally {
|
||||
triggeringJobId.value = undefined;
|
||||
}
|
||||
}
|
||||
function toLogPage(row: any) {
|
||||
router.push({
|
||||
@@ -313,6 +337,18 @@ function handlePageStateChange(state: {
|
||||
v-if="row.status === 1"
|
||||
v-access:code="'/api/v1/sysJob/save'"
|
||||
>
|
||||
<ElDropdownItem
|
||||
:disabled="Boolean(triggeringJobId)"
|
||||
@click="trigger(row)"
|
||||
>
|
||||
<ElButton
|
||||
:icon="VideoPlay"
|
||||
:loading="triggeringJobId === String(row.id)"
|
||||
link
|
||||
>
|
||||
{{ $t('button.run') }}
|
||||
</ElButton>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem @click="stop(row)">
|
||||
<ElButton :icon="CircleCloseFilled" link>
|
||||
{{ $t('button.stop') }}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
/* eslint-disable vue/one-component-per-file -- Inline stubs isolate timer behavior. */
|
||||
import { flushPromises, mount } from '@vue/test-utils';
|
||||
import { defineComponent, h, onMounted } from 'vue';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import SysJobLogList from './SysJobLogList.vue';
|
||||
|
||||
const pageDataMocks = vi.hoisted(() => ({
|
||||
emitLoadError: () => {},
|
||||
reload: vi.fn(() => Promise.resolve()),
|
||||
setQuery: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => ({ query: { jobId: 'job-1' } }),
|
||||
useRouter: () => ({}),
|
||||
}));
|
||||
vi.mock('#/locales', () => ({ $t: (key: string) => key }));
|
||||
vi.mock('#/router/list-return-context', () => ({
|
||||
navigateBackToList: vi.fn(),
|
||||
}));
|
||||
vi.mock('#/store', () => ({
|
||||
useDictStore: () => ({
|
||||
fetchDictionary: vi.fn(),
|
||||
getDictLabel: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
vi.mock('#/components/dict/DictSelect.vue', () => ({
|
||||
default: defineComponent({
|
||||
name: 'DictSelect',
|
||||
setup: () => () => h('div'),
|
||||
}),
|
||||
}));
|
||||
vi.mock('#/components/page/ListPageShell.vue', () => ({
|
||||
default: defineComponent({
|
||||
name: 'ListPageShell',
|
||||
setup(_, { slots }) {
|
||||
return () =>
|
||||
h('div', [slots.filters?.(), slots.actions?.(), slots.default?.()]);
|
||||
},
|
||||
}),
|
||||
}));
|
||||
vi.mock('#/components/page/PageData.vue', () => ({
|
||||
default: defineComponent({
|
||||
name: 'PageData',
|
||||
emits: ['loadError', 'loadSuccess', 'stateChange'],
|
||||
setup(_, { emit, expose }) {
|
||||
expose({
|
||||
reload: pageDataMocks.reload,
|
||||
setQuery: pageDataMocks.setQuery,
|
||||
});
|
||||
pageDataMocks.emitLoadError = () =>
|
||||
emit('loadError', {
|
||||
error: new Error('refresh failed'),
|
||||
lightweight: true,
|
||||
pageNumber: 1,
|
||||
recordCount: 1,
|
||||
});
|
||||
onMounted(() => {
|
||||
emit('stateChange', { pageNumber: 1, pageSize: 10 });
|
||||
emit('loadSuccess', {
|
||||
lightweight: false,
|
||||
pageNumber: 1,
|
||||
recordCount: 1,
|
||||
});
|
||||
});
|
||||
return () => h('div');
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const ElButtonStub = defineComponent({
|
||||
name: 'ElButton',
|
||||
setup(_, { attrs, slots }) {
|
||||
return () => h('button', attrs, slots.default?.());
|
||||
},
|
||||
});
|
||||
const ElSwitchStub = defineComponent({
|
||||
name: 'ElSwitch',
|
||||
emits: ['change', 'update:modelValue'],
|
||||
setup(_, { emit }) {
|
||||
return () =>
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
'data-testid': 'auto-refresh-switch',
|
||||
onClick: () => {
|
||||
emit('update:modelValue', true);
|
||||
emit('change', true);
|
||||
},
|
||||
},
|
||||
'auto refresh',
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function mountPage() {
|
||||
return mount(SysJobLogList, {
|
||||
global: {
|
||||
stubs: {
|
||||
ElButton: ElButtonStub,
|
||||
ElDatePicker: true,
|
||||
ElDrawer: true,
|
||||
ElOption: true,
|
||||
ElSelect: true,
|
||||
ElSwitch: ElSwitchStub,
|
||||
ElTable: true,
|
||||
ElTableColumn: true,
|
||||
ElTag: true,
|
||||
ElTooltip: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('sys job log auto refresh', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.clearAllMocks();
|
||||
localStorage.setItem('easyflow.sysJobLog.autoRefreshIntervalSeconds', '5');
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
configurable: true,
|
||||
value: 'visible',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('refreshes immediately and schedules the next lightweight request', async () => {
|
||||
const wrapper = mountPage();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.get('[data-testid="auto-refresh-switch"]').trigger('click');
|
||||
await flushPromises();
|
||||
expect(pageDataMocks.reload).toHaveBeenCalledWith({
|
||||
lightweight: true,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(5000);
|
||||
await flushPromises();
|
||||
expect(pageDataMocks.reload).toHaveBeenCalledTimes(2);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('stops polling after a lightweight refresh failure', async () => {
|
||||
const wrapper = mountPage();
|
||||
await flushPromises();
|
||||
await wrapper.get('[data-testid="auto-refresh-switch"]').trigger('click');
|
||||
await flushPromises();
|
||||
expect(pageDataMocks.reload).toHaveBeenCalledTimes(1);
|
||||
|
||||
pageDataMocks.emitLoadError();
|
||||
await flushPromises();
|
||||
await vi.advanceTimersByTimeAsync(10_000);
|
||||
expect(pageDataMocks.reload).toHaveBeenCalledTimes(1);
|
||||
expect(wrapper.text()).toContain('sysJobLog.refreshFailedPaused');
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('maps the single time range to the selected fire time field', async () => {
|
||||
const wrapper = mountPage();
|
||||
await flushPromises();
|
||||
const viewModel = wrapper.vm as any;
|
||||
viewModel.filters.status = 1;
|
||||
viewModel.filters.timeField = 'actual';
|
||||
viewModel.filters.timeRange = [
|
||||
'2026-08-31 10:00:00',
|
||||
'2026-08-31 11:00:00',
|
||||
];
|
||||
await flushPromises();
|
||||
|
||||
const queryButton = wrapper
|
||||
.findAll('button')
|
||||
.find((button) => button.text() === 'button.query');
|
||||
await queryButton?.trigger('click');
|
||||
|
||||
expect(pageDataMocks.setQuery).toHaveBeenLastCalledWith({
|
||||
actualEnd: '2026-08-31 11:00:00',
|
||||
actualStart: '2026-08-31 10:00:00',
|
||||
status: 1,
|
||||
});
|
||||
|
||||
viewModel.filters.timeField = 'scheduled';
|
||||
await queryButton?.trigger('click');
|
||||
expect(pageDataMocks.setQuery).toHaveBeenLastCalledWith({
|
||||
scheduledEnd: '2026-08-31 11:00:00',
|
||||
scheduledStart: '2026-08-31 10:00:00',
|
||||
status: 1,
|
||||
});
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user