Skip to content

Commit 9556078

Browse files
authored
refactor(auth): update Google connection UI and logic (#1591)
* refactor(auth): update Google connection UI and logic - Changed sidebar status icons to use a unified "DotIcon" for various Google Calendar connection states, enhancing visual consistency. - Introduced a new `StatusDotPopover` component to manage repair dialogs, improving user interaction during repair processes. - Updated tests to reflect changes in icon usage and dialog behavior, ensuring comprehensive coverage of the new UI flow. - Enhanced the `getGoogleConnectionConfig` function to include `dotColor` and dialog properties for better state representation. * refactor(auth): update Google connection UI and logic - Changed sidebar status icons to use a unified "DotIcon" across various connection states, enhancing visual consistency. - Introduced a new `StatusDotPopover` component for improved user interaction during repair actions, providing a dialog with repair options. - Updated the `useConnectGoogle` hook to return an `isRepairing` state, allowing the UI to reflect ongoing repair processes. - Enhanced tests to cover new UI behaviors and ensure proper functionality of the updated components and hooks. * refactor(sync): enhance StatusDotPopover for improved user feedback - Introduced a dynamic display title in the StatusDotPopover to reflect the repairing state, enhancing user awareness during calendar repairs. - Updated the aria-label and title properties to utilize the new display title, improving accessibility. - Conditionally rendered the description based on the repairing state, streamlining the UI during repair processes. - Added logic to prevent rendering the SyncStatusDot when user attention is not needed, optimizing performance and user experience. * feat(auth): implement anonymous calendar change sign-up prompt - Introduced functionality to manage the sign-up prompt for users making anonymous calendar changes, enhancing user engagement and retention. - Updated the authentication state management to include flags for prompting sign-up after anonymous calendar changes. - Enhanced the `useCompleteAuthentication` hook to clear the sign-up prompt when a user is authenticated. - Added tests to ensure the correct behavior of the sign-up prompt logic during event creation and editing. - Refactored related components and hooks to utilize the new sign-up prompt functionality, improving overall user experience. * fix(auth): update tooltip message for Google Calendar repair status - Changed the tooltip message in the sidebar status from "Google Calendar needs repair. Click to repair." to "Repairing Google Calendar in the background." to better reflect the current state during repairs. - Updated the corresponding test to ensure the new tooltip message is correctly asserted in the `useConnectGoogle` hook tests. * feat(sync): add sync dot pulse animation and update component logic - Introduced a new animation for the sync dot with a pulse effect to enhance visual feedback during synchronization. - Updated the `SyncStatusDot` component to conditionally apply the new animation based on the anonymous sign-up prompt state. - Enhanced the `useSyncStatusDotState` hook to manage the new `isAnonymousSignUpPrompt` state, improving user engagement. - Added tests to verify the correct application of the new animation and its behavior in different states. * refactor(auth): remove AccountIcon component and related tests - Deleted the `AccountIcon` component as part of the refactoring process to streamline the authentication modal. - Removed associated tests from `AuthModal.test.tsx` to reflect the component's removal. - Updated `Header` components in both Calendar and Day views to eliminate references to `AccountIcon`, ensuring a cleaner codebase. * refactor(calendar): simplify Header component structure and remove unused styles - Refactored the `Header` component in the Calendar view to streamline its structure by replacing styled components with utility classes. - Removed unused styled components and associated styles from the `styled.ts` file, enhancing code clarity and maintainability. - Updated the layout to utilize flexbox for better alignment and responsiveness, improving the overall user interface. * refactor(calendar): streamline Header component by removing styled components - Simplified the `Header` component in the Calendar view by replacing styled components with utility classes for better maintainability. - Removed unused styled components from `styled.ts`, enhancing code clarity. - Updated the layout to utilize standard HTML elements and flexbox for improved alignment and responsiveness. * refactor(calendar): replace styled components with utility classes in DayLabels - Updated the `DayLabels` component in the Calendar view to eliminate styled components, enhancing maintainability and readability. - Replaced `StyledWeekDaysFlex` and `StyledWeekDayFlex` with standard HTML elements and utility classes for improved layout and responsiveness. - Removed the now-unnecessary `styled.ts` file, streamlining the codebase. * refactor(sync): separate StatusDotPopover into its own component - Extracted the `StatusDotPopover` from the `SyncStatusDot` component to improve modularity and maintainability. - Updated the tooltip message for the anonymous sign-up prompt to be more concise. - Adjusted tests to reflect changes in the component structure and updated tooltip text. * refactor(calendar): remove Info icon component and update SubCalendarList structure - Deleted the `Info` icon component to streamline the codebase. - Refactored the `SubCalendarList` component to incorporate a tooltip for temporary account information, enhancing user experience. - Updated the layout to utilize utility classes, improving maintainability and readability. - Added tests for the `SubCalendarList` component to ensure correct rendering and functionality. * refactor(auth): update Google connection status handling and introduce HeaderInfoIcon component - Renamed `dotColor` to `iconColor` in the Google connection types and related components for consistency. - Updated the `useConnectGoogle` hook and associated tests to reflect the new naming convention. - Introduced the `HeaderInfoIcon` component to display connection status with improved tooltip handling and user prompts. - Replaced `SyncStatusDot` with `HeaderInfoIcon` in the Calendar and Day view headers for enhanced user feedback. - Added tests for the new `HeaderInfoIcon` component to ensure correct rendering and functionality. * refactor(auth): remove icon references from Google connection handling - Eliminated `icon` property from the sidebar status in the Google connection types and related components to streamline the state management. - Updated tests in the `useConnectGoogle` hook to reflect the removal of icon assertions. - Adjusted the `HeaderInfoIcon` component to remove icon references, ensuring consistency across the application. * refactor(auth): enhance UserProvider and HeaderInfoIcon components for improved user experience - Updated the `UserProvider` to utilize the `useSession` hook for managing authentication state and profile loading. - Added a new test case in `UserProvider.test.tsx` to verify profile fetching behavior based on authentication state. - Enhanced the `HeaderInfoIcon` component to display a spinner during background calendar imports, improving user feedback. - Refactored `useHeaderInfo` to include background import state, ensuring accurate rendering in the `HeaderInfoIcon`. - Adjusted tests in `SidebarIconRow` to confirm the spinner is not rendered when importing is in progress. * refactor(auth): streamline UserProvider and introduce useLoadProfile hook - Refactored the `UserProvider` to utilize the new `useLoadProfile` hook for improved profile loading and state management. - Removed unnecessary state variables and effects related to user loading, enhancing code clarity. - Updated the `UserContext` to reflect changes in the user profile structure. - Added tests for the new `useLoadProfile` hook to ensure correct profile fetching behavior. - Simplified the integration with PostHog for user identification through the `useIdentifyUser` hook. * feat(auth): implement Google authentication flow and enhance session management - Introduced `useCompleteAuthentication` hook to streamline the Google authentication process, managing user state and metadata refresh. - Added `useConnectGoogle` hook to handle Google Calendar connection, including error handling and user prompts. - Created `useGoogleAuth` and `useGoogleAuthWithOverlay` hooks for improved Google login experience with overlay support. - Implemented tests for new hooks to ensure correct functionality and error handling during authentication. - Refactored related components and utilities for better integration with the new authentication flow. * feat(tests): enhance e2e tests for Google Calendar connection status - Added new rules in ESLint configuration for TypeScript files in e2e tests to accommodate Playwright usage. - Updated the sidebar connection status tests to improve clarity and accuracy, including renaming helper functions and adjusting assertions. - Introduced a new utility function to assert Google connection state in Redux, enhancing test reliability. - Refactored existing tests to focus on Redux state management and UI visibility for connection states. * feat(sync): enhance Google Calendar import handling and introduce auto-import logic - Added `triggerAutoImport` action to the sync slice to initiate non-forced imports without showing a spinner. - Updated `useConnectGoogle` hook to dispatch the new `request` action for active imports and `triggerAutoImport` for RESTART state. - Introduced utility functions `isGoogleCalendarImportActive` and `isGoogleCalendarAutoImportNeeded` to manage import states effectively. - Implemented tests to validate the behavior of auto-import logic and spinner visibility during different import states. - Refactored related components and tests to ensure consistent handling of Google Calendar import states. * feat(sync): enhance Google Calendar import handling with new operation types - Introduced `ImportOperation` type to support both "INCREMENTAL" and "REPAIR" operations in import summaries. - Updated `parseImportResult` function to accept operation type as a parameter, improving flexibility in handling import results. - Refactored sync service and controller tests to utilize the new operation types, ensuring consistent behavior across import scenarios. - Enhanced user metadata handling to reconcile import states based on metadata updates, improving user experience during Google Calendar imports. - Updated tests to validate the new import operation logic and ensure accurate state management during imports. * feat(sync): enhance Google Calendar metadata handling and user feedback - Updated `UserMetadataService` to return "ATTENTION" state when a restart is pending, improving user awareness of synchronization status. - Added a new test case to validate the "ATTENTION" state functionality in `user-metadata.service.test.ts`. - Refactored Google Calendar import logic to streamline state management and improve user experience during metadata refresh. - Removed deprecated import state handling from various components and tests, ensuring cleaner code and better maintainability. - Enhanced `useConnectGoogle` and related hooks to improve error handling and user prompts during Google Calendar connection processes. * feat(sync): improve Google Calendar sync handling and UI feedback - Added logic to ignore duplicate restart requests while a full sync is in progress, enhancing user experience by preventing unnecessary operations. - Introduced a new test case to validate the behavior of duplicate restart requests in `sync.service.test.ts`. - Updated `SyncService` to manage active full sync restarts, ensuring proper state handling during synchronization. - Refactored Google Sync UI state management to utilize a more descriptive indicator for syncing states, improving clarity in the user interface. - Enhanced tests across various components to ensure accurate handling of sync states and user feedback during Google Calendar operations. * fix(sync): correct promise handling in sync service tests - Updated the test for `fetchMetadataDeferred` to await the promise directly, ensuring proper synchronization in the test execution. - This change enhances the reliability of the test case by accurately reflecting the intended asynchronous behavior. * refactor(auth): reorganize Google authentication utilities and state management - Updated import paths for Google authentication utilities to improve code organization and clarity. - Refactored Google sync state management to utilize a dedicated state module, enhancing maintainability. - Introduced new utility functions for handling Google authentication errors and revocation states. - Added tests for new utility functions to ensure correct behavior in various scenarios. - Improved overall structure of Google authentication-related code for better readability and maintainability. * feat(auth): implement session management and user profile loading - Introduced `SessionProvider` and `useSession` hook to manage user authentication state and session handling. - Added `UserProvider` and `useLoadProfile` hook for fetching user profile data based on authentication status. - Created utility functions for session management and user state updates, enhancing overall user experience. - Implemented comprehensive tests for session and user profile functionalities to ensure reliability and correctness. - Refactored existing components to integrate with the new session and user management structure, improving code organization and maintainability. * fix(auth): update Google OAuth error utility imports and add tests - Updated import paths for the Google OAuth error utility to improve consistency and clarity. - Introduced a new utility file for handling Google popup closed errors, enhancing error management. - Added comprehensive tests for the new error utility to ensure correct behavior across various scenarios. * feat(auth): add Posthog utility function and integrate into CompassProvider - Introduced a new utility function `isPosthogEnabled` to check Posthog configuration based on environment variables. - Removed the redundant local implementation of `isPosthogEnabled` from `CompassProvider` and replaced it with the new utility import, improving code organization and maintainability.
1 parent 6a3eae1 commit 9556078

122 files changed

Lines changed: 2685 additions & 2386 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 33 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
1-
import { expect, test } from "@playwright/test";
1+
import { type Page, expect, test } from "@playwright/test";
22
import {
33
type GoogleConnectionState,
44
SIDEBAR_STATUS_LABELS,
5+
expectGoogleConnectionStateInStore,
56
markUserAsAuthenticated,
67
prepareOAuthTestPage,
78
setGoogleConnectionState,
89
waitForAppReady,
910
} from "../utils/oauth-test-utils";
1011

1112
/**
12-
* E2E tests for sidebar Google Calendar connection status.
13+
* E2E tests for Google Calendar connection state (Redux + header status when visible).
1314
*
14-
* These tests verify that the sidebar status container correctly reflects the 5 connection
15-
* states from the server (GoogleConnectionState), plus the client-only "checking"
16-
* state that appears while metadata is loading.
17-
*
18-
* Connection states and their status messages:
19-
* - NOT_CONNECTED: "Google Calendar not connected. Click to connect."
20-
* - RECONNECT_REQUIRED: "Google Calendar needs reconnecting. Click to reconnect."
21-
* - IMPORTING: "Google Calendar is syncing in the background."
22-
* - HEALTHY: "Google Calendar connected."
23-
* - ATTENTION: "Google Calendar needs repair. Click to repair."
24-
* - "checking" (client-only): "Checking Google Calendar status…"
25-
*
26-
* The status is rendered in SidebarIconRow with role="status" and managed by useConnectGoogle hook.
15+
* HeaderInfoIcon only renders role="status" for warning/error states (reconnect required,
16+
* needs repair). Other states are reflected in Redux only; command palette still exposes
17+
* connect/repair actions.
2718
*
2819
* NOTE: These tests are skipped on mobile because the MobileGate component
2920
* blocks the entire app on mobile viewports.
@@ -35,10 +26,8 @@ test.describe("Sidebar Connection Status", () => {
3526
// Run tests serially to avoid state interference
3627
test.describe.configure({ mode: "serial" });
3728

38-
// Helper to get the sidebar status container
39-
// Filter: has aria-label (excludes DndLiveRegion), no aria-busy (excludes overlay)
40-
const getSidebarStatus = (page: import("@playwright/test").Page) =>
41-
page.locator('#sidebar [role="status"][aria-label]:not([aria-busy])');
29+
const getHeaderGoogleStatus = (page: Page) =>
30+
page.locator("#cal").getByRole("status", { name: /Google Calendar/i });
4231

4332
test.beforeEach(async ({ page }) => {
4433
await prepareOAuthTestPage(page);
@@ -50,48 +39,33 @@ test.describe("Sidebar Connection Status", () => {
5039
await setGoogleConnectionState(page, "NOT_CONNECTED");
5140
});
5241

53-
test("shows NOT_CONNECTED status", async ({ page }) => {
54-
// State already set in beforeEach - just verify it
55-
56-
const status = getSidebarStatus(page);
57-
await expect(status).toHaveAttribute(
58-
"aria-label",
59-
SIDEBAR_STATUS_LABELS.notConnected,
60-
);
42+
test("stores NOT_CONNECTED in Redux (header icon hidden for muted state)", async ({
43+
page,
44+
}) => {
45+
await expectGoogleConnectionStateInStore(page, "NOT_CONNECTED");
6146
});
6247

63-
test("shows checking status when metadata is loading", async ({ page }) => {
64-
// The "checking" state requires:
65-
// 1. userMetadataStatus === "loading"
66-
// 2. hasUserEverAuthenticated() returns true (localStorage flag)
67-
//
68-
// Set the localStorage flag first, then force loading state.
48+
test("checking path: authenticated user with metadata loading", async ({
49+
page,
50+
}) => {
6951
await markUserAsAuthenticated(page);
7052

71-
// Force the loading state by dispatching both clear and setLoading
7253
await page.evaluate(() => {
7354
const store = window.__COMPASS_E2E_STORE__;
7455
if (!store) return;
75-
// Clear any existing metadata
7656
store.dispatch({ type: "userMetadata/clear" });
77-
// Set to loading state
7857
store.dispatch({ type: "userMetadata/setLoading" });
7958
});
8059

81-
// Wait for state to be in loading
8260
await page.waitForFunction(
8361
() =>
8462
window.__COMPASS_E2E_STORE__?.getState()?.userMetadata?.status ===
8563
"loading",
8664
{ timeout: 5000 },
8765
);
8866

89-
// Wait for status container to show "checking" state via aria-label
90-
const status = getSidebarStatus(page);
91-
await expect(status).toHaveAttribute(
92-
"aria-label",
93-
/Checking Google Calendar/i,
94-
);
67+
// "Checking" does not render HeaderInfoIcon (muted / no warning-error icon).
68+
await expect(getHeaderGoogleStatus(page)).toHaveCount(0);
9569
});
9670

9771
test("shows IMPORTING status", async ({ page }) => {
@@ -118,11 +92,6 @@ test.describe("Sidebar Connection Status - State Transitions", () => {
11892
// Run tests serially to avoid state interference
11993
test.describe.configure({ mode: "serial" });
12094

121-
// Helper to get the sidebar status container
122-
// Filter: has aria-label (excludes DndLiveRegion), no aria-busy (excludes overlay)
123-
const getSidebarStatus = (page: import("@playwright/test").Page) =>
124-
page.locator('#sidebar [role="status"][aria-label]:not([aria-busy])');
125-
12695
test.beforeEach(async ({ page }) => {
12796
await prepareOAuthTestPage(page);
12897
await page.goto("/week");
@@ -133,23 +102,20 @@ test.describe("Sidebar Connection Status - State Transitions", () => {
133102
await setGoogleConnectionState(page, "NOT_CONNECTED");
134103
});
135104

136-
test("transitions from NOT_CONNECTED to HEALTHY correctly", async ({
105+
test("transitions from RECONNECT_REQUIRED to ATTENTION correctly", async ({
137106
page,
138107
}) => {
139-
const status = getSidebarStatus(page);
140-
141-
// State already set to NOT_CONNECTED in beforeEach - just verify it
142-
await expect(status).toHaveAttribute(
143-
"aria-label",
144-
SIDEBAR_STATUS_LABELS.notConnected,
145-
);
108+
await setGoogleConnectionState(page, "RECONNECT_REQUIRED");
109+
await expect(
110+
page.getByRole("status", {
111+
name: SIDEBAR_STATUS_LABELS.reconnectRequired,
112+
}),
113+
).toBeVisible();
146114

147-
// Transition to HEALTHY (simulating successful OAuth flow)
148-
await setGoogleConnectionState(page, "HEALTHY");
149-
await expect(status).toHaveAttribute(
150-
"aria-label",
151-
SIDEBAR_STATUS_LABELS.connected,
152-
);
115+
await setGoogleConnectionState(page, "ATTENTION");
116+
await expect(
117+
page.getByRole("status", { name: SIDEBAR_STATUS_LABELS.needsRepair }),
118+
).toBeVisible();
153119
});
154120

155121
test("cycles through connection states without visual glitches", async ({
@@ -165,14 +131,12 @@ test.describe("Sidebar Connection Status - State Transitions", () => {
165131

166132
for (const state of states) {
167133
await setGoogleConnectionState(page, state);
168-
// setGoogleConnectionState now waits for aria-label, no timeout needed
169134
}
170135

171-
// Should end on RECONNECT_REQUIRED
172-
const status = getSidebarStatus(page);
173-
await expect(status).toHaveAttribute(
174-
"aria-label",
175-
SIDEBAR_STATUS_LABELS.reconnectRequired,
176-
);
136+
await expect(
137+
page.getByRole("status", {
138+
name: SIDEBAR_STATUS_LABELS.reconnectRequired,
139+
}),
140+
).toBeVisible();
177141
});
178142
});

e2e/utils/oauth-test-utils.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Page, expect } from "@playwright/test";
1+
import { type Page, expect } from "@playwright/test";
22
import "./compass-window";
33

44
/**
@@ -200,13 +200,20 @@ const CONNECTION_STATE_TO_LABEL: Record<GoogleConnectionState, RegExp> = {
200200
ATTENTION: /needs repair/i,
201201
};
202202

203-
const SIDEBAR_STATUS_SELECTOR =
204-
"#sidebar [role='status'][aria-label]:not([aria-busy])";
203+
/**
204+
* HeaderInfoIcon only renders role="status" for warning/error connection states
205+
* (see HeaderInfoIcon.tsx). Muted / checking / importing-without-background-import
206+
* do not expose this region.
207+
*/
208+
const GOOGLE_HEADER_STATUS_VISIBLE_STATES: GoogleConnectionState[] = [
209+
"RECONNECT_REQUIRED",
210+
"ATTENTION",
211+
];
205212

206213
/**
207214
* Set the Google connection state via Redux userMetadata slice.
208-
* Dispatches to the store and waits for the sidebar aria-label to update.
209-
* waitForAppReady in beforeEach guarantees the store is ready before this runs.
215+
* Dispatches to the store, waits for Redux to match, then asserts the header
216+
* status region when the UI shows it (reconnect required / needs repair).
210217
*/
211218
export const setGoogleConnectionState = async (
212219
page: Page,
@@ -221,9 +228,37 @@ export const setGoogleConnectionState = async (
221228
});
222229
}, state);
223230

224-
await expect(page.locator(SIDEBAR_STATUS_SELECTOR)).toHaveAttribute(
225-
"aria-label",
226-
CONNECTION_STATE_TO_LABEL[state],
231+
await page.waitForFunction(
232+
(expected) => {
233+
const cs =
234+
window.__COMPASS_E2E_STORE__?.getState()?.userMetadata?.current?.google
235+
?.connectionState;
236+
return cs === expected;
237+
},
238+
state,
239+
{ timeout: 5000 },
240+
);
241+
242+
if (GOOGLE_HEADER_STATUS_VISIBLE_STATES.includes(state)) {
243+
await expect(
244+
page.getByRole("status", { name: CONNECTION_STATE_TO_LABEL[state] }),
245+
).toBeVisible();
246+
}
247+
};
248+
249+
export const expectGoogleConnectionStateInStore = async (
250+
page: Page,
251+
state: GoogleConnectionState,
252+
) => {
253+
await page.waitForFunction(
254+
(expected) => {
255+
const cs =
256+
window.__COMPASS_E2E_STORE__?.getState()?.userMetadata?.current?.google
257+
?.connectionState;
258+
return cs === expected;
259+
},
260+
state,
261+
{ timeout: 5000 },
227262
);
228263
};
229264

eslint.config.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ export default [
111111
"jest/unbound-method": "error",
112112
},
113113
},
114+
{
115+
files: ["e2e/**/*.ts"],
116+
rules: {
117+
// Playwright e2e is not React Testing Library; page.getByRole is correct.
118+
"testing-library/prefer-screen-queries": "off",
119+
"@typescript-eslint/no-unsafe-assignment": "off",
120+
"@typescript-eslint/no-unsafe-member-access": "off",
121+
},
122+
},
114123
// Warn on console.log in packages/web to avoid leaking secure info
115124
{
116125
files: ["packages/web/**/*.{ts,tsx}"],

packages/backend/src/sync/controllers/sync.controller.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,22 @@ describe("SyncController", () => {
4242
const baseDriver = new BaseDriver();
4343
const syncDriver = new SyncControllerDriver(baseDriver);
4444
const importTimeoutMs = 7_000;
45+
type ImportOperation = "INCREMENTAL" | "REPAIR";
4546

4647
interface ImportSummary {
47-
operation: "REPAIR";
48+
operation: ImportOperation;
4849
status: "COMPLETED";
4950
eventsCount: number;
5051
calendarsCount: number;
5152
}
5253

5354
function parseImportResult(
5455
result: ImportGCalEndPayload | undefined,
56+
operation: ImportOperation = "INCREMENTAL",
5557
): ImportSummary {
5658
expect(result).toEqual(
5759
expect.objectContaining({
58-
operation: "REPAIR",
60+
operation,
5961
status: "COMPLETED",
6062
eventsCount: expect.any(Number) as number,
6163
calendarsCount: expect.any(Number) as number,
@@ -607,7 +609,7 @@ describe("SyncController", () => {
607609
const result = (await importEndPromise) as ImportGCalEndPayload;
608610
stream.close();
609611

610-
const parsed = parseImportResult(result);
612+
const parsed = parseImportResult(result, "REPAIR");
611613

612614
expect(parsed).toHaveProperty("eventsCount");
613615
expect(parsed).toHaveProperty("calendarsCount");
@@ -643,7 +645,7 @@ describe("SyncController", () => {
643645
stream.close();
644646

645647
expect(failReason).toEqual({
646-
operation: "REPAIR",
648+
operation: "INCREMENTAL",
647649
status: "IGNORED",
648650
message: `User ${userId} gcal import is in progress or completed, ignoring this request`,
649651
});
@@ -683,7 +685,7 @@ describe("SyncController", () => {
683685
stream.close();
684686

685687
expect(failReason).toEqual({
686-
operation: "REPAIR",
688+
operation: "INCREMENTAL",
687689
status: "IGNORED",
688690
message: `User ${userId} gcal import is in progress or completed, ignoring this request`,
689691
});

0 commit comments

Comments
 (0)