diff --git a/apps/docs/content/docs/search/confluence.mdx b/apps/docs/content/docs/search/confluence.mdx index 79ea9ede7ba..70276ed311f 100644 --- a/apps/docs/content/docs/search/confluence.mdx +++ b/apps/docs/content/docs/search/confluence.mdx @@ -20,7 +20,7 @@ Admin setup uses your organization's **Settings → Sources** page. Teammates co | **Central account** | One account syncs content, space permissions, page restrictions, and group membership. | Connect their own Confluence account so Sim can match their Atlassian identity to those permissions. | | **Member accounts** | Sim syncs content separately through connected members' accounts. | Connect their own Confluence account to establish which pages they can access. | -Adding a source from **Settings → Sources → Confluence → Sources** opens central account setup. Use this when one account can read the intended spaces and their permissions. For a member source, start **Connect account** from **Integrations** in the main sidebar after an admin adds Confluence. Available methods depend on your organization's enabled features. +Selecting **Add source** on Confluence's integration page opens central account setup. Use this when one account can read the intended spaces and their permissions. For a member source, start **Connect account** from **Integrations** in the main sidebar after an admin allows Confluence. Available methods depend on your organization's enabled features. **Everyone still connects in both methods.** With a central account, teammates supply their identity; they do not configure another central crawl or choose spaces again. @@ -40,7 +40,7 @@ On hosted Sim, personal connections authorize the existing Sim app. Teammates do ### Choose Confluence -Open **Settings → Sources → Add integration** and choose **Confluence**. On its **Sources** tab, select **Add source**. If Confluence is already listed, open it directly. **Setup guide** opens this guide from the source form. +Open **Settings → Sources** and turn on **Confluence** under **Allowed in Sim Search**. Select **Set up** (or **Manage** if sources already exist), then **Add source**. **Setup guide** opens this guide from the source form. @@ -79,9 +79,9 @@ Each teammate completes this last step. A previously authorized account may alre ## Connect member accounts -After an admin adds Confluence, open **Integrations** in the main sidebar and select **Connect account**. If there is no source yet, enter **Confluence Domain** and **Space Keys**, then select **Connect** and authorize your account. For another site or space scope, use **Add source** beside **Add another Confluence source**. +After an admin allows Confluence, open **Integrations** in the main sidebar and select **Connect account**. If there is no source yet, enter **Confluence Domain** and **Space Keys**, then select **Connect** and authorize your account. For another site or space scope, use **Add source** beside **Add another Confluence source**. -An admin can open the resulting source under **Settings → Sources → Confluence → Sources** and use **Settings** to adjust its filters. **Account for browsing** helps populate the space picker; it does not connect that account for Search. Manual space keys work without a browsing account. +An admin can open **Settings → Sources**, select **Manage** beside **Confluence**, and open the source's **Settings** tab to adjust its filters. **Account for browsing** helps populate the space picker; it does not connect that account for Search. Manual space keys work without a browsing account. ## Using a service account @@ -168,7 +168,7 @@ Existing organization members see the configured Confluence source and their own With a central account, Sim applies space access together with the page's restrictions and inherited ancestor restrictions. Group membership is refreshed in the background. With member accounts, each person's provider listing determines the pages available to them. A Sim organization admin does not automatically receive access to every Confluence document. -New content and permission changes require a sync and processing before Search reflects them. Open **Settings → Sources → Confluence → Sources → [source]** to inspect **Documents**, edit **Settings**, or review **Sync history**. If your own account needs authorization again, use **Reconnect** in the main Integrations page. Where available, the provider's **Accounts → Request connections** sends account connection requests; these do not invite people to the Sim organization. +New content and permission changes require a sync and processing before Search reflects them. Open **Settings → Sources**, select **Manage** beside **Confluence**, then open the source to inspect **Documents**, edit **Settings**, or review **Sync history**. If your own account needs authorization again, use **Reconnect** in the main Integrations page. Where available, the provider's **Accounts → Request connections** sends account connection requests; these do not invite people to the Sim organization. ## Troubleshooting diff --git a/apps/docs/content/docs/search/connect-your-account.mdx b/apps/docs/content/docs/search/connect-your-account.mdx index 6d4281632f2..26508b493a7 100644 --- a/apps/docs/content/docs/search/connect-your-account.mdx +++ b/apps/docs/content/docs/search/connect-your-account.mdx @@ -7,7 +7,7 @@ import { Callout } from 'fumadocs-ui/components/callout' import { Step, Steps } from 'fumadocs-ui/components/steps' import { Image } from '@/components/ui/image' -Your admin adds the integration and can configure shared source filters. You connect your own account so Sim can establish what you are allowed to search. Admins connect their own accounts through the same flow. +Your admin allows the integration and can configure shared source filters. You connect your own account so Sim can establish what you are allowed to search. Admins connect their own accounts through the same flow. @@ -21,7 +21,7 @@ Accept your Sim organization invitation or sign in through your organization's S ## Open Integrations -Open **Integrations** in the main sidebar, find the provider or source, and select **Connect account**. Your first connection may ask for a GitHub repository, Confluence domain and space keys, or Jira domain and project keys. Enter the required fields and select **Connect**. If the provider is missing, ask an organization admin to add it under **Settings → Sources**. +Open **Integrations** in the main sidebar, find the provider or source, and select **Connect account**. Your first connection may ask for a GitHub repository, Confluence domain and space keys, or Jira domain and project keys. Enter the required fields and select **Connect**. If the provider is missing, ask an organization admin to turn it on under **Settings → Sources → Allowed in Sim Search**. Use **Add source** beside **Add another [provider] source** when you need another supported repository, site, or project scope. Connecting an existing source does not ask you to configure it again. @@ -72,7 +72,7 @@ An account connection request does not invite you into the Sim organization. You Select **Your accounts** on the main Integrations page to open your personal **Connected accounts** settings. Use **Reconnect** to renew an organization account connection or **Disconnect** to withdraw it. Disconnecting stops that account from being used for organization indexing and workflows, and removes Search access that depends on it. -Admins can select **Manage sources** to open organization setup, then open an integration's **Sources** and **Accounts** tabs. This does not grant the admin access to every document. +Admins can select **Manage sources** to open organization setup, then select **Manage** beside an integration to open its **Sources** and **Accounts** tabs. This does not grant the admin access to every document. ## If you get stuck @@ -85,7 +85,7 @@ Admins can select **Manage sources** to open organization setup, then open an in | **Verify email** | Verify your Sim email, then reopen the connection link. | | Expired or cancelled authorization | Return to the original connection page and start again. If the invitation itself expired, ask the admin for a new request. | | Access revoked | Ask the organization admin to restore your account contribution access before reconnecting. | -| Needs admin attention | Ask your admin to open **Settings → Sources → [integration] → Sources → [source]** to inspect its error. | +| Needs admin attention | Ask your admin to open **Settings → Sources**, select **Manage** beside the integration, and open the source to inspect its error. | Your Sim role does not override document access at the source. Connecting a different account or receiving a Search link does not share someone else's mailbox, private calendar, or restricted documents with you. diff --git a/apps/docs/content/docs/search/github.mdx b/apps/docs/content/docs/search/github.mdx index 91ad9551e78..1bcc37159b6 100644 --- a/apps/docs/content/docs/search/github.mdx +++ b/apps/docs/content/docs/search/github.mdx @@ -102,7 +102,7 @@ In the App's sidebar, choose **Install App**, select the target account, and gra ### Open GitHub setup -As a Sim organization admin, open **Settings → Sources → Add integration** and choose **GitHub**. On its **Sources** tab, select **Add source**. If GitHub is already listed, open it directly. +Open **Settings → Sources** and turn on **GitHub** under **Allowed in Sim Search**. Select **Set up** (or **Manage** if sources already exist), then **Add source**. @@ -131,7 +131,7 @@ You can instead select an existing account under **Sync documents with** to supp Open **Integrations** in the main sidebar, select **Connect account** on the GitHub source, and authorize the App. Teammates repeat this step after joining the Sim organization. For private repositories, both the person's account and the App installation must have access. GitHub also permits App user tokens to read public repositories without an installation; see [GitHub's permission rules](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app). -With **Connected members**, indexing begins after someone connects. A dedicated indexing account can start syncing immediately; each teammate still connects before searching. Open **Settings → Sources → GitHub → Sources → [source]** to inspect **Documents**, edit **Settings**, or review **Sync history**. +With **Connected members**, indexing begins after someone connects. A dedicated indexing account can start syncing immediately; each teammate still connects before searching. Open **Settings → Sources**, select **Manage** beside **GitHub**, then open the source to inspect **Documents**, edit **Settings**, or review **Sync history**. Use **GitHub → Accounts → Request connections** to send provider-specific connection requests. These requests do not grant organization membership. For another repository, add another source; members can also use **Add another GitHub source** in the main Integrations page. diff --git a/apps/docs/content/docs/search/gitlab.mdx b/apps/docs/content/docs/search/gitlab.mdx index c60636bfc24..69789c73e36 100644 --- a/apps/docs/content/docs/search/gitlab.mdx +++ b/apps/docs/content/docs/search/gitlab.mdx @@ -49,7 +49,7 @@ The token must read the project, users, inherited project membership, instance s ### Configure the source in Sim -Open **Settings → Sources → Add integration** and choose **GitLab**. On its source list, select **Add source**. If GitLab is already listed, open it directly. Paste the **Personal Access Token**, enter your **Host** and **Project**, and choose the content to index. +Open **Settings → Sources** and turn on **GitLab** under **Allowed in Sim Search**. Select **Set up** (or **Manage** if sources already exist), then **Add source**. Paste the **Personal Access Token**, enter your **Host** and **Project**, and choose the content to index. | Field | What to enter | |---|---| @@ -74,7 +74,7 @@ Select **Connect & Sync**. Sim validates the token and source policy, then start Invite teammates to the Sim organization using their verified work email. Sim matches that email against the GitLab directory and applies project, feature, and confidential-issue permissions. No GitLab **Connect account** step is required. -Admins open **Settings → Sources → GitLab → [source]** to inspect **Documents**, edit **Settings**, or review **Sync history**. GitLab has no personal **Accounts** tab. Permission and membership changes are picked up during background refreshes. +Admins open **Settings → Sources**, select **Manage** beside **GitLab**, then open the source to inspect **Documents**, edit **Settings**, or review **Sync history**. GitLab has no personal **Accounts** tab. Permission and membership changes are picked up during background refreshes. diff --git a/apps/docs/content/docs/search/gmail.mdx b/apps/docs/content/docs/search/gmail.mdx index e207ab493df..1af4d9a514d 100644 --- a/apps/docs/content/docs/search/gmail.mdx +++ b/apps/docs/content/docs/search/gmail.mdx @@ -18,9 +18,9 @@ These steps require a Sim organization admin. -### Add Gmail +### Set up Gmail -Open **Settings → Sources → Add integration** and choose **Gmail**. On its **Sources** tab, select **Add source**. If Gmail is already listed, open it directly. Gmail uses member accounts; there is no domain-wide or service-account crawl in Search. +Open **Settings → Sources** and turn on **Gmail** under **Allowed in Sim Search**. Select **Set up** (or **Manage** if sources already exist), then **Add source**. Gmail uses member accounts; there is no domain-wide or service-account crawl in Search. @@ -51,7 +51,7 @@ Teammates follow these same steps after joining the organization. Once an admin ## Source options -An admin opens **Settings → Sources → Gmail → Sources → [source] → Settings** to change these options. Filters apply separately to each connected mailbox. **Documents** shows indexed threads and **Sync history** shows recent runs. +An admin opens **Settings → Sources**, selects **Manage** beside **Gmail**, and opens the source's **Settings** tab to change these options. Filters apply separately to each connected mailbox. **Documents** shows indexed threads and **Sync history** shows recent runs. | Option | Behavior | | --- | --- | diff --git a/apps/docs/content/docs/search/google-calendar.mdx b/apps/docs/content/docs/search/google-calendar.mdx index 661e972445e..fecad2266eb 100644 --- a/apps/docs/content/docs/search/google-calendar.mdx +++ b/apps/docs/content/docs/search/google-calendar.mdx @@ -18,9 +18,9 @@ These steps require a Sim organization admin. -### Add Google Calendar +### Set up Google Calendar -Open **Settings → Sources → Add integration** and choose **Google Calendar**. On its **Sources** tab, select **Add source**. If it is already listed, open it directly. Search uses member accounts; an admin or service account cannot connect on behalf of everyone. +Open **Settings → Sources** and turn on **Google Calendar** under **Allowed in Sim Search**. Select **Set up** (or **Manage** if sources already exist), then **Add source**. Search uses member accounts; an admin or service account cannot connect on behalf of everyone. @@ -53,11 +53,11 @@ Keep the default date range for the previous and next 30 days. **More options** 2. In the connection tab, choose the Google account whose verified email matches your Sim email. Grant the requested permissions. 3. Return to Integrations to see indexing status and your searchable document count. -Teammates repeat only these connection steps after joining the organization. They do not need to configure the source. An admin can send connection requests from **Settings → Sources → Google Calendar → Accounts → Request connections**. These requests do not grant organization membership. Connecting Gmail or Google Drive does not replace the Calendar connection. +Teammates repeat only these connection steps after joining the organization. They do not need to configure the source. An admin can send connection requests from **Settings → Sources**: select **Manage** beside **Google Calendar**, then **Accounts → Request connections**. These requests do not grant organization membership. Connecting Gmail or Google Drive does not replace the Calendar connection. ## Source options -An admin opens **Settings → Sources → Google Calendar → Sources → [source] → Settings** to change these options. **Documents** shows indexed events and **Sync history** shows recent runs. +An admin opens **Settings → Sources**, selects **Manage** beside **Google Calendar**, and opens the source's **Settings** tab to change these options. **Documents** shows indexed events and **Sync history** shows recent runs. | Option | Behavior | | --- | --- | diff --git a/apps/docs/content/docs/search/google-drive.mdx b/apps/docs/content/docs/search/google-drive.mdx index f7317aec464..3f68cebe7c7 100644 --- a/apps/docs/content/docs/search/google-drive.mdx +++ b/apps/docs/content/docs/search/google-drive.mdx @@ -7,7 +7,7 @@ import { Callout } from 'fumadocs-ui/components/callout' import { Step, Steps } from 'fumadocs-ui/components/steps' import { Image } from '@/components/ui/image' -Search Google Docs, Sheets, Slides, and supported files in Drive. Members can connect their own accounts after an admin adds Google Drive, or an admin can configure a central service-account crawl. +Search Google Docs, Sheets, Slides, and supported files in Drive. Members can connect their own accounts after an admin allows Google Drive, or an admin can configure a central service-account crawl. Admin setup uses your organization's **Settings → Sources** page. Teammates connect from **Integrations** in the main sidebar. For workspace Search, use **Search → Add source** instead; **Create & Invite** is the workspace equivalent of **Add source**. @@ -27,9 +27,9 @@ Admin setup uses your organization's **Settings → Sources** page. Teammates co -### Add Google Drive +### Allow Google Drive -An organization admin opens **Settings → Sources → Add integration** and chooses **Google Drive**. Adding it allows personal connections; it does not create a source or connect anyone's account. +An organization admin opens **Settings → Sources** and turns on **Google Drive** under **Allowed in Sim Search**. This allows personal connections; it does not create a source or connect anyone's account. @@ -43,7 +43,7 @@ Open **Integrations** in the main sidebar and select **Connect account** beside ### Adjust filters if needed -An admin opens **Settings → Sources → Google Drive → Sources → [source] → Settings**. Leave **Folders** empty to include supported files each member can access, or narrow the source to folders. **Account for browsing** helps select folders; manual **Folder IDs** work without it. Browsing does not connect that account to Search. +An admin opens **Settings → Sources**, selects **Manage** beside **Google Drive**, and opens the source's **Settings** tab. Leave **Folders** empty to include supported files each member can access, or narrow the source to folders. **Account for browsing** helps select folders; manual **Folder IDs** work without it. Browsing does not connect that account to Search. Keep **Sync documents with → Connected members** unless a dedicated account should fetch content. Members still connect to establish access. If the dedicated account is a delegated service account, **Crawl as** selects the Google Workspace user whose files it fetches. Save the source settings when finished. @@ -52,7 +52,7 @@ Keep **Sync documents with → Connected members** unless a dedicated account sh ## Set up a central service account -Open **Settings → Sources → Google Drive → Sources → Add source**. This opens central service-account setup. If Google Drive is not listed yet, add it with **Add integration** first. Teammates do not need a personal Drive connection for this source. +Open **Settings → Sources** and turn on **Google Drive** under **Allowed in Sim Search**. Select **Set up** (or **Manage** if sources already exist), then **Add source**. This opens central service-account setup. Teammates do not need a personal Drive connection for this source. This requires a Google Workspace domain and a Workspace super administrator to authorize domain-wide delegation. Consumer Gmail accounts cannot use this path. @@ -121,7 +121,7 @@ Set **Crawl as** to a Google Workspace administrator who can read groups, member Sim exports Docs and Slides as text and Sheets as XLSX spreadsheets. Supported uploaded files use the knowledge-base document pipeline, including PDF and Office formats. Unsupported files and oversized exports cannot be indexed; Google limits Workspace exports to 10 MB. See [Drive export formats](https://developers.google.com/workspace/drive/api/guides/ref-export-formats) and [download limits](https://developers.google.com/workspace/drive/api/guides/manage-downloads). -Search schedules syncs hourly. Content, deletions, and permissions refresh in the background; results are not a live read from Drive. Open **Settings → Sources → Google Drive → Sources → [source]** to inspect **Documents**, edit **Settings**, or review **Sync history**. **Accounts** on the provider page shows personal account connections where configured; it does not list the central service-account credential. +Search schedules syncs hourly. Content, deletions, and permissions refresh in the background; results are not a live read from Drive. Open **Settings → Sources**, select **Manage** beside **Google Drive**, then open the source to inspect **Documents**, edit **Settings**, or review **Sync history**. **Accounts** on the provider page shows personal account connections where configured; it does not list the central service-account credential. ## Troubleshooting diff --git a/apps/docs/content/docs/search/index.mdx b/apps/docs/content/docs/search/index.mdx index 907ea2876d3..3673675951a 100644 --- a/apps/docs/content/docs/search/index.mdx +++ b/apps/docs/content/docs/search/index.mdx @@ -7,25 +7,25 @@ import { Callout } from 'fumadocs-ui/components/callout' import { Step, Steps } from 'fumadocs-ui/components/steps' import { Image } from '@/components/ui/image' -Search brings your connected sources into one place. An organization admin adds an integration and configures its sources. Teammates then [connect their accounts](/search/connect-your-account) when required. Each person searches with their own access. +Search brings your connected sources into one place. An organization admin allows integrations and configures their sources. Teammates then [connect their accounts](/search/connect-your-account) when required. Each person searches with their own access. ## Add your first source -### Add an integration +### Allow an integration -As an organization admin, open **Settings → Sources → Add integration** and choose a provider. Sim opens that integration's page. If the integration is already listed, open it instead. +As an organization admin, open **Settings → Sources** and turn on the integration under **Allowed in Sim Search**. It stays in the list; no setup page opens automatically. -Adding an integration approves it for Search; it does not connect an account or start indexing. Slack also requires **Set up Slack app** before adding sources. +The switch permits the integration in your organization. It does not connect an account, grant document access, or start indexing. Teammates connect from **Integrations** in the main sidebar; only admins manage these switches. ### Configure it once -On the integration page, select **Add source**. Follow its **Setup guide** to connect the indexing account or choose the folders, repositories, calendars, spaces, or channels to include. **More options** contains secondary filters and **Metadata tags**. +Select **Set up** beside the integration, or **Manage** if it already has sources. On its page, select **Add source**. For Slack, complete **Set up Slack app** first. Follow its **Setup guide** to connect the indexing account or choose the folders, repositories, calendars, spaces, or channels to include. **More options** contains secondary filters and **Metadata tags**. Select **Connect & Sync** for an administrator connection, or **Add source** for member accounts. Creating a source does not invite people or authorize their accounts. @@ -39,7 +39,7 @@ Open **Integrations** in the main sidebar and select **Connect account** if prom -Organization Sources settings with the Add integration action +Organization Sources settings with Allowed in Sim Search switches and separate Set up and Manage actions Source availability depends on the deployment and organization policy. An unavailable source needs operator configuration before setup can continue. @@ -78,7 +78,7 @@ Some member sources offer **More options → Sync documents with**. **Connected Invite people through the organization's **Settings → Members**, or use your organization's [SSO provisioning](/platform/enterprise/sso). Share the organization's **Home** or **Integrations** URL. People need their own Sim account and organization membership; they do not need access to a workspace. Connecting an external account alone does not grant organization membership. -To request a particular account connection, open **Settings → Sources → [integration] → Accounts → Request connections**. This tab is available for providers that support personal connections. If setup is missing, complete the source or Slack app setup shown there first. The list shows organization account contributors and their connection status for that provider. +To request a particular account connection, open **Settings → Sources**, select **Manage** beside the integration (or **Set up** if no sources exist), then **Accounts → Request connections**. This tab is available for providers that support personal connections. If setup is missing, complete the source or Slack app setup shown there first. The list shows organization account contributors and their connection status for that provider. These requests are separate from organization invitations. They let recipients contribute an external account; they do not grant Sim organization membership or Search access. **People** on the Sources settings page shows account contributors across providers. See [Connect your account](/search/connect-your-account) for the teammate walkthrough. @@ -88,7 +88,7 @@ These requests are separate from organization invitations. They let recipients c ## Manage sources and documents -Open **Settings → Sources → [integration]**, then select a source. Providers with personal connections have **Sources** and **Accounts** tabs; providers such as GitLab open directly to the source list. Multiple sources can have different folder, repository, space, or project scopes. +Open **Settings → Sources**, select **Manage** beside the integration, then select a source. Providers with personal connections have **Sources** and **Accounts** tabs; providers such as GitLab open directly to the source list. Multiple sources can have different folder, repository, space, or project scopes. Integration detail with Sources and Accounts tabs and a nested source list @@ -98,7 +98,7 @@ Open **Settings → Sources → [integration]**, then select a source. Providers | **Settings** | Edit the source's scope, filters, and supported connection settings. Save your changes before leaving. | | **Sync history** | Review recent sync runs and errors. | -Use the source header to sync, pause, resume, or remove that source. The back link returns to its integration. **Deactivate** on the integration page makes its content unavailable in Search, Assistant, and MCP while preserving its sources and connected accounts; **Activate** enables it again. +Use the source header to sync, pause, resume, or remove that source. The back link returns to its integration. To disable an entire integration, turn off its **Allowed in Sim Search** switch. If it has sources, confirm **Deactivate**. Its content becomes unavailable in Search, Assistant, and MCP; sources and connected accounts are preserved. Turn the switch back on to allow it again. Gmail source Settings tab with label, date range, and search filters @@ -139,7 +139,7 @@ Search runs background syncs on an hourly schedule. Large sources, provider limi A completed sync means the source was checked; some documents may still be indexing. In the main **Integrations** page, each source row shows how many documents you can search and whether indexing failed for any documents you can access. -As an admin, open **Settings → Sources → [integration] → Sources → [source]**. In **Documents**, select **Failed** from the status dropdown to inspect those files. Use the search field to find a document by name. Select **Retry indexing** beside a file to try again. **Exclude** removes a file from search; select **Excluded** and then **Restore** to include it again. Fix a disconnected account or source configuration before retrying a sync that needs attention. +As an admin, open **Settings → Sources**, select **Manage** beside the integration, then open the source. In **Documents**, select **Failed** from the status dropdown to inspect those files. Use the search field to find a document by name. Select **Retry indexing** beside a file to try again. **Exclude** removes a file from search; select **Excluded** and then **Restore** to include it again. Fix a disconnected account or source configuration before retrying a sync that needs attention. Empty source Documents tab with search and an Included status filter diff --git a/apps/docs/content/docs/search/jira.mdx b/apps/docs/content/docs/search/jira.mdx index c15fa80f975..4c0b5fa4df6 100644 --- a/apps/docs/content/docs/search/jira.mdx +++ b/apps/docs/content/docs/search/jira.mdx @@ -32,7 +32,7 @@ Sim uses its existing Jira OAuth integration. Search uses `read:jira-work` to re ### Choose Jira -Open **Settings → Sources → Add integration** and choose **Jira**. On its **Sources** tab, select **Add source**. If Jira is already listed, open it directly. Jira sources use member accounts. +Open **Settings → Sources** and turn on **Jira** under **Allowed in Sim Search**. Select **Set up** (or **Manage** if sources already exist), then **Add source**. Jira sources use member accounts. @@ -88,7 +88,7 @@ Search manages the sync schedule. Item limits and sync frequency are not setup d Existing organization members see the same source configuration and their own **Connect account**, **Reconnect**, or indexing status. They do not choose projects again. Invite new teammates to the Sim organization through its Members settings or SSO onboarding, then have them open Integrations and connect Jira. A Jira authorization does not grant Sim organization membership. -Sim checks Jira separately using each connected person's account. Issue content and tags become searchable as processing finishes; changes and lost issue access are picked up by later syncs. The main Integrations page reports documents searchable by the current viewer. Admins open **Settings → Sources → Jira → Sources → [source]** for **Documents**, **Settings**, and **Sync history**. +Sim checks Jira separately using each connected person's account. Issue content and tags become searchable as processing finishes; changes and lost issue access are picked up by later syncs. The main Integrations page reports documents searchable by the current viewer. Admins open **Settings → Sources**, select **Manage** beside **Jira**, then open the source for **Documents**, **Settings**, and **Sync history**. Use **Jira → Accounts → Request connections** to send Jira connection requests. These requests do not invite people into the Sim organization. For a different site or project scope, an admin can add another source; members can also use **Add another Jira source** in the main Integrations page. diff --git a/apps/docs/content/docs/search/slack.mdx b/apps/docs/content/docs/search/slack.mdx index 6b9a314dccc..81d8c0cb91f 100644 --- a/apps/docs/content/docs/search/slack.mdx +++ b/apps/docs/content/docs/search/slack.mdx @@ -25,7 +25,7 @@ Skip to **Connect the source** if the organization already has a verified Slack ### Open provider settings -Open **Settings → Sources → Add integration** and choose **Slack**. Select **Set up Slack app** on the provider page. If Slack is already listed, open it directly. Adding the integration alone does not configure the app or start indexing. +Open **Settings → Sources** and turn on **Slack** under **Allowed in Sim Search**. Select **Set up** (or **Manage** if sources already exist), then **Set up Slack app**. Allowing Slack does not configure the app or start indexing. @@ -78,7 +78,7 @@ To send a Slack connection request, open **Slack → Accounts → Request connec You can instead select an existing account under **Sync documents with** to supply message content centrally. Members still connect their own accounts to establish access. The selected account must itself be able to read the selected channels. -Admins open **Settings → Sources → Slack → Sources → [source]** to inspect **Documents**, edit **Settings**, or review **Sync history**. Search reads indexed content, so source changes appear after background syncing. Slack retention and API limits determine how much history is available. +Admins open **Settings → Sources**, select **Manage** beside **Slack**, then open the source to inspect **Documents**, edit **Settings**, or review **Sync history**. Search reads indexed content, so source changes appear after background syncing. Slack retention and API limits determine how much history is available. ## Permissions reference diff --git a/apps/docs/public/static/search/add-source.jpg b/apps/docs/public/static/search/add-source.jpg deleted file mode 100644 index 0fb4997da09..00000000000 Binary files a/apps/docs/public/static/search/add-source.jpg and /dev/null differ diff --git a/apps/docs/public/static/search/integration-settings.jpg b/apps/docs/public/static/search/integration-settings.jpg new file mode 100644 index 00000000000..ab46bb0e984 Binary files /dev/null and b/apps/docs/public/static/search/integration-settings.jpg differ diff --git a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integration-catalog.tsx b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integration-catalog.tsx deleted file mode 100644 index fc04b50c57b..00000000000 --- a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integration-catalog.tsx +++ /dev/null @@ -1,130 +0,0 @@ -'use client' - -import { useState } from 'react' -import { - ChipInput, - ChipModal, - ChipModalBody, - ChipModalError, - ChipModalField, - ChipModalFooter, - ChipModalHeader, -} from '@sim/emcn' -import { Search } from '@sim/emcn/icons' -import type { OrganizationSearchProviderSummary } from '@/lib/api/contracts/knowledge/connectors' -import { getConnectorAccessAvailability, SEARCH_SOURCE_TYPES } from '@/lib/sim-search/connectors' -import { IntegrationTile } from '@/app/workspace/[workspaceId]/integrations/components/integrations-showcase' -import { - SettingsEmptyState, - SettingsQueryErrorState, -} from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' -import { - RESOURCE_LIST_STACK, - SettingsResourceRow, -} from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row' -import { useUpdateSearchIntegration } from '@/hooks/queries/search-integrations' -import { usePermissionConfig } from '@/hooks/use-permission-config' - -interface OrganizationIntegrationCatalogProps { - organizationId: string - providers: OrganizationSearchProviderSummary[] - memberAccessAvailable: boolean - mirroredAccessAvailable: boolean - onClose: () => void - onAdded: (connectorType: string) => void -} - -export function OrganizationIntegrationCatalog({ - organizationId, - providers, - memberAccessAvailable, - mirroredAccessAvailable, - onClose, - onAdded, -}: OrganizationIntegrationCatalogProps) { - const [search, setSearch] = useState('') - const availability = usePermissionConfig() - const add = useUpdateSearchIntegration() - const existing = new Set(providers.map((provider) => provider.connectorType)) - const visible = SEARCH_SOURCE_TYPES.filter( - ([type, meta]) => - !existing.has(type) && meta.name.toLowerCase().includes(search.trim().toLowerCase()) - ) - return ( - { - if (!open && !add.isPending) onClose() - }} - > - - Add integration - - - - setSearch(event.target.value)} - placeholder='Search integrations...' - autoComplete='off' - /> - - - {availability.integrationAvailabilityError ? ( - void availability.refetchIntegrationAvailability()} - variant='inline' - /> - ) : !availability.isIntegrationAvailabilityReady ? ( - Loading integrations… - ) : visible.length === 0 ? ( - - {search ? 'No matching integrations' : 'All integrations have been added.'} - - ) : ( -
- {visible.map(([type, meta]) => { - const access = getConnectorAccessAvailability( - meta, - availability.integrationAvailability, - { - memberAccessAvailable, - mirroredAccessAvailable, - oauthServiceAvailability: availability.oauthServiceAvailability, - isIntegrationAvailabilityReady: availability.isIntegrationAvailabilityReady, - } - ) - const available = access.admin || access.members - return ( - } - title={meta.name} - description={!available ? 'Unavailable in this deployment' : undefined} - disabled={add.isPending || !available} - onClick={() => - add.mutate( - { organizationId, connectorType: type, approved: true }, - { onSuccess: () => onAdded(type) } - ) - } - clickLabel={`Add ${meta.name}`} - /> - ) - })} -
- )} -
- {add.error?.message} -
- -
- ) -} diff --git a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-settings.tsx b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-settings.tsx index 80ab693734b..4634cb0e4af 100644 --- a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-settings.tsx +++ b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-settings.tsx @@ -25,7 +25,7 @@ export function OrganizationIntegrationsSettings() { return (
-
+
+ {tab === 'providers' && ( + Allowed in Sim Search + )}
{tab === 'providers' && } {tab === 'people' && ( diff --git a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-setup.test.tsx b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-setup.test.tsx index 45559470eae..c38d53d5c93 100644 --- a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-setup.test.tsx +++ b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-setup.test.tsx @@ -1,5 +1,6 @@ /** @vitest-environment jsdom */ import { act } from 'react' +import { toast } from '@sim/emcn' import { NuqsTestingAdapter } from 'nuqs/adapters/testing' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -11,6 +12,10 @@ const mocks = vi.hoisted(() => ({ push: vi.fn(), add: vi.fn(), refetch: vi.fn(), + availability: vi.fn(), + reset: vi.fn(), + pending: false, + mutationError: null as Error | null, })) vi.mock('next/navigation', () => ({ useRouter: () => ({ push: mocks.push }), @@ -25,17 +30,15 @@ vi.mock('@/app/o/[organizationId]/providers/organization-provider', () => ({ })) vi.mock('@/hooks/queries/kb/connectors', () => ({ useOrganizationSearchOverview: mocks.overview })) vi.mock('@/hooks/queries/search-integrations', () => ({ - useUpdateSearchIntegration: () => ({ mutate: mocks.add, isPending: false }), + useUpdateSearchIntegration: () => ({ + mutate: mocks.add, + isPending: mocks.pending, + reset: mocks.reset, + error: mocks.mutationError, + }), })) vi.mock('@/hooks/use-permission-config', () => ({ - usePermissionConfig: () => ({ - integrationAvailability: new Map(), - oauthServiceAvailability: new Map([ - ['confluence', true], - ['google-drive', true], - ]), - isIntegrationAvailabilityReady: true, - }), + usePermissionConfig: mocks.availability, })) vi.mock('@/app/workspace/[workspaceId]/search/components/search-source-setup', () => ({ SearchSourceSetup: () => null, @@ -55,13 +58,33 @@ const providers: OrganizationSearchProviderSummary[] = [ status: 'waiting_for_connections', isSyncing: false, }, - { connectorType: 'google_drive', approved: false, sourceCount: 2, status: 'paused' }, + { + connectorType: 'google_drive', + approved: false, + sourceCount: 2, + status: 'paused', + isSyncing: false, + }, ] let root: Root let container: HTMLDivElement beforeEach(() => { vi.clearAllMocks() mocks.admin = true + mocks.pending = false + mocks.mutationError = null + mocks.add.mockReset() + vi.spyOn(toast, 'error').mockReturnValue('error-toast') + mocks.availability.mockReturnValue({ + integrationAvailability: new Map(), + oauthServiceAvailability: new Map([ + ['confluence', true], + ['google-drive', true], + ['google-email', true], + ]), + isIntegrationAvailabilityReady: true, + refetchIntegrationAvailability: mocks.refetch, + }) mocks.overview.mockReturnValue({ data: { providers }, isPending: false, refetch: mocks.refetch }) vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) vi.stubGlobal( @@ -80,6 +103,7 @@ afterEach(async () => { await act(async () => root.unmount()) container.remove() vi.unstubAllGlobals() + vi.restoreAllMocks() }) async function render(searchParams = '') { await act(async () => @@ -103,9 +127,9 @@ async function click(label: string) { } describe('organization integration management entry', () => { - it('lists added integrations with stable detail links including deactivated providers', async () => { + it('shows the stable catalog with switches and separate setup and management links', async () => { await render() - expect(document.querySelector('a[aria-label="Manage Gmail"]')).toHaveAttribute( + expect(document.querySelector('a[aria-label="Set up Gmail"]')).toHaveAttribute( 'href', '/o/org-one/settings/integrations/providers/gmail' ) @@ -113,58 +137,210 @@ describe('organization integration management entry', () => { 'href', '/o/org-one/settings/integrations/providers/google_drive' ) - expect(container.textContent).toContain('Waiting for account connections') - expect(container.textContent).toContain('Deactivated') - expect(container.textContent).not.toContain('Confluence') - expect(document.querySelector('[role="switch"]')).toBeNull() + expect(container.textContent).toContain('Needs setup') + expect(container.textContent).toContain('2 sources') + expect(container.textContent).toContain('Confluence') + expect(container.textContent).not.toContain('Add integration') + expect(document.querySelector('[aria-label="Allow Gmail in Sim Search"]')).toHaveAttribute( + 'aria-checked', + 'true' + ) + expect( + document.querySelector('[aria-label="Allow Google Drive in Sim Search"]') + ).toHaveAttribute('aria-checked', 'false') + expect(document.querySelector('a[aria-label="Set up Confluence"]')).toBeNull() }) - it('shows an intentional empty state and an Add integration header action', async () => { + + it('offers the catalog for a new organization without a modal or writes', async () => { mocks.overview.mockReturnValue({ data: { providers: [] }, isPending: false }) await render() - expect(container.textContent).toContain('Add an integration to get started.') - await click('Add integration') - expect(document.querySelector('[role="dialog"]')).not.toBeNull() + expect(container.textContent).toContain('Confluence') + expect(container.textContent).toContain('Google Drive') + expect(document.querySelector('[role="dialog"]')).toBeNull() + expect(mocks.add).not.toHaveBeenCalled() }) - it('adds a provider once and goes to its management page', async () => { - mocks.add.mockImplementation((_input, options) => options.onSuccess()) + + it('enables in place and shows setup after the server confirms approval', async () => { await render() - await click('Add integration') - expect(document.querySelector('button[aria-label="Add Gmail"]')).toBeNull() - await click('Add Confluence') - expect(mocks.add).toHaveBeenCalledWith( + const row = document.querySelector('[aria-label="Allow Confluence in Sim Search"]') + await click('Allow Confluence in Sim Search') + expect(mocks.add).toHaveBeenCalledExactlyOnceWith( { organizationId: 'org-one', connectorType: 'confluence', approved: true }, expect.any(Object) ) - expect(mocks.push).toHaveBeenCalledWith('/o/org-one/settings/integrations/providers/confluence') + expect(row).toHaveAttribute('aria-checked', 'false') + mocks.overview.mockReturnValue({ + data: { + providers: [ + ...providers, + { + connectorType: 'confluence', + approved: true, + sourceCount: 0, + status: 'needs_setup', + isSyncing: false, + }, + ], + }, + isPending: false, + }) + await render() + expect(document.querySelector('[aria-label="Allow Confluence in Sim Search"]')).toBe(row) + expect(row).toHaveAttribute('aria-checked', 'true') + expect(document.querySelector('a[aria-label="Set up Confluence"]')).not.toBeNull() + expect(mocks.push).not.toHaveBeenCalled() + expect(document.querySelector('[role="dialog"]')).toBeNull() + }) + + it('requires confirmation before disabling existing sources and lets the admin cancel', async () => { + mocks.overview.mockReturnValue({ + data: { providers: [{ ...providers[1], approved: true }] }, + isPending: false, + }) + await render() + await click('Allow Google Drive in Sim Search') + expect(document.querySelector('[role="dialog"]')).toHaveTextContent( + 'Sources and connected accounts are preserved.' + ) + expect(mocks.add).not.toHaveBeenCalled() + await click('Cancel') + expect(document.querySelector('[role="dialog"]')).toBeNull() + expect(mocks.add).not.toHaveBeenCalled() + await click('Allow Google Drive in Sim Search') + mocks.add.mockImplementation((_input, options) => options.onSuccess()) + await click('Deactivate') + expect(mocks.add).toHaveBeenCalledExactlyOnceWith( + { organizationId: 'org-one', connectorType: 'google_drive', approved: false }, + expect.any(Object) + ) + expect(document.querySelector('[role="dialog"]')).toBeNull() + }) + + it('allows disabling an integration with no sources in place', async () => { + await render() + await click('Allow Gmail in Sim Search') + expect(mocks.add).toHaveBeenCalledExactlyOnceWith( + { organizationId: 'org-one', connectorType: 'gmail', approved: false }, + expect.any(Object) + ) + expect(document.querySelector('[role="dialog"]')).toBeNull() + }) + + it('keeps the confirmed state and reports failed approval changes', async () => { + mocks.add.mockImplementation((_input, options) => + options.onError(new Error('Could not update integration')) + ) + await render() + await click('Allow Confluence in Sim Search') + expect(toast.error).toHaveBeenCalledWith('Could not update integration') + expect(document.querySelector('[aria-label="Allow Confluence in Sim Search"]')).toHaveAttribute( + 'aria-checked', + 'false' + ) + expect(document.querySelector('a[aria-label="Set up Confluence"]')).toBeNull() + }) + + it('keeps a failed deactivation in the confirmation dialog for retry', async () => { + mocks.overview.mockReturnValue({ + data: { providers: [{ ...providers[1], approved: true }] }, + isPending: false, + }) + await render() + await click('Allow Google Drive in Sim Search') + await click('Deactivate') + mocks.mutationError = new Error('Could not deactivate integration') + await render() + expect(document.querySelector('[role="dialog"]')).toHaveTextContent( + 'Could not deactivate integration' + ) + expect( + document.querySelector('[aria-label="Allow Google Drive in Sim Search"]') + ).toHaveAttribute('aria-checked', 'true') + }) + + it('prevents duplicate changes while a mutation is pending', async () => { + mocks.pending = true + await render() + await click('Allow Confluence in Sim Search') + expect(mocks.add).not.toHaveBeenCalled() + expect(document.querySelector('[aria-label="Allow Confluence in Sim Search"]')).toBeDisabled() + }) + + it('blocks unavailable setup while keeping existing sources manageable and deactivatable', async () => { + mocks.availability.mockReturnValue({ + integrationAvailability: new Map(), + oauthServiceAvailability: new Map(), + isIntegrationAvailabilityReady: true, + }) + mocks.overview.mockReturnValue({ + data: { providers: [{ ...providers[1], approved: true }] }, + isPending: false, + }) + await render() + expect(document.querySelector('[aria-label="Allow Confluence in Sim Search"]')).toBeDisabled() + expect( + document.querySelector('[aria-label="Allow Google Drive in Sim Search"]') + ).not.toBeDisabled() + expect(document.querySelector('a[aria-label="Manage Google Drive"]')).not.toBeNull() + expect(container.textContent).toContain('Unavailable in this deployment') + await click('Allow Confluence in Sim Search') + expect(mocks.add).not.toHaveBeenCalled() }) - it('does not turn a failed summary into an empty setup state', async () => { + + it('fails closed while availability loads and offers retry if it fails', async () => { + mocks.availability.mockReturnValue({ + integrationAvailability: new Map(), + oauthServiceAvailability: new Map(), + isIntegrationAvailabilityReady: false, + integrationAvailabilityError: new Error('Could not load connection availability'), + refetchIntegrationAvailability: mocks.refetch, + }) + await render() + expect(document.querySelector('[aria-label="Allow Confluence in Sim Search"]')).toBeDisabled() + await click('Try again') + expect(mocks.refetch).toHaveBeenCalledOnce() + }) + + it('does not hide actionable sync failures behind a source count', async () => { + mocks.overview.mockReturnValue({ + data: { providers: [{ ...providers[1], approved: true, status: 'needs_attention' }] }, + isPending: false, + }) + await render() + expect(container.textContent).toContain('Needs attention') + expect(document.querySelector('a[aria-label="Manage Google Drive"]')).not.toBeNull() + }) + + it('does not turn a failed overview into unapproved switches', async () => { mocks.overview.mockReturnValue({ error: new Error('Could not load sources'), isError: true, refetch: mocks.refetch, }) await render() - expect(container.textContent).not.toContain('Add an integration to get started.') - expect(document.querySelector('a[aria-label="Manage Gmail"]')).toBeNull() + expect(document.querySelector('[role="switch"]')).toBeNull() await click('Try again') expect(mocks.refetch).toHaveBeenCalledOnce() }) - it('keeps loading distinct from empty and disables creation', async () => { + + it('keeps loading distinct from unapproved integrations', async () => { mocks.overview.mockReturnValue({ isPending: true }) await render() expect(container.textContent).toContain('Loading sources') - await click('Add integration') - expect(document.querySelector('[role="dialog"]')).toBeNull() + expect(document.querySelector('[role="switch"]')).toBeNull() }) + it('does not expose admin controls or load admin data for members', async () => { mocks.admin = false await render() expect(mocks.overview).toHaveBeenLastCalledWith('org-one', { enabled: false }) expect(container.textContent).toBe('') }) - it('filters added integration names without dropping server summary context', async () => { - await render('?search=gmail') - expect(document.querySelector('a[aria-label="Manage Gmail"]')).not.toBeNull() - expect(document.querySelector('a[aria-label="Manage Google Drive"]')).toBeNull() + + it('filters the whole catalog without changing approvals', async () => { + await render('?search=confluence') + expect(container.textContent).toContain('Confluence') + expect(container.textContent).not.toContain('Google Drive') + expect(mocks.add).not.toHaveBeenCalled() }) }) diff --git a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-setup.tsx b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-setup.tsx index 25664a9c7d8..a918636313c 100644 --- a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-setup.tsx +++ b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-setup.tsx @@ -1,13 +1,11 @@ 'use client' import { useState } from 'react' -import { Plus } from '@sim/emcn/icons' -import { useRouter } from 'next/navigation' +import { ChipConfirmModal, ChipLink, ChipModalError, Switch, toast } from '@sim/emcn' import { SettingsPanel } from '@/components/settings/settings-panel' import { organizationRoutes } from '@/lib/navigation/paths' +import { getConnectorAccessAvailability, SEARCH_SOURCE_TYPES } from '@/lib/sim-search/connectors' import { useOrganizationContext } from '@/app/o/[organizationId]/providers/organization-provider' -import { OrganizationIntegrationCatalog } from '@/app/o/[organizationId]/settings/components/integrations/organization-integration-catalog' -import { organizationSearchStatusLabel } from '@/app/o/[organizationId]/settings/components/integrations/organization-search-status' import { OrganizationSlackAccountSetup } from '@/app/o/[organizationId]/settings/components/integrations/slack-account-setup' import { IntegrationTile } from '@/app/workspace/[workspaceId]/integrations/components/integrations-showcase' import { SearchSourceSetup } from '@/app/workspace/[workspaceId]/search/components/search-source-setup' @@ -20,20 +18,34 @@ import { SettingsResourceRow, } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row' import { useSettingsSearch } from '@/app/workspace/[workspaceId]/settings/components/use-settings-search' -import { CONNECTOR_META_REGISTRY } from '@/connectors/registry' import { useOrganizationSearchOverview } from '@/hooks/queries/kb/connectors' +import { useUpdateSearchIntegration } from '@/hooks/queries/search-integrations' +import { usePermissionConfig } from '@/hooks/use-permission-config' export function OrganizationIntegrationsSetup() { const { organization, viewer, searchAccess } = useOrganizationContext() - const router = useRouter() const [search, setSearch] = useSettingsSearch() - const [catalogOpen, setCatalogOpen] = useState(false) const overview = useOrganizationSearchOverview(organization.id, { enabled: viewer.isAdmin }) - const providers = overview.data?.providers ?? [] - const query = search.trim().toLowerCase() - const visible = providers.filter((provider) => - CONNECTOR_META_REGISTRY[provider.connectorType]?.name.toLowerCase().includes(query) + const availability = usePermissionConfig() + const approval = useUpdateSearchIntegration() + const [deactivating, setDeactivating] = useState(null) + const providers = new Map( + overview.data?.providers.map((provider) => [provider.connectorType, provider]) ) + const query = search.trim().toLowerCase() + const visible = SEARCH_SOURCE_TYPES.filter(([, meta]) => meta.name.toLowerCase().includes(query)) + const deactivatingName = SEARCH_SOURCE_TYPES.find(([type]) => type === deactivating)?.[1].name + const changeApproval = (connectorType: string, approved: boolean) => { + approval.reset() + if (!approved && (providers.get(connectorType)?.sourceCount ?? 0) > 0) { + setDeactivating(connectorType) + return + } + approval.mutate( + { organizationId: organization.id, connectorType, approved }, + { onError: (error) => toast.error(error.message) } + ) + } if (!viewer.isAdmin) return null if (!searchAccess.memberScoped && !searchAccess.sourceMirrored) return ( @@ -44,17 +56,17 @@ export function OrganizationIntegrationsSetup() { return ( setCatalogOpen(true), - }, - ]} + search={{ value: search, onChange: setSearch, placeholder: 'Search integrations...' }} > + {availability.integrationAvailabilityError && ( + void availability.refetchIntegrationAvailability()} + variant='inline' + /> + )}
{overview.isError ? ( Loading sources… ) : visible.length === 0 ? ( - - {query ? 'No matching sources' : 'Add an integration to get started.'} - + No matching integrations ) : ( - visible.map((provider) => { - const meta = CONNECTOR_META_REGISTRY[provider.connectorType] + visible.map(([type, meta]) => { + const provider = providers.get(type) + const approved = provider?.approved === true + const sourceCount = provider?.sourceCount ?? 0 + const access = getConnectorAccessAvailability( + meta, + availability.integrationAvailability, + { + memberAccessAvailable: searchAccess.memberScoped, + mirroredAccessAvailable: searchAccess.sourceMirrored, + oauthServiceAvailability: availability.oauthServiceAvailability, + isIntegrationAvailabilityReady: availability.isIntegrationAvailabilityReady, + } + ) + const available = access.admin || access.members + const hasSources = sourceCount > 0 + let description = hasSources + ? `${sourceCount} ${sourceCount === 1 ? 'source' : 'sources'}` + : approved + ? 'Needs setup' + : undefined + if (approved && provider?.status === 'needs_attention') description = 'Needs attention' + if (!hasSources && availability.isIntegrationAvailabilityReady && !available) + description = 'Unavailable in this deployment' return ( } + icon={} title={meta.name} - description={organizationSearchStatusLabel(provider)} - href={organizationRoutes(organization.id).searchProvider(provider.connectorType)} - clickLabel={`Manage ${meta.name}`} - navigable + description={description} + trailing={ +
+ {(hasSources || (approved && available)) && ( + + {hasSources ? 'Manage' : 'Set up'} + + )} + changeApproval(type, checked)} + /> +
+ } /> ) }) )}
- {catalogOpen && ( - setCatalogOpen(false)} - onAdded={(type) => router.push(organizationRoutes(organization.id).searchProvider(type))} - /> - )} + { + if (!open && !approval.isPending) setDeactivating(null) + }} + title={`Deactivate ${deactivatingName ?? 'integration'}?`} + text='Its content will be unavailable in Search, Assistant, and MCP. Sources and connected accounts are preserved.' + confirm={{ + label: 'Deactivate', + variant: 'destructive', + pending: approval.isPending, + onClick: () => { + if (!deactivating) return + approval.mutate( + { organizationId: organization.id, connectorType: deactivating, approved: false }, + { onSuccess: () => setDeactivating(null) } + ) + }, + }} + > + {approval.error?.message} +