mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
3 Commits
debug-logg
...
linear-int
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1cd8dd41f | ||
|
|
2a7fbcd519 | ||
|
|
6ef82e1501 |
@@ -67,24 +67,27 @@ description: Complete guide for setting up Linear integration with OpenHands Clo
|
||||
- Sign in with your Git provider (GitHub, GitLab, or BitBucket)
|
||||
- **Important:** Make sure you're signing in with the same Git provider account that contains the repositories you want the OpenHands agent to work on.
|
||||
|
||||
### Step 2: Configure Linear Integration
|
||||
### Step 2: View Available Workspaces
|
||||
|
||||
1. **Access Integration Settings**
|
||||
- Navigate to **Settings** > **Integrations**
|
||||
- Locate **Linear** section
|
||||
|
||||
2. **Configure Workspace**
|
||||
- Click **Configure** button
|
||||
- Enter your workspace name and click **Connect**
|
||||
- If no integration exists, you'll be prompted to enter additional credentials required for the workspace integration:
|
||||
- **Webhook Secret**: The webhook secret from Step 3 above
|
||||
- **Service Account Email**: The service account email from Step 1 above
|
||||
- **Service Account API Key**: The API key from Step 2 above
|
||||
- Ensure **Active** toggle is enabled
|
||||
2. **Install Linear App**
|
||||
- Click the **Install Linear App** button
|
||||
- You'll be redirected to a page showing available Linear workspaces
|
||||
|
||||
3. **Complete OAuth Flow**
|
||||
3. **Select Workspace**
|
||||
- Choose the workspace you want to connect to
|
||||
- If no integration exists, you'll be prompted to enter additional credentials required for the workspace integration:
|
||||
- **Webhook Secret**: The webhook secret from Step 3 above
|
||||
- **Service Account Email**: The service account email from Step 1 above
|
||||
- **Service Account API Key**: The API key from Step 2 above
|
||||
- Ensure **Active** toggle is enabled
|
||||
|
||||
4. **Complete OAuth Flow**
|
||||
- You'll be redirected to Linear to complete OAuth verification
|
||||
- Grant the necessary permissions to verify your workspace access. If you have access to multiple workspaces, select the correct one that you initially provided
|
||||
- Grant the necessary permissions to verify your workspace access
|
||||
- If successful, you will be redirected back to the **Integrations** settings in the OpenHands Cloud UI
|
||||
|
||||
### Managing Your Integration
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
ConfigureButton,
|
||||
ConfigureModal,
|
||||
} from "#/components/features/settings/project-management/configure-modal";
|
||||
import { LinearInstallButton } from "#/components/features/settings/project-management/linear-install-button";
|
||||
|
||||
interface IntegrationRowProps {
|
||||
platform: "jira" | "jira-dc" | "linear";
|
||||
@@ -88,23 +89,29 @@ export function IntegrationRow({
|
||||
<div className="flex items-center justify-between" data-testid={dataTestId}>
|
||||
<span className="font-medium">{platformName}</span>
|
||||
<div className="flex items-center gap-6">
|
||||
<ConfigureButton
|
||||
onClick={handleConfigure}
|
||||
isDisabled={isLoading}
|
||||
text={buttonText}
|
||||
data-testid={`${platform}-configure-button`}
|
||||
/>
|
||||
{platform === "linear" ? (
|
||||
<LinearInstallButton data-testid={`${platform}-install-button`} />
|
||||
) : (
|
||||
<ConfigureButton
|
||||
onClick={handleConfigure}
|
||||
isDisabled={isLoading}
|
||||
text={buttonText}
|
||||
data-testid={`${platform}-configure-button`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ConfigureModal
|
||||
isOpen={isConfigureModalOpen}
|
||||
onClose={() => setConfigureModalOpen(false)}
|
||||
onConfirm={handleConfigureConfirm}
|
||||
onLink={handleLink}
|
||||
onUnlink={handleUnlink}
|
||||
platformName={platformName}
|
||||
platform={platform}
|
||||
integrationData={integrationData}
|
||||
/>
|
||||
{platform !== "linear" && (
|
||||
<ConfigureModal
|
||||
isOpen={isConfigureModalOpen}
|
||||
onClose={() => setConfigureModalOpen(false)}
|
||||
onConfirm={handleConfigureConfirm}
|
||||
onLink={handleLink}
|
||||
onUnlink={handleUnlink}
|
||||
platformName={platformName}
|
||||
platform={platform}
|
||||
integrationData={integrationData}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { useLinearInstall } from "#/hooks/mutation/use-linear-install";
|
||||
|
||||
interface LinearInstallButtonProps {
|
||||
"data-testid"?: string;
|
||||
}
|
||||
|
||||
export function LinearInstallButton({
|
||||
"data-testid": dataTestId,
|
||||
}: LinearInstallButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
const linearInstallMutation = useLinearInstall();
|
||||
|
||||
const handleInstallClick = () => {
|
||||
linearInstallMutation.mutate();
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleInstallClick}
|
||||
disabled={linearInstallMutation.isPending}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
data-testid={dataTestId}
|
||||
>
|
||||
{linearInstallMutation.isPending
|
||||
? "Installing..."
|
||||
: t(I18nKey.PROJECT_MANAGEMENT$INSTALL_LINEAR_APP)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
42
frontend/src/hooks/mutation/use-linear-install.ts
Normal file
42
frontend/src/hooks/mutation/use-linear-install.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { openHands } from "#/api/open-hands-axios";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { displayErrorToast } from "#/utils/custom-toast-handlers";
|
||||
import { retrieveAxiosErrorMessage } from "#/utils/retrieve-axios-error-message";
|
||||
|
||||
export function useLinearInstall() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const response = await openHands.post(
|
||||
"/integration/linear/workspaces/link",
|
||||
{},
|
||||
);
|
||||
|
||||
const { success, redirect, authorizationUrl } = response.data;
|
||||
|
||||
if (success) {
|
||||
if (redirect) {
|
||||
if (authorizationUrl) {
|
||||
window.location.href = authorizationUrl;
|
||||
} else {
|
||||
throw new Error("Could not get authorization URL from the server.");
|
||||
}
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
throw new Error("Linear installation failed");
|
||||
}
|
||||
|
||||
return response.data;
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorMessage = retrieveAxiosErrorMessage(error);
|
||||
displayErrorToast(errorMessage || t(I18nKey.ERROR$GENERIC));
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -747,6 +747,7 @@ export enum I18nKey {
|
||||
PROJECT_MANAGEMENT$UNLINK_BUTTON_LABEL = "PROJECT_MANAGEMENT$UNLINK_BUTTON_LABEL",
|
||||
PROJECT_MANAGEMENT$LINK_CONFIRMATION_TITLE = "PROJECT_MANAGEMENT$LINK_CONFIRMATION_TITLE",
|
||||
PROJECT_MANAGEMENT$CONFIGURE_BUTTON_LABEL = "PROJECT_MANAGEMENT$CONFIGURE_BUTTON_LABEL",
|
||||
PROJECT_MANAGEMENT$INSTALL_LINEAR_APP = "PROJECT_MANAGEMENT$INSTALL_LINEAR_APP",
|
||||
PROJECT_MANAGEMENT$EDIT_BUTTON_LABEL = "PROJECT_MANAGEMENT$EDIT_BUTTON_LABEL",
|
||||
PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL = "PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL",
|
||||
PROJECT_MANAGEMENT$WORKSPACE_NAME_PLACEHOLDER = "PROJECT_MANAGEMENT$WORKSPACE_NAME_PLACEHOLDER",
|
||||
|
||||
@@ -11951,6 +11951,13 @@
|
||||
"de": "Konfigurieren",
|
||||
"uk": "Налаштувати"
|
||||
},
|
||||
"PROJECT_MANAGEMENT$INSTALL_LINEAR_APP": {
|
||||
"en": "Install Linear App",
|
||||
"ja": "Linear アプリをインストール",
|
||||
"zh-CN": "安装 Linear 应用",
|
||||
"zh-TW": "安裝 Linear 應用",
|
||||
"ko-KR": "Linear 앱 설치"
|
||||
},
|
||||
"PROJECT_MANAGEMENT$EDIT_BUTTON_LABEL": {
|
||||
"en": "Edit",
|
||||
"ja": "編集",
|
||||
|
||||
Reference in New Issue
Block a user