Compare commits

..

18 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
codegen-sh[bot] b689ebd8ca fix: Update GitLabIssueStatusIcon to match style of other icon components 2025-08-18 23:49:32 +00:00
codegen-sh[bot] 8e74bb7d01 fix: add type guard for draft property in GitLabIssueStatusIcon 2025-08-18 23:20:56 +00:00
codegen-sh[bot] c02bc22cce Fix code formatting issues to pass CI checks 2025-08-18 23:16:18 +00:00
codegen-sh[bot] 7ea308a52c Fix linting issues in React Hook dependencies 2025-08-18 23:15:39 +00:00
codegen-sh[bot] e4d1a38367 fix: Fix TypeScript errors in OIDCStrategy
- Change Record<string, unknown> to any to match the Strategy interface
- Add null check for options parameter
2025-08-18 23:15:22 +00:00
codegen-sh[bot] 98d54da0de fix: Remove trailing whitespace in HoverPreviewIssue.tsx 2025-08-18 23:03:21 +00:00
codegen-sh[bot] 4b638ae346 fix: fix remaining linting errors 2025-08-18 22:38:22 +00:00
codegen-sh[bot] abb849e1f6 fix: replace any with unknown in types and add proper type definitions 2025-08-18 22:30:48 +00:00
codegen-sh[bot] 2ec65e3dfc fix: replace any with unknown in MutexLock 2025-08-18 22:27:16 +00:00
codegen-sh[bot] 4e493972e5 fix: fix remaining linting errors 2025-08-18 22:24:07 +00:00
34 changed files with 203 additions and 123 deletions
+1 -1
View File
@@ -125,8 +125,8 @@ function Collaborators(props: Props) {
return (
<AvatarWithPresence
{...rest}
key={collaborator.id}
{...rest}
user={collaborator}
isPresent={isPresent}
isEditing={isEditing}
@@ -31,7 +31,7 @@ const HoverPreviewIssue = React.forwardRef(function _HoverPreviewIssue(
const authorName = author.name;
const urlObj = new URL(url);
let service;
if (urlObj.hostname === "github.com") {
service = IntegrationService.GitHub;
} else if (urlObj.hostname === "gitlab.com") {
+2 -2
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import lazyWithRetry from "~/utils/lazyWithRetry";
export interface LazyComponent<T extends React.ComponentType<any>> {
export interface LazyComponent<T extends React.ComponentType<unknown>> {
Component: React.LazyExoticComponent<T>;
preload: () => Promise<{ default: T }>;
}
@@ -34,7 +34,7 @@ interface LazyLoadOptions {
* MyComponent.preload();
* ```
*/
export function createLazyComponent<T extends React.ComponentType<any>>(
export function createLazyComponent<T extends React.ComponentType<unknown>>(
factory: () => Promise<{ default: T }>,
options: LazyLoadOptions = {}
): LazyComponent<T> {
+1 -1
View File
@@ -14,7 +14,7 @@ describe("PaginatedList", () => {
i18n,
tReady: true,
t: ((key: string) => key) as TFunction,
} as any;
} as unknown;
it("with no items renders nothing", () => {
const result = render(
+6 -4
View File
@@ -34,11 +34,11 @@ interface Props<T extends PaginatedItem>
* @param options Pagination and other query options
*/
fetch?: (
options: Record<string, any> | undefined
options: Record<string, unknown> | undefined
) => Promise<unknown[] | undefined> | undefined;
/** Additional options to pass to the fetch function */
options?: Record<string, any>;
options?: Record<string, unknown>;
/** Optional header content to display above the list */
heading?: React.ReactNode;
@@ -77,7 +77,9 @@ interface Props<T extends PaginatedItem>
* Function to render section headings (typically date-based)
* @param name The heading text or element to render
*/
renderHeading?: (name: React.ReactElement<any> | string) => React.ReactNode;
renderHeading?: (
name: React.ReactElement<unknown> | string
) => React.ReactNode;
/**
* Handler for escape key press
@@ -206,7 +208,7 @@ const PaginatedList = <T extends PaginatedItem>({
if (fetch) {
void fetchResults();
}
}, [fetch]);
}, [fetch, fetchResults]);
// Handle updates to fetch or options
React.useEffect(() => {
+1 -1
View File
@@ -9,7 +9,7 @@ function Toasts() {
return (
<StyledToaster
theme={ui.resolvedTheme as any}
theme={ui.resolvedTheme as unknown}
closeButton
toastOptions={{
duration: 5000,
+24 -18
View File
@@ -70,7 +70,7 @@ const LinkEditor: React.FC<Props> = ({
React.useCallback(async () => {
const res = await client.post("/suggestions.mention", { query });
res.data.documents.map(documents.add);
}, [query])
}, [query, documents.add])
);
useEffect(() => {
@@ -79,6 +79,22 @@ const LinkEditor: React.FC<Props> = ({
}
}, [trimmedQuery, request]);
const save = React.useCallback(
(href: string, title?: string) => {
href = href.trim();
if (href.length === 0) {
return;
}
discardRef.current = true;
href = sanitizeUrl(href) ?? "";
onSelectLink({ href, title, from, to });
},
[onSelectLink, from, to]
);
useEffect(() => {
const handleGlobalKeyDown = (event: KeyboardEvent) => {
if (event.key === "k" && event.metaKey) {
@@ -107,20 +123,7 @@ const LinkEditor: React.FC<Props> = ({
save(trimmedQuery, trimmedQuery);
};
}, [trimmedQuery, initialValue]);
const save = (href: string, title?: string) => {
href = href.trim();
if (href.length === 0) {
return;
}
discardRef.current = true;
href = sanitizeUrl(href) ?? "";
onSelectLink({ href, title, from, to });
};
}, [trimmedQuery, initialValue, handleRemoveLink, save]);
const moveSelectionToEnd = () => {
const { state, dispatch } = view;
@@ -195,7 +198,7 @@ const LinkEditor: React.FC<Props> = ({
}
};
const handleRemoveLink = () => {
const handleRemoveLink = React.useCallback(() => {
discardRef.current = true;
const { state, dispatch } = view;
@@ -203,9 +206,12 @@ const LinkEditor: React.FC<Props> = ({
dispatch(state.tr.removeMark(from, to, mark));
}
onRemoveLink?.();
if (onRemoveLink) {
onRemoveLink();
}
view.focus();
};
}, [view, mark, from, to, onRemoveLink]);
const isInternal = isInternalUrl(query);
const hasResults = !!results.length;
+10 -1
View File
@@ -184,7 +184,16 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
setItems(items);
setLoaded(true);
}
}, [t, actorId, loading, search, users, documents, maxResultsInSection]);
}, [
t,
actorId,
loading,
search,
users,
documents,
maxResultsInSection,
collections,
]);
const handleSelect = useCallback(
async (item: MentionItem) => {
+1 -1
View File
@@ -87,7 +87,7 @@ function useIsActive(state: EditorState) {
const slice = selection.content();
const fragment = slice.content;
const nodes = (fragment as any).content;
const nodes = (fragment as unknown).content;
return some(nodes, (n) => n.content.size);
}
+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
@@ -125,7 +125,7 @@ export type Action = {
* Perform the action note this should generally not be called directly, use `performAction`
* instead. Errors will be caught and displayed to the user as a toast message.
*/
perform?: (context: ActionContext) => any;
perform?: (context: ActionContext) => unknown;
to?: string | { url: string; target?: string };
children?: ((context: ActionContext) => Action[]) | Action[];
};
@@ -154,7 +154,7 @@ export type ActionV2 = BaseActionV2 & {
tooltip?:
| ((context: ActionContext) => React.ReactChild | undefined)
| React.ReactChild;
perform: (context: ActionContext) => any;
perform: (context: ActionContext) => unknown;
};
export type InternalLinkActionV2 = BaseActionV2 & {
+3 -3
View File
@@ -1,6 +1,6 @@
import * as React from "react";
type ComponentPromise<T extends React.ComponentType<any>> = Promise<{
type ComponentPromise<T extends React.ComponentType<unknown>> = Promise<{
default: T;
}>;
@@ -12,7 +12,7 @@ type ComponentPromise<T extends React.ComponentType<any>> = Promise<{
* @param interval The interval between retries in milliseconds, defaults to 1000.
* @returns A lazy component.
*/
export default function lazyWithRetry<T extends React.ComponentType<any>>(
export default function lazyWithRetry<T extends React.ComponentType<unknown>>(
component: () => ComponentPromise<T>,
retries?: number,
interval?: number
@@ -20,7 +20,7 @@ export default function lazyWithRetry<T extends React.ComponentType<any>>(
return React.lazy(() => retry(component, retries, interval));
}
function retry<T extends React.ComponentType<any>>(
function retry<T extends React.ComponentType<unknown>>(
fn: () => ComponentPromise<T>,
retriesLeft = 3,
interval = 1000
+2 -1
View File
@@ -43,7 +43,8 @@ export const isURLMentionable = ({
return (
hostname === "gitlab.com" &&
settings.gitlab?.project.path_with_namespace === pathParts.slice(1, -2).join("/") // ensure installed project path matches with the provided url.
settings.gitlab?.project.path_with_namespace ===
pathParts.slice(1, -2).join("/") // ensure installed project path matches with the provided url.
);
}
-1
View File
@@ -48,4 +48,3 @@ export default function Icon({ size = 24, fill = "currentColor" }: Props) {
</svg>
);
}
+3 -5
View File
@@ -59,8 +59,8 @@ function GitLab() {
<>
<Text as="p">
<Trans>
Enable previews of GitLab issues and merge requests in documents by connecting a
GitLab project to {appName}.
Enable previews of GitLab issues and merge requests in documents
by connecting a GitLab project to {appName}.
</Trans>
</Text>
{integrations.gitlab.length ? (
@@ -73,8 +73,7 @@ function GitLab() {
</Heading>
<List>
{integrations.gitlab.map((integration) => {
const gitlabProject =
integration.settings?.gitlab?.project;
const gitlabProject = integration.settings?.gitlab?.project;
const integrationCreatedBy = integration.user
? integration.user.name
: undefined;
@@ -138,4 +137,3 @@ function GitLab() {
}
export default observer(GitLab);
@@ -21,4 +21,3 @@ export function GitLabConnectButton(props: Props<HTMLButtonElement>) {
</Button>
);
}
+2 -3
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,8 +10,7 @@ PluginManager.add([
value: {
group: "Integrations",
icon: Icon,
component: React.lazy(() => import("./Settings")),
component: createLazyComponent(() => import("./Settings")),
},
},
]);
-1
View File
@@ -5,4 +5,3 @@
"description": "Adds a GitLab integration for link unfurling and converting links to mentions.",
"after": "linear"
}
-1
View File
@@ -97,4 +97,3 @@ router.get(
);
export default router;
-1
View File
@@ -15,4 +15,3 @@ export const GitLabCallbackSchema = BaseSchema.extend({
});
export type GitLabCallbackReq = z.infer<typeof GitLabCallbackSchema>;
-1
View File
@@ -4,4 +4,3 @@ export default {
GITLAB_CLIENT_ID: env.GITLAB_CLIENT_ID,
GITLAB_CLIENT_SECRET: env.GITLAB_CLIENT_SECRET,
};
+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 -2
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";
@@ -49,4 +49,3 @@ export class GitLabUtils {
return `${this.authBaseUrl}?${queryString.stringify(params)}`;
}
}
+9 -1
View File
@@ -55,7 +55,15 @@ export const Notion = observer(() => {
onClose: clearQueryParams,
});
}
}, [t, dialogs, oauthSuccess, service, clearQueryParams]);
}, [
t,
dialogs,
oauthSuccess,
service,
clearQueryParams,
handleSubmit,
integrationId,
]);
React.useEffect(() => {
if (!oauthError) {
@@ -52,7 +52,15 @@ export function ImportDialog({ integrationId, onSubmit }: Props) {
toast.error(err.message);
resetSubmitting();
}
}, [permission, onSubmit]);
}, [
permission,
onSubmit,
integrationId,
t,
imports,
resetSubmitting,
setSubmitting,
]);
return (
<Flex column gap={12}>
+16 -7
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,12 +23,13 @@ export class OIDCStrategy extends Strategy {
}
}
authenticate(req: Request, options: Record<string, unknown>) {
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: Record<string, unknown>) {
authorizationParams(options: OIDCOptions) {
return {
...options.originalQuery,
...super.authorizationParams?.(options),
+1 -1
View File
@@ -573,7 +573,7 @@ router.post(
});
let document: Document | null;
let serializedDocument: Record<string, any> | undefined;
let serializedDocument: Record<string, unknown> | undefined;
let isPublic = false;
if (shareId) {
+9 -5
View File
@@ -227,20 +227,22 @@ describe("#groups.list", () => {
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.groups.length).toEqual(2);
expect(body.data.groups[0].id).toEqual(anotherGroup.id);
expect(body.data.groups[1].id).toEqual(group.id);
expect(body.data.groupMemberships.length).toEqual(2);
expect(body.data.groupMemberships[0].groupId).toEqual(group.id);
expect(body.data.groupMemberships[1].groupId).toEqual(group.id);
expect(
body.data.groupMemberships.map((u: any) => u.user.id).includes(user.id)
body.data.groupMemberships
.map((u: { user: { id: string }; groupId: string }) => u.user.id)
.includes(user.id)
).toBe(true);
expect(
body.data.groupMemberships
.map((u: any) => u.user.id)
.map((u: { user: { id: string }; groupId: string }) => u.user.id)
.includes(anotherUser.id)
).toBe(true);
expect(body.policies.length).toEqual(2);
@@ -259,11 +261,13 @@ describe("#groups.list", () => {
expect(anotherBody.data.groupMemberships[0].groupId).toEqual(group.id);
expect(anotherBody.data.groupMemberships[1].groupId).toEqual(group.id);
expect(
body.data.groupMemberships.map((u: any) => u.user.id).includes(user.id)
body.data.groupMemberships
.map((u: { user: { id: string }; groupId: string }) => u.user.id)
.includes(user.id)
).toBe(true);
expect(
body.data.groupMemberships
.map((u: any) => u.user.id)
.map((u: { user: { id: string }; groupId: string }) => u.user.id)
.includes(anotherUser.id)
).toBe(true);
});
+1 -1
View File
@@ -14,5 +14,5 @@ export class MutexLock {
};
}
private static redlock: any;
private static redlock: unknown;
}
@@ -1,21 +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) {
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 && (
@@ -26,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>
+2 -2
View File
@@ -10,10 +10,10 @@ export type CommandFactory = (attrs?: Record<string, Primitive>) => Command;
export type WidgetProps = { rtl: boolean; readOnly: boolean | undefined };
export default class Extension {
options: any;
options: Record<string, unknown>;
editor: Editor;
constructor(options: Record<string, any> = {}) {
constructor(options: Record<string, unknown> = {}) {
this.options = {
...this.defaultOptions,
...options,
+9 -2
View File
@@ -141,7 +141,9 @@ export const ImportableIntegrationService = {
export type IssueTrackerIntegrationService = Extract<
IntegrationService,
IntegrationService.GitHub | IntegrationService.Linear | IntegrationService.GitLab
| IntegrationService.GitHub
| IntegrationService.Linear
| IntegrationService.GitLab
>;
export const IssueTrackerIntegrationService = {
@@ -192,7 +194,12 @@ export type IntegrationSettings<T> = T extends IntegrationType.Embed
workspace: { id: string; name: string; key: string; logoUrl?: string };
};
gitlab?: {
project: { id: string; name: string; path_with_namespace: string; avatar_url?: string };
project: {
id: string;
name: string;
path_with_namespace: string;
avatar_url?: string;
};
};
}
: T extends IntegrationType.Analytics