mirror of
https://github.com/outline/outline.git
synced 2026-06-13 19:35:02 +03:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 739290b5fc | |||
| cd67566e3e | |||
| 7e9ce2fc64 | |||
| c0ff5aa55b | |||
| 68bc6d20af | |||
| 27f003d9c9 | |||
| b689ebd8ca | |||
| 8e74bb7d01 |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")),
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ export class OIDCStrategy extends Strategy {
|
||||
}
|
||||
}
|
||||
|
||||
authenticate(req: Request, options?: any) {
|
||||
authenticate(req: Request, options?: Record<string, unknown>) {
|
||||
options = options || {};
|
||||
options.originalQuery = req.query;
|
||||
super.authenticate(req, options);
|
||||
}
|
||||
|
||||
authorizationParams(options: any) {
|
||||
authorizationParams(options: Record<string, unknown>) {
|
||||
return {
|
||||
...options.originalQuery,
|
||||
...super.authorizationParams?.(options),
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
import * as React from "react";
|
||||
import { isSafari } from "../../utils/browser";
|
||||
import { BaseIconProps } from ".";
|
||||
|
||||
/** Renders an icon for a specific GitLab issue state */
|
||||
export function GitLabIssueStatusIcon(props: BaseIconProps) {
|
||||
// No theme needed for this component
|
||||
const { state } = props;
|
||||
const { state, className, size = 16 } = props;
|
||||
const isOpen = state.name === "opened";
|
||||
const color = state.color || (isOpen ? "#1aaa55" : "#db3b21"); // Green for open, red for closed
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width={size}
|
||||
height={size}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ marginTop: isSafari() ? 0 : -2 }}
|
||||
className={className}
|
||||
>
|
||||
<circle cx="8" cy="8" r="7" stroke={color} strokeWidth="2" fill="none" />
|
||||
{!isOpen && (
|
||||
@@ -27,7 +23,7 @@ export function GitLabIssueStatusIcon(props: BaseIconProps) {
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
)}
|
||||
{state.draft && (
|
||||
{"draft" in state && state.draft && (
|
||||
<rect x="4" y="7" width="8" height="2" rx="1" fill={color} />
|
||||
)}
|
||||
</svg>
|
||||
|
||||
@@ -22,7 +22,7 @@ function getSortByField<T extends Record<string, unknown>>(
|
||||
typeof keyOrCallback === "string"
|
||||
? item[keyOrCallback]
|
||||
: keyOrCallback(item);
|
||||
return cleanValue(field);
|
||||
return cleanValue(String(field));
|
||||
}
|
||||
|
||||
function naturalSortBy<T extends Record<string, unknown>>(
|
||||
|
||||
Reference in New Issue
Block a user