mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-14 16:53:17 +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 }) => {
|
||||
const goal = await createGoalViaApi(ctx, {
|
||||
name: 'Old goal name',
|
||||
|
||||
@@ -43,6 +43,7 @@ const deleteMutation = useMutation({
|
||||
onSuccess: () => {
|
||||
close();
|
||||
queryClient.invalidateQueries({ queryKey: ['members'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ async function submit() {
|
||||
'There was an error merging the members.',
|
||||
() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['members'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
show.value = false;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -422,6 +422,7 @@ export function useBreakPlacement(deps: BreakPlacementDeps) {
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -217,6 +217,7 @@ export function useCopyLastWeek(
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
|
||||
if (attempted === 0) return;
|
||||
|
||||
|
||||
@@ -174,6 +174,7 @@ export function useTimesheetCellMutations(
|
||||
);
|
||||
} finally {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -261,6 +261,7 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
stopLiveTimer();
|
||||
}
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
} else {
|
||||
throw new Error(
|
||||
'Failed to fetch current time entry because organization ID is missing.'
|
||||
|
||||
@@ -36,6 +36,7 @@ export function useTimeEntriesMutations() {
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -58,6 +59,7 @@ export function useTimeEntriesMutations() {
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -105,6 +107,7 @@ export function useTimeEntriesMutations() {
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -127,6 +130,7 @@ export function useTimeEntriesMutations() {
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -152,6 +156,7 @@ export function useTimeEntriesMutations() {
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['goals'] });
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user