From 9674ad7d1397933180ea2ea1ff10cebc0410f1f4 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Sun, 28 Dec 2025 10:17:48 +0200 Subject: [PATCH 01/12] refactor: remove persistency from zustand-store that doesn't require persistency --- src/enums/storeName.enum.ts | 1 - .../store/dashboardStore.interface.ts | 65 ---------- src/interfaces/store/index.ts | 9 -- src/store/cache/useCacheStore.ts | 10 +- src/store/index.ts | 1 - src/store/useDashboardStore.ts | 120 ------------------ src/store/useOrgConnectionsStore.ts | 107 +++++++--------- 7 files changed, 49 insertions(+), 264 deletions(-) delete mode 100644 src/interfaces/store/dashboardStore.interface.ts delete mode 100644 src/store/useDashboardStore.ts diff --git a/src/enums/storeName.enum.ts b/src/enums/storeName.enum.ts index 8cae79d3c4..4bee1b33e7 100644 --- a/src/enums/storeName.enum.ts +++ b/src/enums/storeName.enum.ts @@ -10,7 +10,6 @@ export enum StoreName { sharedBetweenProjects = "SharedBetweenProjectsStore", tour = "TourStore", drawer = "DrawerStore", - dashboard = "DashboardStore", tablePreferences = "TablePreferencesStore", buildFiles = "BuildFilesStore", } diff --git a/src/interfaces/store/dashboardStore.interface.ts b/src/interfaces/store/dashboardStore.interface.ts deleted file mode 100644 index e303fb70af..0000000000 --- a/src/interfaces/store/dashboardStore.interface.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { DashboardTimeRange } from "@constants"; -import { - DashboardSummary, - EventsByTriggerData, - IntegrationUsageData, - RecentSessionData, - SessionsOverTimeData, - SessionStatusData, -} from "@interfaces/components/dashboardStats.interface"; - -export interface SessionStatistics { - statusDistribution: SessionStatusData[]; - sessionsOverTime: SessionsOverTimeData[]; - recentSessions: RecentSessionData[]; - totalCount: number; - successRate: number; -} - -export interface EventStatistics { - eventsByTrigger: EventsByTriggerData[]; - totalCount: number; - eventsOverTime: Array<{ count: number; date: string }>; -} - -export interface ConnectionStatistics { - integrationUsage: IntegrationUsageData[]; - totalConnections: number; - activeConnections: number; -} - -export interface DashboardState { - timeRange: DashboardTimeRange; - autoRefreshIntervalMs: number; - isAutoRefreshEnabled: boolean; - lastUpdatedAt?: string; - summary?: DashboardSummary; - sessionStatsByScope: Record; - eventStatsByScope: Record; - connectionStats?: ConnectionStatistics; - isLoading: boolean; - error?: string; -} - -export interface DashboardActions { - setTimeRange: (timeRange: DashboardTimeRange) => void; - setAutoRefreshInterval: (ms: number) => void; - toggleAutoRefresh: (enabled: boolean) => void; - setSummary: (summary: DashboardSummary) => void; - setSessionStats: (scopeKey: string, stats: SessionStatistics) => void; - setEventStats: (scopeKey: string, stats: EventStatistics) => void; - setConnectionStats: (stats: ConnectionStatistics) => void; - setLoading: (isLoading: boolean) => void; - setError: (error?: string) => void; - setLastUpdatedAt: (timestamp: string) => void; - clearStats: () => void; - reset: () => void; -} - -export type DashboardStore = DashboardState & DashboardActions; - -export interface ScopeKeyParams { - projectId?: string; - deploymentId?: string; - timeRange: DashboardTimeRange; -} diff --git a/src/interfaces/store/index.ts b/src/interfaces/store/index.ts index 7373805609..48e25f0f07 100644 --- a/src/interfaces/store/index.ts +++ b/src/interfaces/store/index.ts @@ -8,15 +8,6 @@ export type { export type { BuildFilesStore, BuildFilesData } from "@interfaces/store/buildFilesStore.interface"; export type { CacheStore, ProjectValidationLevel } from "@interfaces/store/cacheStore.interface"; export type { ConnectionStore } from "@src/interfaces/store/connectionStore.interface"; -export type { - DashboardStore, - DashboardState, - DashboardActions, - SessionStatistics, - EventStatistics, - ConnectionStatistics, - ScopeKeyParams, -} from "@interfaces/store/dashboardStore.interface"; export type { EventsDrawerStore } from "@interfaces/store/eventsDrawerStore.interface"; export type { FileStore } from "@interfaces/store/fileStore.interface"; export type { LoggerStore, Log } from "@interfaces/store/loggerStore.interface"; diff --git a/src/store/cache/useCacheStore.ts b/src/store/cache/useCacheStore.ts index e2165e6eed..c5592dd065 100644 --- a/src/store/cache/useCacheStore.ts +++ b/src/store/cache/useCacheStore.ts @@ -2,7 +2,6 @@ import { t } from "i18next"; import isEqual from "lodash/isEqual"; import { createSelector } from "reselect"; import { create } from "zustand"; -import { persist } from "zustand/middleware"; import { maxResultsLimitToDisplay, namespaces } from "@constants"; import { DeploymentStateVariant } from "@enums"; @@ -614,12 +613,5 @@ const selectHasActiveDeployments = createSelector( (deployments) => deployments?.some(({ state }) => state === DeploymentStateVariant.active) || false ); -export const useCacheStore = create()( - persist(store, { - name: "cache-storage", - partialize: (state) => ({ - integrations: state.integrations, - }), - }) -); +export const useCacheStore = create()(store); export const useHasActiveDeployments = () => useCacheStore(selectHasActiveDeployments); diff --git a/src/store/index.ts b/src/store/index.ts index a6a5c5c892..f7720bd199 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -5,7 +5,6 @@ export { useDashboardStatisticsStore } from "@src/store/useDashboardStatisticsSt export { useTemplatesStore } from "@src/store/useTemplatesStore"; export { useBuildFilesStore } from "@store/useBuildFilesStore"; export { useConnectionStore } from "@store/useConnectionStore"; -export { useDashboardStore, makeScopeKey } from "@store/useDashboardStore"; export { useEventsDrawerStore } from "@store/useEventsDrawerStore"; export { useOrgConnectionsStore } from "@store/useOrgConnectionsStore"; export { useFileStore } from "@store/useFileStore"; diff --git a/src/store/useDashboardStore.ts b/src/store/useDashboardStore.ts deleted file mode 100644 index e0288d5c15..0000000000 --- a/src/store/useDashboardStore.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { StateCreator, create } from "zustand"; -import { persist } from "zustand/middleware"; -import { immer } from "zustand/middleware/immer"; - -import { autoRefreshIntervalMs, defaultTimeRange, DashboardTimeRange } from "@constants/statusColors.constants"; -import { StoreName } from "@enums"; -import { DashboardSummary } from "@interfaces/components/dashboardStats.interface"; -import { - ConnectionStatistics, - DashboardState, - DashboardStore, - ScopeKeyParams, - SessionStatistics, - EventStatistics, -} from "@interfaces/store/dashboardStore.interface"; - -const defaultState: DashboardState = { - timeRange: defaultTimeRange, - autoRefreshIntervalMs: autoRefreshIntervalMs, - isAutoRefreshEnabled: true, - lastUpdatedAt: undefined, - summary: undefined, - sessionStatsByScope: {}, - eventStatsByScope: {}, - connectionStats: undefined, - isLoading: false, - error: undefined, -}; - -export const makeScopeKey = ({ projectId, deploymentId, timeRange }: ScopeKeyParams): string => { - const parts: string[] = []; - if (projectId) { - parts.push(`project:${projectId}`); - } - if (deploymentId) { - parts.push(`deployment:${deploymentId}`); - } - parts.push(`range:${timeRange}`); - - return parts.join("|"); -}; - -const store: StateCreator = (set) => ({ - ...defaultState, - - setTimeRange: (timeRange: DashboardTimeRange) => { - set((state) => { - state.timeRange = timeRange; - }); - }, - - setAutoRefreshInterval: (ms: number) => { - set((state) => { - state.autoRefreshIntervalMs = ms; - }); - }, - - toggleAutoRefresh: (enabled: boolean) => { - set((state) => { - state.isAutoRefreshEnabled = enabled; - }); - }, - - setSummary: (summary: DashboardSummary) => { - set((state) => { - state.summary = summary; - }); - }, - - setSessionStats: (scopeKey: string, stats: SessionStatistics) => { - set((state) => { - state.sessionStatsByScope[scopeKey] = stats; - }); - }, - - setEventStats: (scopeKey: string, stats: EventStatistics) => { - set((state) => { - state.eventStatsByScope[scopeKey] = stats; - }); - }, - - setConnectionStats: (stats: ConnectionStatistics) => { - set((state) => { - state.connectionStats = stats; - }); - }, - - setLoading: (isLoading: boolean) => { - set((state) => { - state.isLoading = isLoading; - }); - }, - - setError: (error?: string) => { - set((state) => { - state.error = error; - }); - }, - - setLastUpdatedAt: (timestamp: string) => { - set((state) => { - state.lastUpdatedAt = timestamp; - }); - }, - - clearStats: () => { - set((state) => { - state.sessionStatsByScope = {}; - state.eventStatsByScope = {}; - state.connectionStats = undefined; - state.summary = undefined; - }); - }, - - reset: () => { - set(() => defaultState); - }, -}); - -export const useDashboardStore = create(persist(immer(store), { name: StoreName.dashboard })); diff --git a/src/store/useOrgConnectionsStore.ts b/src/store/useOrgConnectionsStore.ts index c965c42336..7271da06b7 100644 --- a/src/store/useOrgConnectionsStore.ts +++ b/src/store/useOrgConnectionsStore.ts @@ -1,5 +1,4 @@ import { create } from "zustand"; -import { persist } from "zustand/middleware"; import { OrgConnectionsState, OrgConnectionsStore } from "@interfaces/store"; import { ConnectionService } from "@services"; @@ -14,72 +13,62 @@ const initialState: OrgConnectionsState = { isDrawerAddMode: false, }; -export const useOrgConnectionsStore = create()( - persist( - (set) => ({ - ...initialState, +export const useOrgConnectionsStore = create()((set) => ({ + ...initialState, - setSelectedOrgConnectionId: (id?: string) => { - set({ selectedOrgConnectionId: id, isDrawerEditMode: !!id }); - }, + setSelectedOrgConnectionId: (id?: string) => { + set({ selectedOrgConnectionId: id, isDrawerEditMode: !!id }); + }, - openDrawer: () => { - set({ isDrawerOpen: true }); - }, + openDrawer: () => { + set({ isDrawerOpen: true }); + }, - closeDrawer: () => { - set({ - isDrawerOpen: false, - selectedOrgConnectionId: undefined, - isDrawerEditMode: false, - isDrawerAddMode: false, - }); - }, + closeDrawer: () => { + set({ + isDrawerOpen: false, + selectedOrgConnectionId: undefined, + isDrawerEditMode: false, + isDrawerAddMode: false, + }); + }, - setDrawerEditMode: (isEditMode: boolean) => { - set({ isDrawerEditMode: isEditMode }); - }, + setDrawerEditMode: (isEditMode: boolean) => { + set({ isDrawerEditMode: isEditMode }); + }, - setDrawerAddMode: (isAddMode: boolean) => { - set({ isDrawerAddMode: isAddMode }); - }, + setDrawerAddMode: (isAddMode: boolean) => { + set({ isDrawerAddMode: isAddMode }); + }, - resetDrawerState: () => { - set({ - isDrawerOpen: false, - selectedOrgConnectionId: undefined, - isDrawerEditMode: false, - isDrawerAddMode: false, - }); - }, + resetDrawerState: () => { + set({ + isDrawerOpen: false, + selectedOrgConnectionId: undefined, + isDrawerEditMode: false, + isDrawerAddMode: false, + }); + }, - fetchOrgConnections: async (orgId: string) => { - set({ isLoading: true, error: undefined }); + fetchOrgConnections: async (orgId: string) => { + set({ isLoading: true, error: undefined }); - const { data: orgConnections, error } = await ConnectionService.listOrgConnectionsByOrgId(orgId); + const { data: orgConnections, error } = await ConnectionService.listOrgConnectionsByOrgId(orgId); - if (error) { - set({ - isLoading: false, - error: typeof error === "string" ? error : (error as Error).message, - }); - return []; - } + if (error) { + set({ + isLoading: false, + error: typeof error === "string" ? error : (error as Error).message, + }); + return []; + } - set({ - orgConnections: orgConnections || [], - isLoading: false, - error: undefined, - }); + set({ + orgConnections: orgConnections || [], + isLoading: false, + error: undefined, + }); - return orgConnections || []; - }, - }), - { - name: "org-connections-storage", - partialize: (state) => ({ - selectedOrgConnectionId: state.selectedOrgConnectionId, - }), - } - ) -); + return orgConnections || []; + }, +})); From 3fa944f6618c49047bb453c06f89018e7dbbbbf8 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Sun, 28 Dec 2025 10:29:04 +0200 Subject: [PATCH 02/12] refactor: remove unnecessary Zustand store persistence Remove persist middleware from stores that should fetch fresh data or contain transient state. This prevents stale data issues and improves app reliability. Stores no longer persisted: - useCacheStore (integrations cache) - useOrgConnectionsStore (org connections) - useOrganizationStore (user/org data - fetch fresh on auth) - useProjectStore (project lists - fetch fresh) - useBuildFilesStore (build info can change) Removed unused code: - useDashboardStore (dead code - never imported) - dashboardStore.interface.ts - Unused StoreName enum entries Stores keeping persistence (legitimate UX/preferences): - useLoggerStore (debug log history) - useFileStore (open files per project) - useManualRunStore (run configuration) - useTemplatesStore (template cache) - useTourStore (completed tours) - useSharedBetweenProjectsStore (UI preferences) - useTablePreferencesStore (table settings) - useEventsDrawerStore (drawer entity/section state) --- src/enums/storeName.enum.ts | 6 ------ src/store/useBuildFilesStore.ts | 11 +---------- src/store/useEventsDrawerStore.ts | 7 ++----- src/store/useLoggerStore.ts | 2 +- src/store/useOrganizationStore.ts | 5 ++--- src/store/useProjectStore.ts | 5 ++--- 6 files changed, 8 insertions(+), 28 deletions(-) diff --git a/src/enums/storeName.enum.ts b/src/enums/storeName.enum.ts index 4bee1b33e7..b5440c3cdf 100644 --- a/src/enums/storeName.enum.ts +++ b/src/enums/storeName.enum.ts @@ -1,15 +1,9 @@ export enum StoreName { logger = "logger", - project = "ProjectStore", - organization = "OrganizationStore", - user = "UserStore", files = "FileStore", manualRun = "ManualRunStore", - cache = "CacheStore", templates = "TemplatesStore", sharedBetweenProjects = "SharedBetweenProjectsStore", tour = "TourStore", - drawer = "DrawerStore", tablePreferences = "TablePreferencesStore", - buildFiles = "BuildFilesStore", } diff --git a/src/store/useBuildFilesStore.ts b/src/store/useBuildFilesStore.ts index b400a034de..303cdc0f8f 100644 --- a/src/store/useBuildFilesStore.ts +++ b/src/store/useBuildFilesStore.ts @@ -1,10 +1,8 @@ import { StateCreator } from "zustand"; -import { persist } from "zustand/middleware"; import { immer } from "zustand/middleware/immer"; import { shallow } from "zustand/shallow"; import { createWithEqualityFn as create } from "zustand/traditional"; -import { StoreName } from "@enums"; import { BuildsService } from "@services"; import { BuildFilesStore } from "@src/interfaces/store"; import { convertBuildRuntimesToViewTriggers } from "@src/utilities"; @@ -56,11 +54,4 @@ const store: StateCreator = (set, get) => ({ }, }); -export const useBuildFilesStore = create( - persist(immer(store), { - name: StoreName.buildFiles, - version: 1, - migrate: () => ({}), - }), - shallow -); +export const useBuildFilesStore = create(immer(store), shallow); diff --git a/src/store/useEventsDrawerStore.ts b/src/store/useEventsDrawerStore.ts index c3f288d469..b59950b7ab 100644 --- a/src/store/useEventsDrawerStore.ts +++ b/src/store/useEventsDrawerStore.ts @@ -27,11 +27,8 @@ const store: StateCreator = (set) => ({ })), }); -export const useEventsDrawerStore = create( +export const useEventsDrawerStore = create()( persist(store, { name: "events-drawer-store", - partialize: (state) => ({ - ...state, - }), - }) as any + }) ); diff --git a/src/store/useLoggerStore.ts b/src/store/useLoggerStore.ts index 86650acf7d..c602ee067b 100644 --- a/src/store/useLoggerStore.ts +++ b/src/store/useLoggerStore.ts @@ -3,7 +3,7 @@ import { StateCreator, create } from "zustand"; import { persist } from "zustand/middleware"; import { maxLogs } from "@constants"; -import { StoreName, LoggerLevel } from "@enums"; +import { LoggerLevel, StoreName } from "@enums"; import { LoggerStore } from "@interfaces/store"; import { LogType } from "@src/types/components"; diff --git a/src/store/useOrganizationStore.ts b/src/store/useOrganizationStore.ts index e2819838b3..3fa6a2d6f7 100644 --- a/src/store/useOrganizationStore.ts +++ b/src/store/useOrganizationStore.ts @@ -2,10 +2,9 @@ import dayjs from "dayjs"; import { t } from "i18next"; import { produce } from "immer"; import { StateCreator, create } from "zustand"; -import { persist } from "zustand/middleware"; import { immer } from "zustand/middleware/immer"; -import { MemberRole, MemberStatusType, StoreName, UserStatusType } from "@enums"; +import { MemberRole, MemberStatusType, UserStatusType } from "@enums"; import { AuthService, BillingService, LoggerService, OrganizationsService, UsersService } from "@services"; import { namespaces, cookieRefreshInterval, featureFlags } from "@src/constants"; import { defaultUsage } from "@src/mockups"; @@ -774,4 +773,4 @@ const store: StateCreator = (set, get) => ({ })); }, }); -export const useOrganizationStore = create(persist(immer(store), { name: StoreName.organization })); +export const useOrganizationStore = create(immer(store)); diff --git a/src/store/useProjectStore.ts b/src/store/useProjectStore.ts index 33b0c418ed..08535c0f34 100644 --- a/src/store/useProjectStore.ts +++ b/src/store/useProjectStore.ts @@ -2,10 +2,9 @@ import { t } from "i18next"; import { load } from "js-yaml"; import isEqual from "lodash/isEqual"; import { StateCreator, create } from "zustand"; -import { persist } from "zustand/middleware"; import { immer } from "zustand/middleware/immer"; -import { EventListenerName, StoreName } from "@enums"; +import { EventListenerName } from "@enums"; import { SidebarHrefMenu } from "@enums/components"; import { ProjectStore } from "@interfaces/store"; import { ProjectsService } from "@services"; @@ -237,4 +236,4 @@ const store: StateCreator = (set, get) => ({ isProjectNameTaken: (projectName: string) => get().projectsList.some((project) => project.name === projectName), }); -export const useProjectStore = create(persist(immer(store), { name: StoreName.project })); +export const useProjectStore = create(immer(store)); From 6da62031b36559fd0f3f6e867ff6fd7a6d2cca79 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Sun, 28 Dec 2025 12:48:56 +0200 Subject: [PATCH 03/12] refactor: remove unused properties from sharedBetweenProjectsStore Remove 9 unused properties and their setters that were defined but never used anywhere in the codebase: - isChatbotFullScreen / setIsChatbotFullScreen - fullScreenEditor / setFullScreenEditor - expandedProjectNavigation - projectSplitScreenWidth / setProjectSplitScreenWidth - lastVisitedUrl / setLastVisitedUrl --- .../sharedBetweenProjectsStore.interface.ts | 9 ----- src/store/useSharedBetweenProjectsStore.ts | 34 ------------------- 2 files changed, 43 deletions(-) diff --git a/src/interfaces/store/sharedBetweenProjectsStore.interface.ts b/src/interfaces/store/sharedBetweenProjectsStore.interface.ts index 2636c27fb2..3bc4784840 100644 --- a/src/interfaces/store/sharedBetweenProjectsStore.interface.ts +++ b/src/interfaces/store/sharedBetweenProjectsStore.interface.ts @@ -20,11 +20,6 @@ export interface SharedBetweenProjectsStore { selectionPerProject: { [projectId: string]: EditorSelection; }; - fullScreenEditor: { [projectId: string]: boolean }; - setFullScreenEditor: (projectId: string, value: boolean) => void; - expandedProjectNavigation: { [projectId: string]: boolean }; - projectSplitScreenWidth: { [projectId: string]: number }; - setProjectSplitScreenWidth: (projectId: string, width: number) => void; sessionsTableSplit: { [projectId: string]: number }; setSessionsTableWidth: (projectId: string, width: number) => void; chatbotWidth: { [projectId: string]: number }; @@ -37,8 +32,6 @@ export interface SharedBetweenProjectsStore { setProjectFilesWidth: (projectId: string, width: number) => void; fullScreenDashboard: boolean; setFullScreenDashboard: (value: boolean) => void; - isChatbotFullScreen: { [projectId: string]: boolean }; - setIsChatbotFullScreen: (projectId: string, value: boolean) => void; isProjectFilesVisible: { [projectId: string]: boolean }; setIsProjectFilesVisible: (projectId: string, value: boolean) => void; projectSettingsAccordionState: { [projectId: string]: { [accordionKey: string]: boolean } }; @@ -57,8 +50,6 @@ export interface SharedBetweenProjectsStore { closeDrawer: (projectId: string, drawerName: DrawerName) => void; isDrawerOpen: (projectId: string, drawerName: DrawerName) => boolean | undefined; setDrawerAnimated: (projectId: string, drawerName: DrawerName, hasAnimated: boolean) => void; - lastVisitedUrl: { [projectId: string]: string }; - setLastVisitedUrl: (projectId: string, url: string) => void; lastSeenSession: { [projectId: string]: string }; setLastSeenSession: (projectId: string, sessionId: string) => void; settingsPath: { [projectId: string]: string }; diff --git a/src/store/useSharedBetweenProjectsStore.ts b/src/store/useSharedBetweenProjectsStore.ts index cffc506ae3..a3460133d8 100644 --- a/src/store/useSharedBetweenProjectsStore.ts +++ b/src/store/useSharedBetweenProjectsStore.ts @@ -9,10 +9,7 @@ const defaultState: Omit< SharedBetweenProjectsStore, | "setCursorPosition" | "setSelection" - | "setFullScreenEditor" - | "setProjectSplitScreenWidth" | "setFullScreenDashboard" - | "setIsChatbotFullScreen" | "setChatbotWidth" | "setProjectSettingsWidth" | "setEventsDrawerWidth" @@ -23,40 +20,28 @@ const defaultState: Omit< | "closeDrawer" | "isDrawerOpen" | "setDrawerAnimated" - | "setLastVisitedUrl" | "setLastSeenSession" | "setSessionsTableWidth" | "setSettingsPath" > = { cursorPositionPerProject: {}, selectionPerProject: {}, - fullScreenEditor: {}, - expandedProjectNavigation: {}, fullScreenDashboard: false, - projectSplitScreenWidth: {}, sessionsTableSplit: {}, chatbotWidth: {}, projectSettingsWidth: {}, eventsDrawerWidth: {}, projectFilesWidth: {}, - isChatbotFullScreen: {}, isProjectFilesVisible: {}, projectSettingsAccordionState: {}, drawers: {}, drawerAnimated: {}, - lastVisitedUrl: {}, lastSeenSession: {}, settingsPath: {}, }; const store: StateCreator = (set) => ({ ...defaultState, - setIsChatbotFullScreen: (projectId: string, value: boolean) => - set((state) => { - state.isChatbotFullScreen[projectId] = value; - return state; - }), - setCursorPosition: (projectId: string, fileName: string, cursorPosition: EditorSelection) => set((state) => { state.cursorPositionPerProject[projectId] = { @@ -77,19 +62,6 @@ const store: StateCreator = (set) => ({ return state; }), - setFullScreenEditor: (projectId, value) => - set((state) => { - state.fullScreenEditor[projectId] = value; - - return state; - }), - - setProjectSplitScreenWidth: (projectId: string, width: number) => - set((state) => { - state.projectSplitScreenWidth[projectId] = width; - return state; - }), - setSessionsTableWidth: (projectId: string, width: number) => set((state) => { state.sessionsTableSplit[projectId] = width; @@ -192,12 +164,6 @@ const store: StateCreator = (set) => ({ return state; }), - setLastVisitedUrl: (projectId: string, url: string) => - set((state) => { - state.lastVisitedUrl[projectId] = url; - return state; - }), - setLastSeenSession: (projectId: string, sessionId: string) => set((state) => { state.lastSeenSession[projectId] = sessionId; From a683992200d6e6ab5d5c012840de68b6476e22f7 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Mon, 29 Dec 2025 19:41:38 +0200 Subject: [PATCH 04/12] test: fix org-connections tests --- e2e/pages/orgConnections.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/pages/orgConnections.ts b/e2e/pages/orgConnections.ts index 2db5ede3ee..273ddc40d2 100644 --- a/e2e/pages/orgConnections.ts +++ b/e2e/pages/orgConnections.ts @@ -17,7 +17,7 @@ export class OrgConnectionsPage { async goto() { await this.page.goto("/"); await waitForLoadingOverlayGone(this.page); - await this.page.goto("/connections"); + await this.page.getByRole("link", { name: "Connections" }).click(); await waitForLoadingOverlayGone(this.page); await expect(this.page.getByRole("heading", { name: /Org Connections \(\d+\)/ })).toBeVisible(); } From 9fb8bd69e97a231cd1273b2152106a3468dfcc4a Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Tue, 30 Dec 2025 19:20:40 +0200 Subject: [PATCH 05/12] refactor: remove Twilio Auth Token authentication method and refine middleware authentication logic --- .../twilio/authMethods/authToken.tsx | 137 ------------------ .../integrations/twilio/authMethods/index.ts | 3 +- .../templates/descopeMiddleware.tsx | 16 +- .../formsPerIntegrationsMapping.constants.ts | 8 +- .../lists/connections/options.constants.ts | 5 +- src/hooks/useConnectionForm.ts | 5 +- 6 files changed, 20 insertions(+), 154 deletions(-) delete mode 100644 src/components/organisms/configuration/connections/integrations/twilio/authMethods/authToken.tsx diff --git a/src/components/organisms/configuration/connections/integrations/twilio/authMethods/authToken.tsx b/src/components/organisms/configuration/connections/integrations/twilio/authMethods/authToken.tsx deleted file mode 100644 index c68abaf71d..0000000000 --- a/src/components/organisms/configuration/connections/integrations/twilio/authMethods/authToken.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import { FieldErrors, UseFormRegister, useWatch } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import { infoTwilioLinks } from "@constants/lists/connections"; - -import { Button, ErrorMessage, Input, Link, SecretInput, Spinner } from "@components/atoms"; -import { Accordion } from "@components/molecules"; - -import { ExternalLinkIcon, FloppyDiskIcon } from "@assets/image/icons"; - -export const AuthTokenTwilioForm = ({ - control, - errors, - isLoading, - mode, - patWebhookKey, - register, - setValue, -}: { - control: any; - errors: FieldErrors; - isLoading: boolean; - mode: "create" | "edit"; - patWebhookKey?: string; - register: UseFormRegister<{ [key: string]: any }>; - setValue: (name: string, value: any) => void; -}) => { - const { t } = useTranslation("integrations"); - const [lockState, setLockState] = useState<{ account_sid: boolean; auth_token: boolean }>({ - account_sid: true, - auth_token: true, - }); - const isEditMode = mode === "edit"; - - const accountSid = useWatch({ control, name: "account_sid" }); - const authToken = useWatch({ control, name: "auth_token" }); - - useEffect(() => { - if (patWebhookKey) { - setValue("webhook", patWebhookKey); - } - }, [patWebhookKey, setValue]); - - return ( - <> -
- {isEditMode ? ( - setValue("account_sid", newSidValue)} - handleLockAction={(newLockState: boolean) => - setLockState((prevState) => ({ ...prevState, account_sid: newLockState })) - } - isError={!!errors.account_sid} - isLocked={lockState.account_sid} - isRequired - label={t("twilio.placeholders.sid")} - value={accountSid} - /> - ) : ( - - )} - {errors.account_sid?.message as string} -
-
- {isEditMode ? ( - setValue("auth_token", newTokenValue)} - handleLockAction={(newLockState: boolean) => - setLockState((prevState) => ({ ...prevState, auth_token: newLockState })) - } - isError={!!errors.auth_token} - isLocked={lockState.auth_token} - isRequired - label={t("twilio.placeholders.token")} - value={authToken} - /> - ) : ( - - )} - {errors.auth_token?.message as string} -
- - -
- {infoTwilioLinks.map(({ text, url }, index) => ( - - {text} - - - ))} -
-
- - - - ); -}; diff --git a/src/components/organisms/configuration/connections/integrations/twilio/authMethods/index.ts b/src/components/organisms/configuration/connections/integrations/twilio/authMethods/index.ts index 0703ec7cbe..5f512d96fd 100644 --- a/src/components/organisms/configuration/connections/integrations/twilio/authMethods/index.ts +++ b/src/components/organisms/configuration/connections/integrations/twilio/authMethods/index.ts @@ -1,2 +1 @@ -export { ApiKeyTwilioForm } from "@components/organisms/configuration/connections/integrations/twilio/authMethods/apiKey"; -export { AuthTokenTwilioForm } from "@components/organisms/configuration/connections/integrations/twilio/authMethods/authToken"; +export { ApiTokenTwilioForm } from "@components/organisms/configuration/connections/integrations/twilio/authMethods/apiToken"; diff --git a/src/components/templates/descopeMiddleware.tsx b/src/components/templates/descopeMiddleware.tsx index 38b03a86c6..aadf941806 100644 --- a/src/components/templates/descopeMiddleware.tsx +++ b/src/components/templates/descopeMiddleware.tsx @@ -26,6 +26,7 @@ const routes = [ { path: "/intro" }, { path: "/projects/*" }, { path: "/settings/*" }, + { path: "/connections/*" }, { path: "/events/*" }, { path: "/template/*" }, { path: "/chat" }, @@ -260,8 +261,17 @@ export const DescopeMiddleware = ({ children }: { children: ReactNode }) => { } }, [trackUserLogin, setTrackUserLoginFunction]); + const matches = matchRoutes(routes, location); const isLoggedIn = user && Cookies.get(systemCookies.isLoggedIn); - if ((playwrightTestsAuthBearer || apiToken || isLoggedIn) && !isLoggingIn) { + const shouldShowApp = (playwrightTestsAuthBearer || apiToken || isLoggedIn) && !isLoggingIn; + + useEffect(() => { + if (!shouldShowApp && !matches && location.pathname !== "/404") { + navigate("/404"); + } + }, [matches, navigate, shouldShowApp, location.pathname]); + + if (shouldShowApp) { const isCliLogin = handleCliLoginRedirect(); if (isCliLogin) { return ; @@ -273,9 +283,7 @@ export const DescopeMiddleware = ({ children }: { children: ReactNode }) => { return ; } - const matches = matchRoutes(routes, location); - if (!matches) { - navigate("/404"); + if (!matches && !shouldShowApp) { return null; } diff --git a/src/constants/connections/formsPerIntegrationsMapping.constants.ts b/src/constants/connections/formsPerIntegrationsMapping.constants.ts index c87a9f7f81..22171e782e 100644 --- a/src/constants/connections/formsPerIntegrationsMapping.constants.ts +++ b/src/constants/connections/formsPerIntegrationsMapping.constants.ts @@ -49,10 +49,7 @@ import { SocketForm, SlackOauthPrivateForm, } from "@components/organisms/configuration/connections/integrations/slack/authMethods"; -import { - ApiKeyTwilioForm, - AuthTokenTwilioForm, -} from "@components/organisms/configuration/connections/integrations/twilio/authMethods"; +import { ApiTokenTwilioForm } from "@components/organisms/configuration/connections/integrations/twilio/authMethods"; import { ZoomOauthForm, ZoomOauthPrivateForm, @@ -75,8 +72,7 @@ export const formsPerIntegrationsMapping: Partial< [ConnectionAuthType.OauthPrivate]: SlackOauthPrivateForm, }, [Integrations.twilio]: { - [ConnectionAuthType.ApiKey]: ApiKeyTwilioForm, - [ConnectionAuthType.AuthToken]: AuthTokenTwilioForm, + [ConnectionAuthType.ApiToken]: ApiTokenTwilioForm, }, [Integrations.gmail]: { [ConnectionAuthType.Oauth]: OauthGoogleForm, diff --git a/src/constants/lists/connections/options.constants.ts b/src/constants/lists/connections/options.constants.ts index 5d7380dc2f..684112750d 100644 --- a/src/constants/lists/connections/options.constants.ts +++ b/src/constants/lists/connections/options.constants.ts @@ -238,10 +238,7 @@ export const selectIntegrationHttp: SelectOption[] = [ { label: "Bearer", value: ConnectionAuthType.Bearer, disabled: true }, ]; -export const selectIntegrationTwilio: SelectOption[] = [ - { label: "Auth Token", value: ConnectionAuthType.AuthToken }, - { label: "API Key", value: ConnectionAuthType.ApiKey }, -]; +export const selectIntegrationTwilio: SelectOption[] = [{ label: "API Token", value: ConnectionAuthType.ApiToken }]; export const selectIntegrationJira: SelectOption[] = [ { label: "OAuth 2.0 App", value: ConnectionAuthType.Oauth }, diff --git a/src/hooks/useConnectionForm.ts b/src/hooks/useConnectionForm.ts index 54b861c28b..31b1d928dc 100644 --- a/src/hooks/useConnectionForm.ts +++ b/src/hooks/useConnectionForm.ts @@ -116,7 +116,6 @@ export const useConnectionForm = ( } const connectionAuthType = vars?.find((variable) => variable.name === "auth_type"); - if (connectionAuthType) { setConnectionType(connectionAuthType.value as ConnectionAuthType); } else if (authOptions && authOptions.length > 0 && integrationName) { @@ -124,6 +123,10 @@ export const useConnectionForm = ( const defaultOption = getDefaultAuthType(authOptions, integrationName as keyof typeof Integrations); setConnectionType(defaultOption.value as ConnectionAuthType); } catch { + LoggerService.warn( + namespaces.hooks.connectionForm, + `getConnectionAuthType: failed to set default auth type` + ); // If getDefaultAuthType fails (e.g., no valid options), leave connectionType unset // This allows the form to maintain its current state } From 5dde0de6d5c488db0847bc3d768fa362527406e8 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Tue, 30 Dec 2025 19:22:22 +0200 Subject: [PATCH 06/12] refactor: rename `twilioApiKeyIntegrationSchema` to `twilioApiTokenIntegrationSchema` and update its auth type to `ApiToken` --- .../twilio/authMethods/{apiKey.tsx => apiToken.tsx} | 2 +- src/validations/connection.schema.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) rename src/components/organisms/configuration/connections/integrations/twilio/authMethods/{apiKey.tsx => apiToken.tsx} (99%) diff --git a/src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiKey.tsx b/src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiToken.tsx similarity index 99% rename from src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiKey.tsx rename to src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiToken.tsx index f1e764a6e3..c82fe79e25 100644 --- a/src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiKey.tsx +++ b/src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiToken.tsx @@ -10,7 +10,7 @@ import { Accordion } from "@components/molecules"; import { ExternalLinkIcon, FloppyDiskIcon } from "@assets/image/icons"; -export const ApiKeyTwilioForm = ({ +export const ApiTokenTwilioForm = ({ control, errors, isLoading, diff --git a/src/validations/connection.schema.ts b/src/validations/connection.schema.ts index 26a5179b33..1eac66d571 100644 --- a/src/validations/connection.schema.ts +++ b/src/validations/connection.schema.ts @@ -105,11 +105,12 @@ export const twilioTokenIntegrationSchema = z.object({ auth_token: z.string().min(1, "Auth Token is required"), auth_type: z.literal(ConnectionAuthType.AuthToken).default(ConnectionAuthType.AuthToken), }); -export const twilioApiKeyIntegrationSchema = z.object({ + +export const twilioApiTokenIntegrationSchema = z.object({ account_sid: z.string().min(1, "Account SID is required"), api_key: z.string().min(1, "API Key is required"), api_secret: z.string().min(1, "API Secret is required"), - auth_type: z.literal(ConnectionAuthType.ApiKey).default(ConnectionAuthType.ApiKey), + auth_type: z.literal(ConnectionAuthType.ApiToken).default(ConnectionAuthType.ApiToken), }); export const telegramBotTokenIntegrationSchema = z.object({ From e9374ba70278faa7a31bda7a498eaac79cee4011 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Wed, 31 Dec 2025 00:12:04 +0200 Subject: [PATCH 07/12] refactor: fix leftovers after twilio fix --- .../connections/integrations/twilio/add.tsx | 11 +++-------- .../connections/integrations/twilio/edit.tsx | 5 ++--- src/validations/connection.schema.ts | 6 ------ src/validations/index.ts | 3 +-- 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/components/organisms/configuration/connections/integrations/twilio/add.tsx b/src/components/organisms/configuration/connections/integrations/twilio/add.tsx index e2a355c07a..1d2e661388 100644 --- a/src/components/organisms/configuration/connections/integrations/twilio/add.tsx +++ b/src/components/organisms/configuration/connections/integrations/twilio/add.tsx @@ -10,7 +10,7 @@ import { formsPerIntegrationsMapping } from "@src/constants/connections/formsPer import { Integrations } from "@src/enums/components"; import { useConnectionForm } from "@src/hooks"; import { getDefaultAuthType } from "@src/utilities"; -import { legacyOauthSchema, twilioApiKeyIntegrationSchema, twilioTokenIntegrationSchema } from "@validations"; +import { legacyOauthSchema, twilioApiTokenIntegrationSchema } from "@validations"; import { Select } from "@components/molecules"; @@ -33,13 +33,8 @@ export const TwilioIntegrationAddForm = ({ if (!connectionType?.value) { return; } - if (connectionType.value === ConnectionAuthType.AuthToken) { - setValidationSchema(twilioTokenIntegrationSchema); - - return; - } - if (connectionType.value === ConnectionAuthType.ApiKey) { - setValidationSchema(twilioApiKeyIntegrationSchema); + if (connectionType.value === ConnectionAuthType.ApiToken) { + setValidationSchema(twilioApiTokenIntegrationSchema); return; } diff --git a/src/components/organisms/configuration/connections/integrations/twilio/edit.tsx b/src/components/organisms/configuration/connections/integrations/twilio/edit.tsx index 999cfc4b44..6296992c70 100644 --- a/src/components/organisms/configuration/connections/integrations/twilio/edit.tsx +++ b/src/components/organisms/configuration/connections/integrations/twilio/edit.tsx @@ -3,7 +3,7 @@ import React from "react"; import { selectIntegrationTwilio } from "@constants/lists/connections"; import { ConnectionAuthType } from "@enums"; import { Integrations } from "@src/enums/components"; -import { twilioApiKeyIntegrationSchema, twilioTokenIntegrationSchema } from "@validations"; +import { twilioApiTokenIntegrationSchema } from "@validations"; import { IntegrationEditForm } from "@components/organisms/configuration/connections/integrations"; @@ -11,8 +11,7 @@ export const TwilioIntegrationEditForm = () => ( diff --git a/src/validations/connection.schema.ts b/src/validations/connection.schema.ts index 1eac66d571..8b79e5af01 100644 --- a/src/validations/connection.schema.ts +++ b/src/validations/connection.schema.ts @@ -100,12 +100,6 @@ export const openAiIntegrationSchema = z.object({ auth_type: z.literal(ConnectionAuthType.Key).default(ConnectionAuthType.Key), }); -export const twilioTokenIntegrationSchema = z.object({ - account_sid: z.string().min(1, "Account SID is required"), - auth_token: z.string().min(1, "Auth Token is required"), - auth_type: z.literal(ConnectionAuthType.AuthToken).default(ConnectionAuthType.AuthToken), -}); - export const twilioApiTokenIntegrationSchema = z.object({ account_sid: z.string().min(1, "Account SID is required"), api_key: z.string().min(1, "API Key is required"), diff --git a/src/validations/index.ts b/src/validations/index.ts index 9669b9fa97..55fd23ded1 100644 --- a/src/validations/index.ts +++ b/src/validations/index.ts @@ -6,8 +6,7 @@ export { slackPrivateAuthIntegrationSchema, awsIntegrationSchema, openAiIntegrationSchema, - twilioTokenIntegrationSchema, - twilioApiKeyIntegrationSchema, + twilioApiTokenIntegrationSchema, telegramBotTokenIntegrationSchema, jiraIntegrationSchema, discordIntegrationSchema, From 27ae894207e69c894b53ba0c8d980195effa5bc1 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Wed, 31 Dec 2025 00:32:58 +0200 Subject: [PATCH 08/12] feat: remove unused connection test cases and update test data generation script --- e2e/fixtures/connectionsTestCases.json | 762 ++++++++++++------------- scripts/generateConnectionTestData.ts | 5 +- 2 files changed, 378 insertions(+), 389 deletions(-) diff --git a/e2e/fixtures/connectionsTestCases.json b/e2e/fixtures/connectionsTestCases.json index 1767c1d19c..4afd8d8883 100644 --- a/e2e/fixtures/connectionsTestCases.json +++ b/e2e/fixtures/connectionsTestCases.json @@ -1,386 +1,378 @@ [ - { - "testName": "Linear - OAuth v2 - Default app", - "integration": "linear", - "label": "Linear", - "authType": "oauthDefault", - "authLabel": "OAuth v2 - Default app", - "category": "multi-type" - }, - { - "testName": "Linear - OAuth v2 - Private app", - "integration": "linear", - "label": "Linear", - "authType": "oauthPrivate", - "authLabel": "OAuth v2 - Private app", - "category": "multi-type" - }, - { - "testName": "Linear - API Key", - "integration": "linear", - "label": "Linear", - "authType": "apiKey", - "authLabel": "API Key", - "category": "multi-type" - }, - { - "testName": "Auth0 (no auth types)", - "integration": "auth0", - "label": "Auth0", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Asana (no auth types)", - "integration": "asana", - "label": "Asana", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Anthropic (no auth types)", - "integration": "anthropic", - "label": "Anthropic", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "AWS (no auth types)", - "integration": "aws", - "label": "AWS", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Google Calendar - User (OAuth 2.0)", - "integration": "calendar", - "label": "Google Calendar", - "authType": "oauth", - "authLabel": "User (OAuth 2.0)", - "category": "multi-type" - }, - { - "testName": "Google Calendar - Service Account (JSON Key)", - "integration": "calendar", - "label": "Google Calendar", - "authType": "json", - "authLabel": "Service Account (JSON Key)", - "category": "multi-type" - }, - { - "testName": "OpenAI ChatGPT (no auth types)", - "integration": "chatgpt", - "label": "OpenAI ChatGPT", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Atlassian Confluence - OAuth 2.0 App", - "integration": "confluence", - "label": "Atlassian Confluence", - "authType": "oauth", - "authLabel": "OAuth 2.0 App", - "category": "multi-type" - }, - { - "testName": "Atlassian Confluence - User API Token / PAT", - "integration": "confluence", - "label": "Atlassian Confluence", - "authType": "apiToken", - "authLabel": "User API Token / PAT", - "category": "multi-type" - }, - { - "testName": "Discord (no auth types)", - "integration": "discord", - "label": "Discord", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Google Drive - User (OAuth 2.0)", - "integration": "drive", - "label": "Google Drive", - "authType": "oauth", - "authLabel": "User (OAuth 2.0)", - "category": "multi-type" - }, - { - "testName": "Google Drive - Service Account (JSON Key)", - "integration": "drive", - "label": "Google Drive", - "authType": "json", - "authLabel": "Service Account (JSON Key)", - "category": "multi-type" - }, - { - "testName": "Google Forms - User (OAuth 2.0)", - "integration": "forms", - "label": "Google Forms", - "authType": "oauth", - "authLabel": "User (OAuth 2.0)", - "category": "multi-type" - }, - { - "testName": "Google Forms - Service Account (JSON Key)", - "integration": "forms", - "label": "Google Forms", - "authType": "json", - "authLabel": "Service Account (JSON Key)", - "category": "multi-type" - }, - { - "testName": "GitHub - OAuth v2 - Default app", - "integration": "github", - "label": "GitHub", - "authType": "oauth", - "authLabel": "OAuth v2 - Default app", - "category": "multi-type" - }, - { - "testName": "GitHub - OAuth v2 - Private app", - "integration": "github", - "label": "GitHub", - "authType": "oauthPrivate", - "authLabel": "OAuth v2 - Private app", - "category": "multi-type" - }, - { - "testName": "GitHub - PAT + Webhook", - "integration": "github", - "label": "GitHub", - "authType": "pat", - "authLabel": "PAT + Webhook", - "category": "multi-type" - }, - { - "testName": "Gmail - User (OAuth 2.0)", - "integration": "gmail", - "label": "Gmail", - "authType": "oauth", - "authLabel": "User (OAuth 2.0)", - "category": "multi-type" - }, - { - "testName": "Gmail - Service Account (JSON Key)", - "integration": "gmail", - "label": "Gmail", - "authType": "json", - "authLabel": "Service Account (JSON Key)", - "category": "multi-type" - }, - { - "testName": "YouTube - User (OAuth 2.0)", - "integration": "youtube", - "label": "YouTube", - "authType": "oauth", - "authLabel": "User (OAuth 2.0)", - "category": "multi-type" - }, - { - "testName": "YouTube - Service Account (JSON Key)", - "integration": "youtube", - "label": "YouTube", - "authType": "json", - "authLabel": "Service Account (JSON Key)", - "category": "multi-type" - }, - { - "testName": "Google Gemini (no auth types)", - "integration": "googlegemini", - "label": "Google Gemini", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Atlassian Jira - OAuth 2.0 App", - "integration": "jira", - "label": "Atlassian Jira", - "authType": "oauth", - "authLabel": "OAuth 2.0 App", - "category": "multi-type" - }, - { - "testName": "Atlassian Jira - User API Token / PAT", - "integration": "jira", - "label": "Atlassian Jira", - "authType": "apiToken", - "authLabel": "User API Token / PAT", - "category": "multi-type" - }, - { - "testName": "Google Sheets - User (OAuth 2.0)", - "integration": "sheets", - "label": "Google Sheets", - "authType": "oauth", - "authLabel": "User (OAuth 2.0)", - "category": "multi-type" - }, - { - "testName": "Google Sheets - Service Account (JSON Key)", - "integration": "sheets", - "label": "Google Sheets", - "authType": "json", - "authLabel": "Service Account (JSON Key)", - "category": "multi-type" - }, - { - "testName": "Slack - OAuth v2 - Default app", - "integration": "slack", - "label": "Slack", - "authType": "oauthDefault", - "authLabel": "OAuth v2 - Default app", - "category": "multi-type" - }, - { - "testName": "Slack - OAuth v2 - Private app", - "integration": "slack", - "label": "Slack", - "authType": "oauthPrivate", - "authLabel": "OAuth v2 - Private app", - "category": "multi-type" - }, - { - "testName": "Twilio - Auth Token", - "integration": "twilio", - "label": "Twilio", - "authType": "authToken", - "authLabel": "Auth Token", - "category": "multi-type" - }, - { - "testName": "Twilio - API Key", - "integration": "twilio", - "label": "Twilio", - "authType": "apiKey", - "authLabel": "API Key", - "category": "multi-type" - }, - { - "testName": "Telegram (no auth types)", - "integration": "telegram", - "label": "Telegram", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "HubSpot (no auth types)", - "integration": "hubspot", - "label": "HubSpot", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Zoom - OAuth v2 - Default app", - "integration": "zoom", - "label": "Zoom", - "authType": "oauthDefault", - "authLabel": "OAuth v2 - Default app", - "category": "multi-type" - }, - { - "testName": "Zoom - OAuth v2 - Private app", - "integration": "zoom", - "label": "Zoom", - "authType": "oauthPrivate", - "authLabel": "OAuth v2 - Private app", - "category": "multi-type" - }, - { - "testName": "Zoom - Private Server-to-Server", - "integration": "zoom", - "label": "Zoom", - "authType": "serverToServer", - "authLabel": "Private Server-to-Server", - "category": "multi-type" - }, - { - "testName": "Salesforce - OAuth v2 - Default app", - "integration": "salesforce", - "label": "Salesforce", - "authType": "oauthDefault", - "authLabel": "OAuth v2 - Default app", - "category": "multi-type" - }, - { - "testName": "Salesforce - OAuth v2 - Private app", - "integration": "salesforce", - "label": "Salesforce", - "authType": "oauthPrivate", - "authLabel": "OAuth v2 - Private app", - "category": "multi-type" - }, - { - "testName": "Microsoft Teams - Default user-delegated app", - "integration": "microsoft_teams", - "label": "Microsoft Teams", - "authType": "oauthDefault", - "authLabel": "Default user-delegated app", - "category": "multi-type" - }, - { - "testName": "Microsoft Teams - Private user-delegated app", - "integration": "microsoft_teams", - "label": "Microsoft Teams", - "authType": "oauthPrivate", - "authLabel": "Private user-delegated app", - "category": "multi-type" - }, - { - "testName": "Microsoft Teams - Private daemon application", - "integration": "microsoft_teams", - "label": "Microsoft Teams", - "authType": "daemonApp", - "authLabel": "Private daemon application", - "category": "multi-type" - }, - { - "testName": "Kubernetes (no auth types)", - "integration": "kubernetes", - "label": "Kubernetes", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Reddit (no auth types)", - "integration": "reddit", - "label": "Reddit", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Pipedrive (no auth types)", - "integration": "pipedrive", - "label": "Pipedrive", - "authType": null, - "authLabel": null, - "category": "single-type" - }, - { - "testName": "Notion - OAuth v2 - Default app", - "integration": "notion", - "label": "Notion", - "authType": "oauthDefault", - "authLabel": "OAuth v2 - Default app", - "category": "multi-type" - }, - { - "testName": "Notion - API Key", - "integration": "notion", - "label": "Notion", - "authType": "apiKey", - "authLabel": "API Key", - "category": "multi-type" - } -] + { + "testName": "Linear - OAuth v2 - Default app", + "integration": "linear", + "label": "Linear", + "authType": "oauthDefault", + "authLabel": "OAuth v2 - Default app", + "category": "multi-type" + }, + { + "testName": "Linear - OAuth v2 - Private app", + "integration": "linear", + "label": "Linear", + "authType": "oauthPrivate", + "authLabel": "OAuth v2 - Private app", + "category": "multi-type" + }, + { + "testName": "Linear - API Key", + "integration": "linear", + "label": "Linear", + "authType": "apiKey", + "authLabel": "API Key", + "category": "multi-type" + }, + { + "testName": "Auth0 (no auth types)", + "integration": "auth0", + "label": "Auth0", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Asana (no auth types)", + "integration": "asana", + "label": "Asana", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Anthropic (no auth types)", + "integration": "anthropic", + "label": "Anthropic", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "AWS (no auth types)", + "integration": "aws", + "label": "AWS", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Google Calendar - User (OAuth 2.0)", + "integration": "calendar", + "label": "Google Calendar", + "authType": "oauth", + "authLabel": "User (OAuth 2.0)", + "category": "multi-type" + }, + { + "testName": "Google Calendar - Service Account (JSON Key)", + "integration": "calendar", + "label": "Google Calendar", + "authType": "json", + "authLabel": "Service Account (JSON Key)", + "category": "multi-type" + }, + { + "testName": "OpenAI ChatGPT (no auth types)", + "integration": "chatgpt", + "label": "OpenAI ChatGPT", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Atlassian Confluence - OAuth 2.0 App", + "integration": "confluence", + "label": "Atlassian Confluence", + "authType": "oauth", + "authLabel": "OAuth 2.0 App", + "category": "multi-type" + }, + { + "testName": "Atlassian Confluence - User API Token / PAT", + "integration": "confluence", + "label": "Atlassian Confluence", + "authType": "apiToken", + "authLabel": "User API Token / PAT", + "category": "multi-type" + }, + { + "testName": "Discord (no auth types)", + "integration": "discord", + "label": "Discord", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Google Drive - User (OAuth 2.0)", + "integration": "drive", + "label": "Google Drive", + "authType": "oauth", + "authLabel": "User (OAuth 2.0)", + "category": "multi-type" + }, + { + "testName": "Google Drive - Service Account (JSON Key)", + "integration": "drive", + "label": "Google Drive", + "authType": "json", + "authLabel": "Service Account (JSON Key)", + "category": "multi-type" + }, + { + "testName": "Google Forms - User (OAuth 2.0)", + "integration": "forms", + "label": "Google Forms", + "authType": "oauth", + "authLabel": "User (OAuth 2.0)", + "category": "multi-type" + }, + { + "testName": "Google Forms - Service Account (JSON Key)", + "integration": "forms", + "label": "Google Forms", + "authType": "json", + "authLabel": "Service Account (JSON Key)", + "category": "multi-type" + }, + { + "testName": "GitHub - OAuth v2 - Default app", + "integration": "github", + "label": "GitHub", + "authType": "oauth", + "authLabel": "OAuth v2 - Default app", + "category": "multi-type" + }, + { + "testName": "GitHub - OAuth v2 - Private app", + "integration": "github", + "label": "GitHub", + "authType": "oauthPrivate", + "authLabel": "OAuth v2 - Private app", + "category": "multi-type" + }, + { + "testName": "GitHub - PAT + Webhook", + "integration": "github", + "label": "GitHub", + "authType": "pat", + "authLabel": "PAT + Webhook", + "category": "multi-type" + }, + { + "testName": "Gmail - User (OAuth 2.0)", + "integration": "gmail", + "label": "Gmail", + "authType": "oauth", + "authLabel": "User (OAuth 2.0)", + "category": "multi-type" + }, + { + "testName": "Gmail - Service Account (JSON Key)", + "integration": "gmail", + "label": "Gmail", + "authType": "json", + "authLabel": "Service Account (JSON Key)", + "category": "multi-type" + }, + { + "testName": "YouTube - User (OAuth 2.0)", + "integration": "youtube", + "label": "YouTube", + "authType": "oauth", + "authLabel": "User (OAuth 2.0)", + "category": "multi-type" + }, + { + "testName": "YouTube - Service Account (JSON Key)", + "integration": "youtube", + "label": "YouTube", + "authType": "json", + "authLabel": "Service Account (JSON Key)", + "category": "multi-type" + }, + { + "testName": "Google Gemini (no auth types)", + "integration": "googlegemini", + "label": "Google Gemini", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Atlassian Jira - OAuth 2.0 App", + "integration": "jira", + "label": "Atlassian Jira", + "authType": "oauth", + "authLabel": "OAuth 2.0 App", + "category": "multi-type" + }, + { + "testName": "Atlassian Jira - User API Token / PAT", + "integration": "jira", + "label": "Atlassian Jira", + "authType": "apiToken", + "authLabel": "User API Token / PAT", + "category": "multi-type" + }, + { + "testName": "Google Sheets - User (OAuth 2.0)", + "integration": "sheets", + "label": "Google Sheets", + "authType": "oauth", + "authLabel": "User (OAuth 2.0)", + "category": "multi-type" + }, + { + "testName": "Google Sheets - Service Account (JSON Key)", + "integration": "sheets", + "label": "Google Sheets", + "authType": "json", + "authLabel": "Service Account (JSON Key)", + "category": "multi-type" + }, + { + "testName": "Slack - OAuth v2 - Default app", + "integration": "slack", + "label": "Slack", + "authType": "oauthDefault", + "authLabel": "OAuth v2 - Default app", + "category": "multi-type" + }, + { + "testName": "Slack - OAuth v2 - Private app", + "integration": "slack", + "label": "Slack", + "authType": "oauthPrivate", + "authLabel": "OAuth v2 - Private app", + "category": "multi-type" + }, + { + "testName": "Twilio - API Token", + "integration": "twilio", + "label": "Twilio", + "authType": "apiToken", + "authLabel": "API Token", + "category": "single-type" + }, + { + "testName": "Telegram (no auth types)", + "integration": "telegram", + "label": "Telegram", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "HubSpot (no auth types)", + "integration": "hubspot", + "label": "HubSpot", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Zoom - OAuth v2 - Default app", + "integration": "zoom", + "label": "Zoom", + "authType": "oauthDefault", + "authLabel": "OAuth v2 - Default app", + "category": "multi-type" + }, + { + "testName": "Zoom - OAuth v2 - Private app", + "integration": "zoom", + "label": "Zoom", + "authType": "oauthPrivate", + "authLabel": "OAuth v2 - Private app", + "category": "multi-type" + }, + { + "testName": "Zoom - Private Server-to-Server", + "integration": "zoom", + "label": "Zoom", + "authType": "serverToServer", + "authLabel": "Private Server-to-Server", + "category": "multi-type" + }, + { + "testName": "Salesforce - OAuth v2 - Default app", + "integration": "salesforce", + "label": "Salesforce", + "authType": "oauthDefault", + "authLabel": "OAuth v2 - Default app", + "category": "multi-type" + }, + { + "testName": "Salesforce - OAuth v2 - Private app", + "integration": "salesforce", + "label": "Salesforce", + "authType": "oauthPrivate", + "authLabel": "OAuth v2 - Private app", + "category": "multi-type" + }, + { + "testName": "Microsoft Teams - Default user-delegated app", + "integration": "microsoft_teams", + "label": "Microsoft Teams", + "authType": "oauthDefault", + "authLabel": "Default user-delegated app", + "category": "multi-type" + }, + { + "testName": "Microsoft Teams - Private user-delegated app", + "integration": "microsoft_teams", + "label": "Microsoft Teams", + "authType": "oauthPrivate", + "authLabel": "Private user-delegated app", + "category": "multi-type" + }, + { + "testName": "Microsoft Teams - Private daemon application", + "integration": "microsoft_teams", + "label": "Microsoft Teams", + "authType": "daemonApp", + "authLabel": "Private daemon application", + "category": "multi-type" + }, + { + "testName": "Kubernetes (no auth types)", + "integration": "kubernetes", + "label": "Kubernetes", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Reddit (no auth types)", + "integration": "reddit", + "label": "Reddit", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Pipedrive (no auth types)", + "integration": "pipedrive", + "label": "Pipedrive", + "authType": null, + "authLabel": null, + "category": "single-type" + }, + { + "testName": "Notion - OAuth v2 - Default app", + "integration": "notion", + "label": "Notion", + "authType": "oauthDefault", + "authLabel": "OAuth v2 - Default app", + "category": "multi-type" + }, + { + "testName": "Notion - API Key", + "integration": "notion", + "label": "Notion", + "authType": "apiKey", + "authLabel": "API Key", + "category": "multi-type" + } +] \ No newline at end of file diff --git a/scripts/generateConnectionTestData.ts b/scripts/generateConnectionTestData.ts index 2b7a0e465f..0b81eb3080 100644 --- a/scripts/generateConnectionTestData.ts +++ b/scripts/generateConnectionTestData.ts @@ -144,10 +144,7 @@ const microsoftTeamsIntegrationAuthMethods: SelectOption[] = [ { label: "Private daemon application", value: ConnectionAuthType.DaemonApp }, ]; -const selectIntegrationTwilio: SelectOption[] = [ - { label: "Auth Token", value: ConnectionAuthType.AuthToken }, - { label: "API Key", value: ConnectionAuthType.ApiKey }, -]; +const selectIntegrationTwilio: SelectOption[] = [{ label: "API Token", value: ConnectionAuthType.ApiToken }]; const selectIntegrationJira: SelectOption[] = [ { label: "OAuth 2.0 App", value: ConnectionAuthType.Oauth }, From 4b00840eb441d4d340d8cc82b1b5c9c6c855e5f5 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Wed, 31 Dec 2025 00:49:32 +0200 Subject: [PATCH 09/12] test: fix twilio tests --- e2e/pages/orgConnections.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/e2e/pages/orgConnections.ts b/e2e/pages/orgConnections.ts index 273ddc40d2..c2e4ecddba 100644 --- a/e2e/pages/orgConnections.ts +++ b/e2e/pages/orgConnections.ts @@ -27,17 +27,18 @@ export class OrgConnectionsPage { await this.page.waitForURL("/connections/new"); } - async fillTwilioAccountSidAndAuthToken() { + async fillTwilioAccountSidAndApiTokenAndApiSecret() { await this.page.getByRole("textbox", { name: "Account SID" }).fill("AC1234567890"); - await this.page.getByRole("textbox", { name: "Auth Token" }).fill("1234567890"); + await this.page.getByRole("textbox", { name: "API Token" }).fill("1234567890"); + await this.page.getByRole("textbox", { name: "API Secret" }).fill("1234567899"); } async createTwilioConnection(connectionName: string): Promise { await this.clickAddConnection(); await this.connectionsConfig.fillConnectionName(connectionName); await this.connectionsConfig.selectIntegration(testIntegrationName); - await this.connectionsConfig.selectConnectionType("Auth Token"); - await this.fillTwilioAccountSidAndAuthToken(); + await this.connectionsConfig.selectConnectionType("API Token"); + await this.fillTwilioAccountSidAndApiTokenAndApiSecret(); await this.connectionsConfig.clickSaveConnection(); await this.page.waitForURL(/\/connections\/.*\/edit/); From 92556b9c0f59ba95f62487f4ba3991e77372b30b Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Wed, 31 Dec 2025 01:05:34 +0200 Subject: [PATCH 10/12] test: update Twilio connection filling to use API Token and API Secret instead of Auth Token --- e2e/project/connections/orgConnections.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/project/connections/orgConnections.spec.ts b/e2e/project/connections/orgConnections.spec.ts index 3e4f000aab..ee1cdf10c5 100644 --- a/e2e/project/connections/orgConnections.spec.ts +++ b/e2e/project/connections/orgConnections.spec.ts @@ -24,7 +24,7 @@ test.describe("Org Connections Suite", () => { await orgConnectionsPage.goto(); await orgConnectionsPage.clickAddConnection(); await connectionsConfig.selectIntegration(testIntegrationName); - await orgConnectionsPage.fillTwilioAccountSidAndAuthToken(); + await orgConnectionsPage.fillTwilioAccountSidAndApiTokenAndApiSecret(); await page.getByRole("button", { name: "Save Connection" }).click(); const nameError = page.getByText("Name is required"); From 00dff22844904c12c121e53420132d0d8dd08d4c Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Wed, 31 Dec 2025 02:00:45 +0200 Subject: [PATCH 11/12] fix: fix twilio fields --- .../integrations/twilio/authMethods/apiToken.tsx | 8 ++++---- src/locales/en/integrations/translation.json | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiToken.tsx b/src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiToken.tsx index c82fe79e25..6466015496 100644 --- a/src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiToken.tsx +++ b/src/components/organisms/configuration/connections/integrations/twilio/authMethods/apiToken.tsx @@ -75,7 +75,7 @@ export const ApiTokenTwilioForm = ({ setValue("api_key", newKeyValue)} handleLockAction={(newLockState: boolean) => @@ -84,18 +84,18 @@ export const ApiTokenTwilioForm = ({ isError={!!errors.api_key} isLocked={lockState.api_key} isRequired - label={t("twilio.placeholders.key")} + label={t("twilio.placeholders.token")} value={apiKey} /> ) : ( )} {errors.api_key?.message as string} diff --git a/src/locales/en/integrations/translation.json b/src/locales/en/integrations/translation.json index ff9fbe9d94..803f18493c 100644 --- a/src/locales/en/integrations/translation.json +++ b/src/locales/en/integrations/translation.json @@ -131,8 +131,7 @@ "twilio": { "placeholders": { "sid": "Account SID", - "token": "Auth Token", - "key": "API Key", + "token": "API Token", "secret": "API Secret" }, "information": { @@ -284,4 +283,4 @@ "apiKey": "Internal Integration Secret" } } -} +} \ No newline at end of file From 4c94ca55546464a6944f099b65c14220ea0e25f2 Mon Sep 17 00:00:00 2001 From: Ronen Mars Date: Wed, 31 Dec 2025 02:30:39 +0200 Subject: [PATCH 12/12] test: add `data-testid` to project name span and update E2E tests to use it for verification --- e2e/pages/project.ts | 28 ++++++++++++++----- .../organisms/topbar/project/projectName.tsx | 1 + 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/e2e/pages/project.ts b/e2e/pages/project.ts index a253aee302..32feb3a734 100644 --- a/e2e/pages/project.ts +++ b/e2e/pages/project.ts @@ -10,13 +10,23 @@ export class ProjectPage { this.page = page; } - async deleteProject(projectName: string, withActiveDeployment: boolean = false) { - if (!projectName?.trim()?.length) { + async deleteProject(incomingProjectName: string, withActiveDeployment: boolean = false) { + if (!incomingProjectName?.trim()?.length) { throw new Error("Project name is required to delete a project"); } - if ((await this.page.getByRole("button", { name: "Edit project title" }).textContent()) !== projectName) { - throw new Error("Project name is not the same as the one in the page"); + const currentPageProjectName = await this.page.getByTestId("project-name").textContent(); + + if (currentPageProjectName !== incomingProjectName) { + // eslint-disable-next-line no-console + console.error( + "Project name is not the same as the one in the page", + incomingProjectName, + currentPageProjectName + ); + throw new Error( + `Project name is not the same as the one in the page: ${incomingProjectName} !== ${currentPageProjectName}` + ); } const additionalActionsButton = this.page.locator('button[aria-label="Project additional actions"]'); @@ -39,20 +49,24 @@ export class ProjectPage { this.page.waitForURL("/", { waitUntil: "domcontentloaded" }), ]); } catch { - throw new Error('Neither "/welcome" nor "/" URL was reached after project deletion'); + // eslint-disable-next-line no-console + console.error(`Neither "/welcome" nor "/" URL was reached after project deletion: ${incomingProjectName}`); + throw new Error( + `Neither "/welcome" nor "/" URL was reached after project deletion: ${incomingProjectName}` + ); } const loaders = this.page.locator(".loader-cycle-disks").all(); const loadersArray = await loaders; await Promise.all(loadersArray.map((loader) => loader.waitFor({ state: "detached" }))); - const deletedProjectNameCell = this.page.getByRole("cell", { name: projectName }); + const deletedProjectNameCell = this.page.getByRole("cell", { name: incomingProjectName }); await expect(deletedProjectNameCell).toHaveCount(0); await this.page.locator('button[aria-label="System Log"]').click(); - const deletedProjectLogText = `Project deletion completed successfully, project name: ${projectName}`; + const deletedProjectLogText = `Project deletion completed successfully, project name: ${incomingProjectName}`; const deletedProjectLog = this.page.getByText(deletedProjectLogText); await expect(deletedProjectLog).toBeVisible(); diff --git a/src/components/organisms/topbar/project/projectName.tsx b/src/components/organisms/topbar/project/projectName.tsx index a7da0aa81e..62f8e9d598 100644 --- a/src/components/organisms/topbar/project/projectName.tsx +++ b/src/components/organisms/topbar/project/projectName.tsx @@ -120,6 +120,7 @@ export const ProjectTopbarName = () => { {project?.name}