Skip to content

Commit 6340c51

Browse files
fix: missing connectors section (#5387)
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
1 parent 3baae2d commit 6340c51

File tree

7 files changed

+215
-6
lines changed

7 files changed

+215
-6
lines changed

.github/workflows/pr-playwright-tests.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ env:
1717
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
1818
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
1919
GEN_AI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
20+
21+
# for federated slack tests
22+
SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }}
23+
SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }}
24+
2025
MOCK_LLM_RESPONSE: true
2126

2227
jobs:

web/admin2_auth.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"cookies": [
33
{
44
"name": "fastapiusersauth",
5-
"value": "h3qhacpHbE4_09HcOLlVW4lSee48m1UbjYTUiKYwNiw",
5+
"value": "3_1lUBEkRwurVz_5uWN-jimnGhCD9drBvVggguRyCZI",
66
"domain": "localhost",
77
"path": "/",
8-
"expires": 1745624493.119168,
8+
"expires": 1758141723.8561,
99
"httpOnly": true,
1010
"secure": false,
1111
"sameSite": "Lax"

web/playwright.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { defineConfig, devices } from "@playwright/test";
2+
import * as dotenv from "dotenv";
3+
4+
dotenv.config({ path: ".vscode/.env" });
25

36
export default defineConfig({
47
globalSetup: require.resolve("./tests/e2e/global-setup"),

web/src/app/assistants/SidebarWrapper.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { useSidebarShortcut } from "@/lib/browserUtilities";
1515
import { UserSettingsModal } from "@/app/chat/components/modal/UserSettingsModal";
1616
import { usePopup } from "@/components/admin/connectors/Popup";
1717
import { useUser } from "@/components/user/UserProvider";
18+
import { useFederatedOAuthStatus } from "@/lib/hooks/useFederatedOAuthStatus";
1819

1920
interface SidebarWrapperProps<T extends object> {
2021
size?: "sm" | "lg";
@@ -41,7 +42,12 @@ export default function SidebarWrapper<T extends object>({
4142
}, [sidebarVisible]);
4243

4344
const sidebarElementRef = useRef<HTMLDivElement>(null);
44-
const { folders, openedFolders, chatSessions } = useChatContext();
45+
const { folders, chatSessions, ccPairs } = useChatContext();
46+
const {
47+
connectors: federatedConnectors,
48+
refetch: refetchFederatedConnectors,
49+
} = useFederatedOAuthStatus();
50+
4551
const explicitlyUntoggle = () => {
4652
setShowDocSidebar(false);
4753

@@ -114,6 +120,9 @@ export default function SidebarWrapper<T extends object>({
114120
<UserSettingsModal
115121
setPopup={setPopup}
116122
llmProviders={llmProviders}
123+
ccPairs={ccPairs}
124+
federatedConnectors={federatedConnectors}
125+
refetchFederatedConnectors={refetchFederatedConnectors}
117126
onClose={() => setUserSettingsToggled(false)}
118127
defaultModel={user?.preferences?.default_model!}
119128
/>

web/src/app/chat/components/ChatPage.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { usePopup } from "@/components/admin/connectors/Popup";
1919
import { SEARCH_PARAM_NAMES } from "../services/searchParams";
2020
import { useFederatedConnectors, useFilters, useLlmManager } from "@/lib/hooks";
21+
import { useFederatedOAuthStatus } from "@/lib/hooks/useFederatedOAuthStatus";
2122
import { FeedbackType } from "@/app/chat/interfaces";
2223
import { OnyxInitializingLoader } from "@/components/OnyxInitializingLoader";
2324
import { FeedbackModal } from "./modal/FeedbackModal";
@@ -161,6 +162,10 @@ export function ChatPage({
161162

162163
// Also fetch federated connectors for the sources list
163164
const { data: federatedConnectorsData } = useFederatedConnectors();
165+
const {
166+
connectors: federatedConnectorOAuthStatus,
167+
refetch: refetchFederatedConnectors,
168+
} = useFederatedOAuthStatus();
164169

165170
const { user, isAdmin } = useUser();
166171
const existingChatIdRaw = searchParams?.get("chatId");
@@ -796,6 +801,9 @@ export function ChatPage({
796801
updateCurrentLlm={llmManager.updateCurrentLlm}
797802
defaultModel={user?.preferences.default_model!}
798803
llmProviders={llmProviders}
804+
ccPairs={ccPairs}
805+
federatedConnectors={federatedConnectorOAuthStatus}
806+
refetchFederatedConnectors={refetchFederatedConnectors}
799807
onClose={() => {
800808
setUserSettingsToggled(false);
801809
setSettingsToggled(false);

web/src/app/chat/components/modal/UserSettingsModal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ export function UserSettingsModal({
4949
updateCurrentLlm?: (newOverride: LlmDescriptor) => void;
5050
onClose: () => void;
5151
defaultModel: string | null;
52-
ccPairs?: CCPairBasicInfo[];
53-
federatedConnectors?: FederatedConnectorOAuthStatus[];
54-
refetchFederatedConnectors?: () => void;
52+
ccPairs: CCPairBasicInfo[];
53+
federatedConnectors: FederatedConnectorOAuthStatus[];
54+
refetchFederatedConnectors: () => void;
5555
}) {
5656
const {
5757
refreshUser,
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { test, expect } from "@chromatic-com/playwright";
2+
import type { Page } from "@playwright/test";
3+
import { loginAs, loginAsRandomUser } from "../utils/auth";
4+
5+
test.use({ storageState: "admin_auth.json" });
6+
7+
const SLACK_CLIENT_ID = process.env.SLACK_CLIENT_ID;
8+
const SLACK_CLIENT_SECRET = process.env.SLACK_CLIENT_SECRET;
9+
10+
async function createFederatedSlackConnector(page: Page) {
11+
// Navigate to add connector page
12+
await page.goto("http://localhost:3000/admin/add-connector");
13+
await page.waitForLoadState("networkidle");
14+
15+
// Click on Slack connector tile (specifically the one with "Logo Slack" text, not "Slack Bots")
16+
await page.getByRole("link", { name: "Logo Slack" }).first().click();
17+
await page.waitForLoadState("networkidle");
18+
19+
if (!SLACK_CLIENT_ID || !SLACK_CLIENT_SECRET) {
20+
throw new Error("SLACK_CLIENT_ID and SLACK_CLIENT_SECRET must be set");
21+
}
22+
23+
// Fill in the client ID and client secret
24+
await page.getByLabel(/client id/i).fill(SLACK_CLIENT_ID);
25+
await page.getByLabel(/client secret/i).fill(SLACK_CLIENT_SECRET);
26+
27+
// Submit the form to create or update the federated connector
28+
const createOrUpdateButton = await page.getByRole("button", {
29+
name: /create|update/i,
30+
});
31+
await createOrUpdateButton.click();
32+
33+
// Wait for success message or redirect
34+
await page.waitForTimeout(2000);
35+
}
36+
37+
async function navigateToUserSettings(page: Page) {
38+
// Wait for any existing modals to close
39+
await page.waitForTimeout(1000);
40+
41+
// Wait for potential modal backdrop to disappear
42+
await page
43+
.waitForSelector(".fixed.inset-0.bg-neutral-950\\/50", {
44+
state: "detached",
45+
timeout: 5000,
46+
})
47+
.catch(() => {});
48+
49+
// Click on user dropdown/settings button
50+
await page.locator("#onyx-user-dropdown").click();
51+
52+
// Click on settings option
53+
await page.getByText("User Settings").click();
54+
55+
// Wait for settings modal to appear
56+
await expect(page.locator("h2", { hasText: "User Settings" })).toBeVisible();
57+
}
58+
59+
async function openConnectorsTab(page: Page) {
60+
// Click on the Connectors tab in user settings
61+
await page.getByRole("button", { name: "Connectors" }).click();
62+
63+
// Wait for connectors section to be visible
64+
// Allow multiple instances of "Connected Services" to be visible
65+
const connectedServicesLocators = page.getByText("Connected Services");
66+
await expect(connectedServicesLocators.first()).toBeVisible();
67+
}
68+
69+
/**
70+
* Cleanup function to delete the federated Slack connector from the admin panel
71+
* This ensures test isolation by removing any test data created during the test
72+
*/
73+
async function deleteFederatedSlackConnector(page: Page) {
74+
// Navigate to admin indexing status page
75+
await page.goto("http://localhost:3000/admin/indexing/status");
76+
await page.waitForLoadState("networkidle");
77+
78+
// Expand the Slack section first (summary row toggles open on click)
79+
const slackSummaryRow = page.locator("tr").filter({
80+
has: page.locator("text=/^\s*Slack\s*$/i"),
81+
});
82+
if ((await slackSummaryRow.count()) > 0) {
83+
await slackSummaryRow.first().click();
84+
// Wait a moment for rows to render
85+
await page.waitForTimeout(500);
86+
}
87+
88+
// Look for the Slack federated connector row inside the expanded section
89+
// The federated connectors have a "Federated Access" badge
90+
const slackRow = page.locator("tr", { hasText: /federated access/i });
91+
92+
// Check if the connector exists
93+
const rowCount = await slackRow.count();
94+
if (rowCount === 0) {
95+
// No federated Slack connector found, nothing to delete
96+
console.log("No federated Slack connector found to delete");
97+
return;
98+
}
99+
100+
// Click on the row to navigate to the detail page
101+
await slackRow.first().click();
102+
await page.waitForLoadState("networkidle");
103+
104+
// Look for and click the delete button
105+
// Open the Manage menu and click Delete
106+
const manageButton = page.getByRole("button", { name: /manage/i });
107+
await manageButton
108+
.waitFor({ state: "visible", timeout: 5000 })
109+
.catch(() => {});
110+
if (!(await manageButton.isVisible().catch(() => false))) {
111+
console.log("Manage button not visible; skipping delete");
112+
return;
113+
}
114+
await manageButton.click();
115+
// Wait for the dropdown menu to appear and settle (Radix animation)
116+
await page
117+
.getByRole("menu")
118+
.waitFor({ state: "visible", timeout: 3000 })
119+
.catch(() => {});
120+
await page.waitForTimeout(150);
121+
122+
page.once("dialog", (dialog) => dialog.accept());
123+
const deleteMenuItem = page.getByRole("menuitem", { name: /^Delete$/ });
124+
await expect(deleteMenuItem).toBeVisible({ timeout: 5000 });
125+
await deleteMenuItem.click({ force: true });
126+
// Wait for deletion to complete and redirect
127+
await page.waitForURL("**/admin/indexing/status*", { timeout: 15000 });
128+
await page.waitForLoadState("networkidle");
129+
}
130+
131+
test("Federated Slack Connector - Create, OAuth Modal, and User Settings Flow", async ({
132+
page,
133+
}) => {
134+
try {
135+
// Setup: Clear cookies and log in as admin
136+
await page.context().clearCookies();
137+
await loginAs(page, "admin");
138+
139+
// Create a federated Slack connector in admin panel
140+
await createFederatedSlackConnector(page);
141+
142+
// Log in as a random user
143+
await page.context().clearCookies();
144+
await loginAsRandomUser(page);
145+
146+
// Navigate back to main page and verify OAuth modal appears
147+
await page.goto("http://localhost:3000/chat");
148+
await page.waitForLoadState("networkidle");
149+
150+
// Check if the OAuth modal appears
151+
await expect(
152+
page.getByText(/improve answer quality by letting/i)
153+
).toBeVisible({ timeout: 10000 });
154+
await expect(page.getByText(/slack/i)).toBeVisible();
155+
156+
// Decline the OAuth connection
157+
await page.getByRole("button", { name: "Skip for now" }).click();
158+
159+
// Wait for modal to disappear
160+
await expect(
161+
page.getByText(/improve answer quality by letting/i)
162+
).not.toBeVisible();
163+
164+
// Go to user settings and verify the connector appears
165+
await navigateToUserSettings(page);
166+
await openConnectorsTab(page);
167+
168+
// Verify Slack connector appears in the federated connectors section
169+
await expect(page.getByText("Federated Connectors")).toBeVisible();
170+
await expect(page.getByText("Slack")).toBeVisible();
171+
await expect(page.getByText("Not connected")).toBeVisible();
172+
173+
// Verify there's a Connect button available
174+
await expect(
175+
page.locator("button", { hasText: /^Connect$/ })
176+
).toBeVisible();
177+
} finally {
178+
// Cleanup: Delete the federated Slack connector
179+
// Log back in as admin to delete the connector
180+
await page.context().clearCookies();
181+
await loginAs(page, "admin");
182+
await deleteFederatedSlackConnector(page);
183+
}
184+
});

0 commit comments

Comments
 (0)