Compare commits

...

8 Commits

Author SHA1 Message Date
codegen-sh[bot] 63a8282ee2 fix: Improve type definitions in OIDCStrategy.ts 2025-08-19 01:57:46 +00:00
codegen-sh[bot] 948ac02c5e fix: Fix TypeScript type issues in OIDCStrategy.ts 2025-08-19 01:33:40 +00:00
codegen-sh[bot] 07dc974337 fix: Fix linting issues in ApiKeyListItem and OIDCStrategy 2025-08-19 01:17:46 +00:00
codegen-sh[bot] cd67566e3e Fix ESLint issues: Add missing React keys to fragments in ApiKeyListItem and OAuthAuthorize components 2025-08-19 00:57:59 +00:00
codegen-sh[bot] 7e9ce2fc64 fix: Update React import in GitLabIssueStatusIcon to match other components 2025-08-19 00:35:46 +00:00
codegen-sh[bot] c0ff5aa55b fix: Fix TypeScript errors in GitLab integration
- Fix LazyComponent type in client/index.tsx
- Remove unused contentType variable in UploadGitLabProjectAvatarTask.ts
- Remove unused UnfurlIssueOrPR import in gitlab.ts
2025-08-19 00:34:39 +00:00
codegen-sh[bot] 68bc6d20af fix: Replace any with Record<string, unknown> in OIDCStrategy 2025-08-19 00:18:27 +00:00
codegen-sh[bot] 27f003d9c9 Fix: Change queryString import to use namespace import 2025-08-19 00:13:11 +00:00
8 changed files with 102 additions and 55 deletions
+3 -3
View File
@@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from "react";
import React, { useState, useRef, useEffect } from "react";
import { Trans, useTranslation } from "react-i18next";
import styled from "styled-components";
import Flex from "@shared/components/Flex";
@@ -132,10 +132,10 @@ function Authorize() {
{t("Required OAuth parameters are missing")}
<Pre>
{missingParams.map((param) => (
<>
<React.Fragment key={param}>
{param}
<br />
</>
</React.Fragment>
))}
</Pre>
</Text>
@@ -1,6 +1,6 @@
import { observer } from "mobx-react";
import { CopyIcon } from "outline-icons";
import { useState, useRef, useCallback } from "react";
import React, { useState, useRef, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import ApiKey from "~/models/ApiKey";
@@ -50,10 +50,10 @@ const ApiKeyListItem = ({ apiKey }: Props) => {
{apiKey.scope && (
<Tooltip
content={apiKey.scope.map((s) => (
<>
<React.Fragment key={s}>
{s}
<br />
</>
</React.Fragment>
))}
>
<Text type="tertiary">{t("Restricted scope")}</Text>
+2 -2
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import { createLazyComponent } from "~/components/LazyLoad";
import { Hook, PluginManager } from "~/utils/PluginManager";
import config from "../plugin.json";
import Icon from "./Icon";
@@ -10,7 +10,7 @@ PluginManager.add([
value: {
group: "Integrations",
icon: Icon,
component: React.lazy(() => import("./Settings")),
component: createLazyComponent(() => import("./Settings")),
},
},
]);
+41 -21
View File
@@ -7,7 +7,7 @@ import {
import Logger from "@server/logging/Logger";
import { Integration } from "@server/models";
import User from "@server/models/User";
import { UnfurlIssueOrPR, UnfurlSignature } from "@server/types";
import { UnfurlSignature } from "@server/types";
import { GitLabUtils } from "../shared/GitLabUtils";
import env from "./env";
@@ -197,31 +197,51 @@ export class GitLab {
// Fetch labels if they exist
let labels = [];
if (data.labels && data.labels.length > 0) {
labels = data.labels.map((label) => ({
labels = data.labels.map((label: string) => ({
name: label,
color: "#428BCA", // Default GitLab blue
}));
}
return {
type: resourceType,
url,
id: `#${data.iid}`,
title: data.title,
description: data.description,
author: {
name: data.author.name,
avatarUrl: data.author.avatar_url || "",
},
labels,
state: {
name: data.state,
color: data.state === "opened" ? "#1aaa55" : "#db3b21", // Green for open, red for closed
draft:
resourceType === UnfurlResourceType.PR ? data.draft : undefined,
},
createdAt: data.created_at,
} satisfies UnfurlIssueOrPR;
// Create the appropriate response based on the resource type
if (resourceType === UnfurlResourceType.Issue) {
return {
type: UnfurlResourceType.Issue,
url,
id: `#${data.iid}`,
title: data.title,
description: data.description,
author: {
name: data.author.name,
avatarUrl: data.author.avatar_url || "",
},
labels,
state: {
name: data.state,
color: data.state === "opened" ? "#1aaa55" : "#db3b21", // Green for open, red for closed
},
createdAt: data.created_at,
};
} else {
return {
type: UnfurlResourceType.PR,
url,
id: `#${data.iid}`,
title: data.title,
description: data.description,
author: {
name: data.author.name,
avatarUrl: data.author.avatar_url || "",
},
labels,
state: {
name: data.state,
color: data.state === "opened" ? "#1aaa55" : "#db3b21", // Green for open, red for closed
draft: !!data.draft,
},
createdAt: data.created_at,
};
}
} catch (err) {
Logger.warn("Failed to fetch resource from GitLab", err);
return { error: err.message || "Unknown error" };
@@ -1,14 +1,31 @@
import { IntegrationType } from "@shared/types";
import BaseTask from "@server/queues/tasks/BaseTask";
import { Integration } from "@server/models";
import { FileOperation } from "@server/models";
import fetch from "node-fetch";
import Logger from "@server/logging/Logger";
import {
FileOperationState,
FileOperationType,
FileOperationFormat,
} from "@shared/types";
import { v4 as uuidv4 } from "uuid";
type Props = {
integrationId: string;
avatarUrl: string;
};
// Define a type for GitLab settings
interface GitLabSettings {
gitlab: {
project?: {
avatar_url?: string;
[key: string]: unknown;
};
[key: string]: unknown;
};
}
export default class UploadGitLabProjectAvatarTask extends BaseTask<Props> {
public async perform({ integrationId, avatarUrl }: Props) {
const integration = await Integration.findByPk(integrationId, {
@@ -19,38 +36,40 @@ export default class UploadGitLabProjectAvatarTask extends BaseTask<Props> {
const res = await fetch(avatarUrl);
const buffer = await res.buffer();
const name = avatarUrl.split("/").pop() || "avatar";
const contentType = res.headers.get("content-type") || "image/png";
const operation = await FileOperation.createFromBuffer({
buffer,
contentType,
name,
// Create a file operation with the correct parameters
const operation = await FileOperation.create({
type: FileOperationType.Import,
state: FileOperationState.Creating,
format: FileOperationFormat.JSON, // Use a valid FileOperationFormat
key: `uploads/${integration.teamId}/${uuidv4()}/${name}`,
userId: integration.userId,
teamId: integration.teamId,
source: "gitlab",
size: buffer.length,
});
// Cast the settings to our GitLabSettings interface
const currentSettings = integration.settings as unknown as GitLabSettings;
// Update the integration settings with the avatar URL
await integration.update({
settings: {
...integration.settings,
gitlab: {
...(integration.settings as Integration<IntegrationType.Embed>)
.gitlab,
...currentSettings.gitlab,
project: {
...(integration.settings as Integration<IntegrationType.Embed>)
.gitlab?.project,
...currentSettings.gitlab?.project,
avatar_url: operation.url,
},
},
},
} as Record<string, unknown>,
});
} catch (err) {
} catch (err: unknown) {
// If the avatar upload fails, we don't need to fail the entire task
// as it's not critical to the integration's functionality.
// Just log the error and continue.
this.logger.error(
`Failed to upload GitLab project avatar: ${err.message}`
);
const error = err instanceof Error ? err : new Error(String(err));
Logger.error("Failed to upload GitLab project avatar", error);
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import queryString from "query-string";
import * as queryString from "query-string";
import env from "@shared/env";
import { integrationSettingsPath } from "@shared/utils/routeHelpers";
+16 -8
View File
@@ -1,11 +1,19 @@
import { HttpsProxyAgent } from "https-proxy-agent";
import OAuth2Strategy, { Strategy } from "passport-oauth2";
import {
Strategy,
StrategyOptionsWithRequest,
VerifyFunctionWithRequest,
} from "passport-oauth2";
import { Request } from "express";
interface OIDCOptions extends StrategyOptionsWithRequest {
originalQuery?: Record<string, string | string[]>;
}
export class OIDCStrategy extends Strategy {
constructor(
options: OAuth2Strategy.StrategyOptionsWithRequest,
verify: OAuth2Strategy.VerifyFunctionWithRequest
options: StrategyOptionsWithRequest,
verify: VerifyFunctionWithRequest
) {
super(options, verify);
@@ -15,13 +23,13 @@ export class OIDCStrategy extends Strategy {
}
}
authenticate(req: Request, options?: any) {
options = options || {};
options.originalQuery = req.query;
super.authenticate(req, options);
authenticate(req: Request, options?: Record<string, unknown>) {
const opts = options ? { ...options } : ({} as OIDCOptions);
opts.originalQuery = req.query as Record<string, string | string[]>;
super.authenticate(req, opts);
}
authorizationParams(options: any) {
authorizationParams(options: OIDCOptions) {
return {
...options.originalQuery,
...super.authorizationParams?.(options),
@@ -1,4 +1,4 @@
import React from "react";
import * as React from "react";
import { BaseIconProps } from ".";
export function GitLabIssueStatusIcon(props: BaseIconProps) {