mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-17 02:03:16 +01:00
Invalidate goals after time entry and member changes
This commit is contained in:
@@ -342,6 +342,89 @@ test('test that a running time entry counts towards the goal progress', async ({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Close to midnight UTC an entry that started a few minutes ago could fall on the previous day, so
|
||||||
|
// the day of the goal is moved to a timezone (without daylight saving time) where it is late morning
|
||||||
|
function timezoneAwayFromMidnight() {
|
||||||
|
const utcHour = new Date().getUTCHours();
|
||||||
|
return utcHour >= 2 && utcHour < 22 ? 'UTC' : 'Asia/Tokyo';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigates via the sidebar, a full page load would start with an empty query cache and hide
|
||||||
|
// a missing goals invalidation
|
||||||
|
async function navigateViaSidebar(page: Page, name: 'Time' | 'Goals') {
|
||||||
|
await page.getByRole('link', { name, exact: true }).first().click();
|
||||||
|
await expect(page).toHaveURL(new RegExp('/' + name.toLowerCase() + '$'));
|
||||||
|
}
|
||||||
|
|
||||||
|
test('test that goal progress updates after creating a time entry without a page reload', async ({
|
||||||
|
page,
|
||||||
|
ctx,
|
||||||
|
}) => {
|
||||||
|
const goal = await createGoalViaApi(ctx, {
|
||||||
|
name: 'Cache goal',
|
||||||
|
target_seconds: 3600,
|
||||||
|
period: 'day',
|
||||||
|
timezone: timezoneAwayFromMidnight(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToGoalsOverview(page);
|
||||||
|
await expect(goalRow(page, goal.id).getByTestId('goal_tracked_time')).toHaveText('0h 00min');
|
||||||
|
|
||||||
|
await navigateViaSidebar(page, 'Time');
|
||||||
|
await page.getByRole('button', { name: 'Time entry actions' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Manual time entry' }).click();
|
||||||
|
const durationInput = page.locator('[role="dialog"] input[name="Duration"]');
|
||||||
|
await durationInput.fill('15min');
|
||||||
|
await durationInput.press('Tab');
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) => response.url().includes('/time-entries') && response.status() === 201
|
||||||
|
),
|
||||||
|
page.getByRole('button', { name: 'Create Time Entry' }).click(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await navigateViaSidebar(page, 'Goals');
|
||||||
|
await expect(goalRow(page, goal.id).getByTestId('goal_tracked_time')).toHaveText('0h 15min');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that goal progress updates after changing the project of the running time entry without a page reload', async ({
|
||||||
|
page,
|
||||||
|
ctx,
|
||||||
|
}) => {
|
||||||
|
const project = await createProjectViaApi(ctx, { name: 'Running Project', is_public: true });
|
||||||
|
const start = new Date(Date.now() - 30 * 60 * 1000).toISOString().replace(/\.\d{3}Z$/, 'Z');
|
||||||
|
await createRunningTimeEntryWithStartViaApi(ctx, 'Running work', start);
|
||||||
|
const goal = await createGoalViaApi(ctx, {
|
||||||
|
name: 'Running project goal',
|
||||||
|
target_seconds: 3600,
|
||||||
|
period: 'day',
|
||||||
|
timezone: timezoneAwayFromMidnight(),
|
||||||
|
filters: { project_ids: [project.id], time_entry_type: 'work' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await goToGoalsOverview(page);
|
||||||
|
await expect(goalRow(page, goal.id).getByTestId('goal_tracked_time')).toHaveText('0h 00min');
|
||||||
|
|
||||||
|
await navigateViaSidebar(page, 'Time');
|
||||||
|
await page.getByRole('button', { name: 'No Project' }).click();
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
async (response) =>
|
||||||
|
response.url().includes('/time-entries') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200 &&
|
||||||
|
(await response.json()).data.project_id === project.id
|
||||||
|
),
|
||||||
|
page.getByRole('option').filter({ hasText: project.name }).click(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await navigateViaSidebar(page, 'Goals');
|
||||||
|
// The entry keeps running while the test runs
|
||||||
|
await expect(goalRow(page, goal.id).getByTestId('goal_tracked_time')).toHaveText(
|
||||||
|
/^0h 3[0-2]min$/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('test that editing a goal via the modal works', async ({ page, ctx }) => {
|
test('test that editing a goal via the modal works', async ({ page, ctx }) => {
|
||||||
const goal = await createGoalViaApi(ctx, {
|
const goal = await createGoalViaApi(ctx, {
|
||||||
name: 'Old goal name',
|
name: 'Old goal name',
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const deleteMutation = useMutation({
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
close();
|
close();
|
||||||
queryClient.invalidateQueries({ queryKey: ['members'] });
|
queryClient.invalidateQueries({ queryKey: ['members'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ async function submit() {
|
|||||||
'There was an error merging the members.',
|
'There was an error merging the members.',
|
||||||
() => {
|
() => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['members'] });
|
queryClient.invalidateQueries({ queryKey: ['members'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
show.value = false;
|
show.value = false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -422,6 +422,7 @@ export function useBreakPlacement(deps: BreakPlacementDeps) {
|
|||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ export function useCopyLastWeek(
|
|||||||
}
|
}
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
|
|
||||||
if (attempted === 0) return;
|
if (attempted === 0) return;
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ export function useTimesheetCellMutations(
|
|||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
|||||||
stopLiveTimer();
|
stopLiveTimer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Failed to fetch current time entry because organization ID is missing.'
|
'Failed to fetch current time entry because organization ID is missing.'
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export function useTimeEntriesMutations() {
|
|||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ export function useTimeEntriesMutations() {
|
|||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -105,6 +107,7 @@ export function useTimeEntriesMutations() {
|
|||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,6 +130,7 @@ export function useTimeEntriesMutations() {
|
|||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -152,6 +156,7 @@ export function useTimeEntriesMutations() {
|
|||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user