mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
(Hotfix): Slack app installation flow (#9162)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -5,15 +5,38 @@ description: This guide walks you through installing the OpenHands Slack app.
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- You are a slack workspace admin
|
|
||||||
- Access to OpenHands Cloud
|
- Access to OpenHands Cloud
|
||||||
|
|
||||||
## Installation Steps
|
## Installation Steps
|
||||||
|
|
||||||
1. Log in to [OpenHands Cloud](https://app.all-hands.dev)
|
<AccordionGroup>
|
||||||
2. Click the button below to OpenHands Slack App <a target="_blank" href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,chat:write,users:read,channels:history,groups:history,mpim:history,im:history&user_scope=channels:history,groups:history,im:history,mpim:history"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>
|
<Accordion title="Install Slack App (only for Slack admins/owners)">
|
||||||
3. In the top right corner, select the workspace to install the OpenHands Slack app.
|
|
||||||
4. Review permissions and click allow
|
**This step is for Slack admins/owners**
|
||||||
|
|
||||||
|
1. Make sure you have permissions to install Apps to your workspace.
|
||||||
|
2. Click the button below to install OpenHands Slack App <a target="_blank" href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,chat:write,users:read,channels:history,groups:history,mpim:history,im:history&user_scope=channels:history,groups:history,im:history,mpim:history"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>
|
||||||
|
3. In the top right corner, select the workspace to install the OpenHands Slack app.
|
||||||
|
4. Review permissions and click allow.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="Authorize Slack App (for all Slack workspace members)">
|
||||||
|
|
||||||
|
**Make sure your Slack workspace admin/owner has installed OpenHands Slack App first**
|
||||||
|
|
||||||
|
Every user in the slack workspace (including admins/owners) must link their Cloud OpenHands account to the OpenHands Slack App. To do this
|
||||||
|
1. Visit [integrations settings](https://app.all-hands.dev/settings/integrations) in OpenHands Cloud.
|
||||||
|
2. Click the button "Install Slack App".
|
||||||
|
3. In the top right corner, select the workspace to install the OpenHands Slack app.
|
||||||
|
4. Review permissions and click allow.
|
||||||
|
|
||||||
|
Depending on the workspace settings, you may need approval from your slack admin to authorize the Slack App.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
</AccordionGroup>
|
||||||
|
|
||||||
|
|
||||||
## Working With the Slack App
|
## Working With the Slack App
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const renderRepoConnector = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: () => <div data-testid="git-settings-screen" />,
|
Component: () => <div data-testid="git-settings-screen" />,
|
||||||
path: "/settings/git",
|
path: "/settings/integrations",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ const queryClient = new QueryClient();
|
|||||||
const GitSettingsRouterStub = createRoutesStub([
|
const GitSettingsRouterStub = createRoutesStub([
|
||||||
{
|
{
|
||||||
Component: GitSettingsScreen,
|
Component: GitSettingsScreen,
|
||||||
path: "/settings/github",
|
path: "/settings/integrations",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const renderGitSettingsScreen = () => {
|
const renderGitSettingsScreen = () => {
|
||||||
const { rerender, ...rest } = render(
|
const { rerender, ...rest } = render(
|
||||||
<GitSettingsRouterStub initialEntries={["/settings/github"]} />,
|
<GitSettingsRouterStub initialEntries={["/settings/integrations"]} />,
|
||||||
{
|
{
|
||||||
wrapper: ({ children }) => (
|
wrapper: ({ children }) => (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
@@ -54,7 +54,7 @@ const renderGitSettingsScreen = () => {
|
|||||||
const rerenderGitSettingsScreen = () =>
|
const rerenderGitSettingsScreen = () =>
|
||||||
rerender(
|
rerender(
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<GitSettingsRouterStub initialEntries={["/settings/github"]} />
|
<GitSettingsRouterStub initialEntries={["/settings/integrations"]} />
|
||||||
</QueryClientProvider>,
|
</QueryClientProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const RouterStub = createRoutesStub([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: () => <div data-testid="git-settings-screen" />,
|
Component: () => <div data-testid="git-settings-screen" />,
|
||||||
path: "/settings/git",
|
path: "/settings/integrations",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ vi.mock("react-i18next", async () => {
|
|||||||
useTranslation: () => ({
|
useTranslation: () => ({
|
||||||
t: (key: string) => {
|
t: (key: string) => {
|
||||||
const translations: Record<string, string> = {
|
const translations: Record<string, string> = {
|
||||||
"SETTINGS$NAV_GIT": "Git",
|
"SETTINGS$NAV_INTEGRATIONS": "Integrations",
|
||||||
"SETTINGS$NAV_APPLICATION": "Application",
|
"SETTINGS$NAV_APPLICATION": "Application",
|
||||||
"SETTINGS$NAV_CREDITS": "Credits",
|
"SETTINGS$NAV_CREDITS": "Credits",
|
||||||
"SETTINGS$NAV_API_KEYS": "API Keys",
|
"SETTINGS$NAV_API_KEYS": "API Keys",
|
||||||
@@ -61,7 +61,7 @@ describe("Settings Billing", () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: () => <div data-testid="git-settings-screen" />,
|
Component: () => <div data-testid="git-settings-screen" />,
|
||||||
path: "/settings/git",
|
path: "/settings/integrations",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: () => <div data-testid="user-settings-screen" />,
|
Component: () => <div data-testid="user-settings-screen" />,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ vi.mock("react-i18next", async () => {
|
|||||||
useTranslation: () => ({
|
useTranslation: () => ({
|
||||||
t: (key: string) => {
|
t: (key: string) => {
|
||||||
const translations: Record<string, string> = {
|
const translations: Record<string, string> = {
|
||||||
SETTINGS$NAV_GIT: "Git",
|
SETTINGS$NAV_INTEGRATIONS: "Integrations",
|
||||||
SETTINGS$NAV_APPLICATION: "Application",
|
SETTINGS$NAV_APPLICATION: "Application",
|
||||||
SETTINGS$NAV_CREDITS: "Credits",
|
SETTINGS$NAV_CREDITS: "Credits",
|
||||||
SETTINGS$NAV_API_KEYS: "API Keys",
|
SETTINGS$NAV_API_KEYS: "API Keys",
|
||||||
@@ -49,7 +49,7 @@ describe("Settings Screen", () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: () => <div data-testid="git-settings-screen" />,
|
Component: () => <div data-testid="git-settings-screen" />,
|
||||||
path: "/settings/git",
|
path: "/settings/integrations",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: () => <div data-testid="application-settings-screen" />,
|
Component: () => <div data-testid="application-settings-screen" />,
|
||||||
@@ -79,7 +79,7 @@ describe("Settings Screen", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it("should render the navbar", async () => {
|
it("should render the navbar", async () => {
|
||||||
const sectionsToInclude = ["llm", "git", "application", "secrets"];
|
const sectionsToInclude = ["llm", "integrations", "application", "secrets"];
|
||||||
const sectionsToExclude = ["api keys", "credits"];
|
const sectionsToExclude = ["api keys", "credits"];
|
||||||
const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
|
const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
|
||||||
// @ts-expect-error - only return app mode
|
// @ts-expect-error - only return app mode
|
||||||
@@ -111,7 +111,7 @@ describe("Settings Screen", () => {
|
|||||||
APP_MODE: "saas",
|
APP_MODE: "saas",
|
||||||
});
|
});
|
||||||
const sectionsToInclude = [
|
const sectionsToInclude = [
|
||||||
"git",
|
"integrations",
|
||||||
"application",
|
"application",
|
||||||
"credits",
|
"credits",
|
||||||
"secrets",
|
"secrets",
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ export function ConnectToProviderMessage() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<p>{t("HOME$CONNECT_PROVIDER_MESSAGE")}</p>
|
<p>{t("HOME$CONNECT_PROVIDER_MESSAGE")}</p>
|
||||||
<Link data-testid="navigate-to-settings-button" to="/settings/git">
|
<Link
|
||||||
|
data-testid="navigate-to-settings-button"
|
||||||
|
to="/settings/integrations"
|
||||||
|
>
|
||||||
<BrandButton type="button" variant="primary" isDisabled={isLoading}>
|
<BrandButton type="button" variant="primary" isDisabled={isLoading}>
|
||||||
{!isLoading && t("SETTINGS$TITLE")}
|
{!isLoading && t("SETTINGS$TITLE")}
|
||||||
{isLoading && t("HOME$LOADING")}
|
{isLoading && t("HOME$LOADING")}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
|
import { BrandButton } from "../brand-button";
|
||||||
|
|
||||||
|
export function InstallSlackAppAnchor() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
data-testid="install-slack-app-button"
|
||||||
|
href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,chat:write,users:read,channels:history,groups:history,mpim:history,im:history&user_scope=channels:history,groups:history,im:history,mpim:history"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
className="py-9"
|
||||||
|
>
|
||||||
|
<BrandButton type="button" variant="secondary">
|
||||||
|
{t(I18nKey.SLACK$INSTALL_APP)}
|
||||||
|
</BrandButton>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -80,7 +80,7 @@ export enum I18nKey {
|
|||||||
ANALYTICS$CONFIRM_PREFERENCES = "ANALYTICS$CONFIRM_PREFERENCES",
|
ANALYTICS$CONFIRM_PREFERENCES = "ANALYTICS$CONFIRM_PREFERENCES",
|
||||||
SETTINGS$SAVING = "SETTINGS$SAVING",
|
SETTINGS$SAVING = "SETTINGS$SAVING",
|
||||||
SETTINGS$SAVE_CHANGES = "SETTINGS$SAVE_CHANGES",
|
SETTINGS$SAVE_CHANGES = "SETTINGS$SAVE_CHANGES",
|
||||||
SETTINGS$NAV_GIT = "SETTINGS$NAV_GIT",
|
SETTINGS$NAV_INTEGRATIONS = "SETTINGS$NAV_INTEGRATIONS",
|
||||||
SETTINGS$NAV_APPLICATION = "SETTINGS$NAV_APPLICATION",
|
SETTINGS$NAV_APPLICATION = "SETTINGS$NAV_APPLICATION",
|
||||||
SETTINGS$NAV_CREDITS = "SETTINGS$NAV_CREDITS",
|
SETTINGS$NAV_CREDITS = "SETTINGS$NAV_CREDITS",
|
||||||
SETTINGS$NAV_SECRETS = "SETTINGS$NAV_SECRETS",
|
SETTINGS$NAV_SECRETS = "SETTINGS$NAV_SECRETS",
|
||||||
@@ -174,6 +174,7 @@ export enum I18nKey {
|
|||||||
GITHUB$TOKEN_INVALID = "GITHUB$TOKEN_INVALID",
|
GITHUB$TOKEN_INVALID = "GITHUB$TOKEN_INVALID",
|
||||||
BUTTON$DISCONNECT = "BUTTON$DISCONNECT",
|
BUTTON$DISCONNECT = "BUTTON$DISCONNECT",
|
||||||
GITHUB$CONFIGURE_REPOS = "GITHUB$CONFIGURE_REPOS",
|
GITHUB$CONFIGURE_REPOS = "GITHUB$CONFIGURE_REPOS",
|
||||||
|
SLACK$INSTALL_APP = "SLACK$INSTALL_APP",
|
||||||
COMMON$CLICK_FOR_INSTRUCTIONS = "COMMON$CLICK_FOR_INSTRUCTIONS",
|
COMMON$CLICK_FOR_INSTRUCTIONS = "COMMON$CLICK_FOR_INSTRUCTIONS",
|
||||||
LLM$SELECT_MODEL_PLACEHOLDER = "LLM$SELECT_MODEL_PLACEHOLDER",
|
LLM$SELECT_MODEL_PLACEHOLDER = "LLM$SELECT_MODEL_PLACEHOLDER",
|
||||||
LLM$MODEL = "LLM$MODEL",
|
LLM$MODEL = "LLM$MODEL",
|
||||||
|
|||||||
@@ -1279,21 +1279,21 @@
|
|||||||
"de": "Änderungen speichern",
|
"de": "Änderungen speichern",
|
||||||
"uk": "Зберегти зміни"
|
"uk": "Зберегти зміни"
|
||||||
},
|
},
|
||||||
"SETTINGS$NAV_GIT": {
|
"SETTINGS$NAV_INTEGRATIONS": {
|
||||||
"en": "Git",
|
"en": "Integrations",
|
||||||
"ja": "Git",
|
"ja": "統合",
|
||||||
"zh-CN": "Git",
|
"zh-CN": "集成",
|
||||||
"zh-TW": "Git",
|
"zh-TW": "整合",
|
||||||
"ko-KR": "Git",
|
"ko-KR": "통합",
|
||||||
"no": "Git",
|
"no": "Integrasjoner",
|
||||||
"it": "Git",
|
"it": "Integrazioni",
|
||||||
"pt": "Git",
|
"pt": "Integrações",
|
||||||
"es": "Git",
|
"es": "Integraciones",
|
||||||
"ar": "Git",
|
"ar": "التكامل",
|
||||||
"fr": "Git",
|
"fr": "Intégrations",
|
||||||
"tr": "Git",
|
"tr": "Entegrasyonlar",
|
||||||
"de": "Git",
|
"de": "Integrationen",
|
||||||
"uk": "Git"
|
"uk": "Інтеграції"
|
||||||
},
|
},
|
||||||
"SETTINGS$NAV_APPLICATION": {
|
"SETTINGS$NAV_APPLICATION": {
|
||||||
"en": "Application",
|
"en": "Application",
|
||||||
@@ -2783,6 +2783,22 @@
|
|||||||
"de": "GitHub-Repositories konfigurieren",
|
"de": "GitHub-Repositories konfigurieren",
|
||||||
"uk": "Налаштування репозиторіїв Github"
|
"uk": "Налаштування репозиторіїв Github"
|
||||||
},
|
},
|
||||||
|
"SLACK$INSTALL_APP": {
|
||||||
|
"en": "Install OpenHands Slack App",
|
||||||
|
"ja": "OpenHands Slackアプリをインストール",
|
||||||
|
"zh-CN": "安装 OpenHands Slack 应用",
|
||||||
|
"zh-TW": "安裝 OpenHands Slack 應用程式",
|
||||||
|
"ko-KR": "OpenHands Slack 앱 설치",
|
||||||
|
"no": "Installer OpenHands Slack-app",
|
||||||
|
"it": "Installa l'app Slack di OpenHands",
|
||||||
|
"pt": "Instalar aplicativo Slack do OpenHands",
|
||||||
|
"es": "Instalar aplicación Slack de OpenHands",
|
||||||
|
"ar": "تثبيت تطبيق OpenHands Slack",
|
||||||
|
"fr": "Installer l'application Slack OpenHands",
|
||||||
|
"tr": "OpenHands Slack uygulamasını yükle",
|
||||||
|
"de": "OpenHands Slack-App installieren",
|
||||||
|
"uk": "Встановити додаток OpenHands Slack"
|
||||||
|
},
|
||||||
"COMMON$CLICK_FOR_INSTRUCTIONS": {
|
"COMMON$CLICK_FOR_INSTRUCTIONS": {
|
||||||
"en": "Click here for instructions",
|
"en": "Click here for instructions",
|
||||||
"ja": "手順はこちらをクリック",
|
"ja": "手順はこちらをクリック",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default [
|
|||||||
index("routes/llm-settings.tsx"),
|
index("routes/llm-settings.tsx"),
|
||||||
route("mcp", "routes/mcp-settings.tsx"),
|
route("mcp", "routes/mcp-settings.tsx"),
|
||||||
route("user", "routes/user-settings.tsx"),
|
route("user", "routes/user-settings.tsx"),
|
||||||
route("git", "routes/git-settings.tsx"),
|
route("integrations", "routes/git-settings.tsx"),
|
||||||
route("app", "routes/app-settings.tsx"),
|
route("app", "routes/app-settings.tsx"),
|
||||||
route("billing", "routes/billing.tsx"),
|
route("billing", "routes/billing.tsx"),
|
||||||
route("secrets", "routes/secrets-settings.tsx"),
|
route("secrets", "routes/secrets-settings.tsx"),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useLogout } from "#/hooks/mutation/use-logout";
|
|||||||
import { GitHubTokenInput } from "#/components/features/settings/git-settings/github-token-input";
|
import { GitHubTokenInput } from "#/components/features/settings/git-settings/github-token-input";
|
||||||
import { GitLabTokenInput } from "#/components/features/settings/git-settings/gitlab-token-input";
|
import { GitLabTokenInput } from "#/components/features/settings/git-settings/gitlab-token-input";
|
||||||
import { ConfigureGitHubRepositoriesAnchor } from "#/components/features/settings/git-settings/configure-github-repositories-anchor";
|
import { ConfigureGitHubRepositoriesAnchor } from "#/components/features/settings/git-settings/configure-github-repositories-anchor";
|
||||||
|
import { InstallSlackAppAnchor } from "#/components/features/settings/git-settings/install-slack-app-anchor";
|
||||||
import { I18nKey } from "#/i18n/declaration";
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
import {
|
import {
|
||||||
displayErrorToast,
|
displayErrorToast,
|
||||||
@@ -103,6 +104,10 @@ function GitSettingsScreen() {
|
|||||||
<ConfigureGitHubRepositoriesAnchor slug={config.APP_SLUG!} />
|
<ConfigureGitHubRepositoriesAnchor slug={config.APP_SLUG!} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{shouldRenderExternalConfigureButtons && !isLoading && (
|
||||||
|
<InstallSlackAppAnchor />
|
||||||
|
)}
|
||||||
|
|
||||||
{!isSaas && (
|
{!isSaas && (
|
||||||
<GitHubTokenInput
|
<GitHubTokenInput
|
||||||
name="github-token-input"
|
name="github-token-input"
|
||||||
|
|||||||
@@ -84,7 +84,11 @@ function SecretsSettingsScreen() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{shouldRenderConnectToGitButton && (
|
{shouldRenderConnectToGitButton && (
|
||||||
<Link to="/settings/git" data-testid="connect-git-button" type="button">
|
<Link
|
||||||
|
to="/settings/integrations"
|
||||||
|
data-testid="connect-git-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
<BrandButton type="button" variant="secondary">
|
<BrandButton type="button" variant="secondary">
|
||||||
Connect a Git provider to manage secrets
|
Connect a Git provider to manage secrets
|
||||||
</BrandButton>
|
</BrandButton>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function SettingsScreen() {
|
|||||||
|
|
||||||
const saasNavItems = [
|
const saasNavItems = [
|
||||||
{ to: "/settings/user", text: t("SETTINGS$NAV_USER") },
|
{ to: "/settings/user", text: t("SETTINGS$NAV_USER") },
|
||||||
{ to: "/settings/git", text: t("SETTINGS$NAV_GIT") },
|
{ to: "/settings/integrations", text: t("SETTINGS$NAV_INTEGRATIONS") },
|
||||||
{ to: "/settings/app", text: t("SETTINGS$NAV_APPLICATION") },
|
{ to: "/settings/app", text: t("SETTINGS$NAV_APPLICATION") },
|
||||||
{ to: "/settings/billing", text: t("SETTINGS$NAV_CREDITS") },
|
{ to: "/settings/billing", text: t("SETTINGS$NAV_CREDITS") },
|
||||||
{ to: "/settings/secrets", text: t("SETTINGS$NAV_SECRETS") },
|
{ to: "/settings/secrets", text: t("SETTINGS$NAV_SECRETS") },
|
||||||
@@ -26,7 +26,7 @@ function SettingsScreen() {
|
|||||||
const ossNavItems = [
|
const ossNavItems = [
|
||||||
{ to: "/settings", text: t("SETTINGS$NAV_LLM") },
|
{ to: "/settings", text: t("SETTINGS$NAV_LLM") },
|
||||||
{ to: "/settings/mcp", text: t("SETTINGS$NAV_MCP") },
|
{ to: "/settings/mcp", text: t("SETTINGS$NAV_MCP") },
|
||||||
{ to: "/settings/git", text: t("SETTINGS$NAV_GIT") },
|
{ to: "/settings/integrations", text: t("SETTINGS$NAV_INTEGRATIONS") },
|
||||||
{ to: "/settings/app", text: t("SETTINGS$NAV_APPLICATION") },
|
{ to: "/settings/app", text: t("SETTINGS$NAV_APPLICATION") },
|
||||||
{ to: "/settings/secrets", text: t("SETTINGS$NAV_SECRETS") },
|
{ to: "/settings/secrets", text: t("SETTINGS$NAV_SECRETS") },
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user