[SILO-28] Chore: Gitlab integration UI and backend changes (#2071)

* chore: gitlab integration add project and group APIs

* entity connection schema changes + gitlab integration UI and store changes

* gitlab integration UI + APIs for entity connections

* Merge branch 'preview' into chore-gitlab_integration_ui

* Merge branch 'preview' into chore-gitlab_integration_ui

* gitlab webhook

* sharedwithgroups key in gitlab merge request type

* chore: gitlab update issue based on project connection

* chore: gitlab integration

* merged with preview

* merged with preview

* yarn fix
This commit is contained in:
Saurabh Kumar
2025-01-02 13:19:26 +05:30
committed by GitHub
parent 12a11a9cad
commit 13cefcbc3b
44 changed files with 2132 additions and 602 deletions

View File

@@ -34,6 +34,8 @@ export type TGitlabWorkspaceConnectionType = keyof typeof EGitlabWorkspaceConnec
export type TGitlabWorkspaceConnectionData = {
id: number | undefined;
name: string | undefined;
organization: string | undefined;
url: string | undefined;
type: string | undefined;
login: string | undefined;

View File

@@ -100,4 +100,80 @@ export class GitLabService {
const response = await this.client.get(`/projects/${projectId}/members`);
return response.data;
}
async addWebhookToProject(projectId: string, url: string, token: string) {
try {
const response = await this.client.post(
`/projects/${projectId}/hooks`,
{ url, token, push_events: true, merge_requests_events: true, pipeline_events: true, tag_push_events: true, issues_events: true },
);
return response.data;
} catch (error) {
throw error;
}
}
/**
*
* @param projectId - entityId or gitlab project id
* @param hookId - webhookId or gitlab hook id
* @returns
*/
async removeWebhookFromProject(projectId: string, hookId: string) {
try {
const response = await this.client.delete(
`/projects/${projectId}/hooks/${hookId}`
);
return response.data;
} catch (error) {
throw error;
}
}
async addWebhookToGroup(groupId: string, url: string, token: string) {
try {
const response = await this.client.post(
`/groups/${groupId}/hooks`,
{ url, token, push_events: true, merge_requests_events: true, pipeline_events: true, tag_push_events: true, issues_events: true },
);
return response.data;
} catch (error) {
throw error;
}
}
/**
*
* @param groupId - entityId or gitlab group id
* @param hookId - webhookId or gitlab hook id
* @returns
*/
async removeWebhookFromGroup(groupId: string, hookId: string) {
try {
const response = await this.client.delete(
`/groups/${groupId}/hooks/${hookId}`
);
return response.data;
} catch (error) {
throw error;
}
}
async getGroups() {
try {
const response = await this.client.get("/groups");
return response.data;
} catch (error) {
throw error;
}
}
async getProjects() {
try {
const response = await this.client.get("/projects?membership=true&pages=100");
return response.data;
} catch (error) {
throw error;
}
}
}

View File

@@ -1,4 +1,5 @@
import axios from "axios";
import crypto from 'crypto';
import { GitLabAuthConfig, GitLabAuthorizeState, GitLabAuthPayload, GitLabTokenResponse } from "../types/auth";
const DEFAULT_SCOPES = ["api", "read_api", "read_user", "read_repository", "profile", "email"];
@@ -51,4 +52,55 @@ export class GitLabAuthService {
return { response, state: decodedState };
}
/**
*
* @param workspaceId
* @returns workspace webhook secret
*/
getWorkspaceWebhookSecret(workspaceId: string) {
try {
const GITLAB_CLIENT_SECRET = this.config.clientSecret;
if (!GITLAB_CLIENT_SECRET) {
throw new Error("GITLAB_CLIENT_SECRET is not defined");
}
if (!workspaceId) {
throw new Error("workspaceId is not defined");
}
// Combine the strings with a delimiter (e.g., ":")
const combined = `${workspaceId}:${GITLAB_CLIENT_SECRET}`;
// Hash the combined string using SHA-256
const hash = crypto.createHash('sha256').update(combined).digest('hex');
// Return the first 32 characters of the hash
return hash.slice(0, 32);
} catch (error) {
console.error("error getWorkspaceWebhookSecret", error);
return "";
}
}
/**
*
* @param workspaceId
* @param webhookSecret
* @returns boolean
*/
verifyGitlabWebhookSecret(workspaceId: string, webhookSecret: string) {
try {
const webhookHash = this.getWorkspaceWebhookSecret(workspaceId);
if (!webhookHash) return false;
return webhookHash === webhookSecret;
} catch (error) {
console.error("error verifyGitlabWebhookSecret", error);
return false
}
}
}

View File

@@ -70,6 +70,10 @@ export interface GitlabProject {
kind: string;
full_path: string;
};
shared_with_groups: {
group_id: number;
group_name: string;
}[];
default_branch: string;
visibility: "private" | "internal" | "public";
}
@@ -147,3 +151,31 @@ export interface GitlabNote {
imported?: boolean;
imported_from?: string;
}
export interface GitlabWebhook {
url: string;
token: string;
}
export interface GitlabEntityData {
id: string;
type: string;
webhookId: string;
}
export enum GitlabEntityType {
PROJECT = "PROJECT",
GROUP = "GROUP",
}
export interface IGitlabEntity {
id: string;
name: string;
type: GitlabEntityType;
}
export enum EConnectionType {
ENTITY = "ENTITY",
PLANE_PROJECT = "PLANE_PROJECT",
}

View File

@@ -2,9 +2,12 @@ import { Request, Response } from "express";
import { Controller, Get, Post, Put, Delete } from "@/lib";
import {
createEntityConnectionByWorkspaceConnectionId,
deleteEntityConnection,
deleteEntityConnectionByWorkspaceConnectionIdAndEntityId,
getEntityConnection,
getEntityConnectionByWorkspaceIdAndConnectionId,
getEntityConnectionByWorkspaceIdAndConnectionIdAndEntityId,
updateEntityConnection,
updateEntityConnectionByWorkspaceConnectionIdAndEntityId,
} from "@/db/query/connection";
@@ -128,6 +131,80 @@ export class EntityConnectionController {
}
}
@Get("/:id")
async getEntityConnectionByConnectionId(req: Request, res: Response) {
try {
const { id } = req.params;
if (!id) {
return res.status(400).send({
message: "Bad Request, expected id to be present.",
});
}
const entityConnections = await getEntityConnection(id);
return res.status(200).send(entityConnections);
} catch (error) {
console.error(error);
return res.status(500).send("Internal Server Error");
}
}
@Put("/:id")
async updateEntityConnectionById(req: Request, res: Response) {
try {
const { id } = req.params;
if (!id) {
return res.status(400).send({
message: "Bad Request, expected id to be present.",
});
}
const payload = {
workspaceSlug: req.body.workspaceSlug,
projectId: req.body.projectId,
entityId: req.body.entityId,
entitySlug: req.body.entitySlug,
entityData: req.body.entityData,
config: req.body.config,
};
const entityConnections = await updateEntityConnection(
id,
payload
);
return res.status(200).send(entityConnections);
} catch (error) {
console.error(error);
return res.status(500).send("Internal Server Error");
}
}
@Delete("/:id")
async deleteEntityConnectionById(req: Request, res: Response) {
try {
const { id } = req.params;
if (!id) {
return res.status(400).send({
message: "Bad Request, expected id to be present.",
});
}
const entityConnections = await deleteEntityConnection(id);
return res.status(200).send(entityConnections);
} catch (error) {
console.error(error);
return res.status(500).send("Internal Server Error");
}
}
@Delete("/:workspaceId/:workspaceConnectionId/:entityId")
async deleteEntityConnection(req: Request, res: Response) {
try {

View File

@@ -80,14 +80,14 @@ export const syncCommentWithPlane = async (
workspaceConnection.config.userMap.map((obj) => [obj.githubUser.login, obj.planeUser.id])
);
const planeUsers = await planeClient.users.list(entityConnection.workspaceSlug, entityConnection.projectId);
const planeUsers = await planeClient.users.list(entityConnection.workspaceSlug, entityConnection.projectId ?? "");
let comment: ExIssueComment | null = null;
try {
comment = await planeClient.issueComment.getIssueCommentWithExternalId(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
issue.id,
data.comment.id.toString(),
"GITHUB"
@@ -101,7 +101,7 @@ export const syncCommentWithPlane = async (
issue.id,
data.repository.full_name,
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
planeClient,
ghService,
userMap,
@@ -112,7 +112,7 @@ export const syncCommentWithPlane = async (
if (comment) {
await planeClient.issueComment.update(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
issue.id,
comment.id,
planeComment
@@ -121,7 +121,7 @@ export const syncCommentWithPlane = async (
} else {
const createdComment = await planeClient.issueComment.create(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
issue.id,
planeComment
);
@@ -137,7 +137,7 @@ const getPlaneIssue = async (planeClient: PlaneClient, entityConnection: GithubE
try {
return await planeClient.issue.getIssueWithExternalId(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
issueId.toString(),
"GITHUB"
);

View File

@@ -83,13 +83,13 @@ export const syncIssueWithPlane = async (store: Store, data: GithubIssueDedupPay
try {
issue = await planeClient.issue.getIssueWithExternalId(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
data.issueNumber.toString(),
"GITHUB"
);
} catch (error) { }
const planeUsers = await planeClient.users.list(entityConnection.workspaceSlug, entityConnection.projectId);
const planeUsers = await planeClient.users.list(entityConnection.workspaceSlug, entityConnection.projectId ?? "");
const userMap: Record<string, string> = Object.fromEntries(
workspaceConnection.config.userMap.map((obj) => [obj.githubUser.login, obj.planeUser.id])
@@ -103,14 +103,14 @@ export const syncIssueWithPlane = async (store: Store, data: GithubIssueDedupPay
data.repositoryName,
userMap,
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
planeUsers,
ghService,
issue ? true : false
);
const states = (await planeClient.state.list(entityConnection.workspaceSlug, entityConnection.projectId)).results;
const users = await planeClient.users.list(entityConnection.workspaceSlug, entityConnection.projectId);
const states = (await planeClient.state.list(entityConnection.workspaceSlug, entityConnection.projectId ?? "")).results;
const users = await planeClient.users.list(entityConnection.workspaceSlug, entityConnection.projectId ?? "");
if (planeIssue.state) {
const state = states.find((s) => s.name === planeIssue.state);
@@ -120,7 +120,7 @@ export const syncIssueWithPlane = async (store: Store, data: GithubIssueDedupPay
}
if (planeIssue.labels) {
const labels = (await planeClient.label.list(entityConnection.workspaceSlug, entityConnection.projectId)).results;
const labels = (await planeClient.label.list(entityConnection.workspaceSlug, entityConnection.projectId ?? "")).results;
const githubLabel = labels.find((l) => l.name.toLowerCase() === "github");
if (githubLabel) {
@@ -137,7 +137,7 @@ export const syncIssueWithPlane = async (store: Store, data: GithubIssueDedupPay
const labelPromises = labelsToCreate.map(async (label: any) => {
const createdLabel = await planeClient.label.create(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
{
name: label.name,
color: `#${label.color}`,
@@ -182,12 +182,12 @@ export const syncIssueWithPlane = async (store: Store, data: GithubIssueDedupPay
}
if (issue) {
await planeClient.issue.update(entityConnection.workspaceSlug, entityConnection.projectId, issue.id, planeIssue);
await planeClient.issue.update(entityConnection.workspaceSlug, entityConnection.projectId ?? "", issue.id, planeIssue);
await store.set(`silo:issue:${issue.id}`, "true");
} else {
const createdIssue = await planeClient.issue.create(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
planeIssue
);
@@ -195,12 +195,12 @@ export const syncIssueWithPlane = async (store: Store, data: GithubIssueDedupPay
const createLink = async () => {
const linkTitle = `[${entityConnection.entitySlug}] ${ghIssue?.data.title} #${ghIssue?.data.number}`;
const linkUrl = ghIssue?.data.html_url;
await planeClient.issue.createLink(entityConnection.workspaceSlug, entityConnection.projectId, createdIssue.id, linkTitle, linkUrl);
await planeClient.issue.createLink(entityConnection.workspaceSlug, entityConnection.projectId ?? "", createdIssue.id, linkTitle, linkUrl);
}
const createLinkBack = async () => {
// Get the project for the issue
const project = await planeClient.project.getProject(entityConnection.workspaceSlug, entityConnection.projectId);
const project = await planeClient.project.getProject(entityConnection.workspaceSlug, entityConnection.projectId ?? "");
const comment = `Synced Issue with [Plane](${env.APP_BASE_URL}) Workspace 🔄\n\n[${project.identifier}-${createdIssue.sequence_id} ${createdIssue.name}](${env.APP_BASE_URL}/${entityConnection.workspaceSlug}/projects/${entityConnection.projectId}/issues/${createdIssue.id})`;
await ghService.createIssueComment(data.owner, data.repositoryName, Number(data.issueNumber), comment);
}

View File

@@ -59,7 +59,7 @@ const handlePullRequestOpened = async (data: GithubWebhookPayload["webhook-pull-
try {
const issue = await planeClient.issue.getIssueWithExternalId(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
reference.node.databaseId.toString(),
"GITHUB"
);
@@ -68,7 +68,7 @@ const handlePullRequestOpened = async (data: GithubWebhookPayload["webhook-pull-
// Create a link in the issue for the pull request
await planeClient.issue.createLink(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
issue.id,
`GitHub PR: ${data.pull_request.title}`,
data.pull_request.html_url

View File

@@ -50,7 +50,7 @@ const handleCommentSync = async (store: Store, payload: PlaneWebhookPayload) =>
// Get the issue associated with the comment
const issue = await planeClient.issue.getIssue(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
payload.issue
);
@@ -66,7 +66,7 @@ const handleCommentSync = async (store: Store, payload: PlaneWebhookPayload) =>
const comment = await planeClient.issueComment.getComment(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
payload.issue,
payload.id
);
@@ -88,7 +88,7 @@ const handleCommentSync = async (store: Store, payload: PlaneWebhookPayload) =>
) {
await planeClient.issueComment.update(
entityConnection.workspaceSlug,
entityConnection.projectId,
entityConnection.projectId ?? "",
payload.issue,
payload.id,
{
@@ -110,8 +110,8 @@ const createOrUpdateGitHubComment = async (
entityConnection: GithubEntityConnection,
credentials: TServiceCredentials
) => {
const owner = entityConnection.entitySlug.split("/")[0];
const repo = entityConnection.entitySlug.split("/")[1];
const owner = (entityConnection.entitySlug ?? "").split("/")[0];
const repo = (entityConnection.entitySlug ?? "").split("/")[1];
const assetImagePrefix = imagePrefix + workspaceConnection.workspaceId + "/" + credentials.user_id;

View File

@@ -58,7 +58,7 @@ const handleIssueSync = async (store: Store, payload: PlaneWebhookPayload) => {
const issue = await planeClient.issue.getIssue(entityConnection.workspaceSlug, payload.project, payload.id);
const labels = await planeClient.label.list(entityConnection.workspaceSlug, entityConnection.projectId);
const labels = await planeClient.label.list(entityConnection.workspaceSlug, entityConnection.projectId ?? "");
const githubIssue = await createOrUpdateGitHubIssue(
githubService,
planeClient,
@@ -73,7 +73,7 @@ const handleIssueSync = async (store: Store, payload: PlaneWebhookPayload) => {
if (!issue.external_id || !issue.external_source || (issue.external_source && issue.external_source !== "GITHUB")) {
// Add the external id and source
const addExternalId = async () => {
await planeClient.issue.update(entityConnection.workspaceSlug, entityConnection.projectId, payload.id, {
await planeClient.issue.update(entityConnection.workspaceSlug, entityConnection.projectId ?? "", payload.id, {
external_id: githubIssue?.data.number.toString(),
external_source: "GITHUB",
});
@@ -83,14 +83,8 @@ const handleIssueSync = async (store: Store, payload: PlaneWebhookPayload) => {
const createLink = async () => {
const linkTitle = `[${entityConnection.entitySlug}] ${githubIssue?.data.title} #${githubIssue?.data.number}`;
const linkUrl = githubIssue?.data.html_url;
await planeClient.issue.createLink(
entityConnection.workspaceSlug,
entityConnection.projectId,
issue.id,
linkTitle,
linkUrl
);
};
await planeClient.issue.createLink(entityConnection.workspaceSlug, entityConnection.projectId ?? "", issue.id, linkTitle, linkUrl);
}
// Execute all the promises
await Promise.all([addExternalId(), createLink()]);
@@ -118,9 +112,9 @@ const createOrUpdateGitHubIssue = async (
workspaceConnection.config.userMap.map((obj) => [obj.planeUser.id, obj.githubUser])
);
const owner = entityConnection.entitySlug.split("/")[0];
const repo = entityConnection.entitySlug.split("/")[1];
const issueImagePrefix = imagePrefix + workspaceConnection.workspaceId + "/" + credentials.user_id;
const owner = (entityConnection.entitySlug ?? "").split("/")[0];
const repo = (entityConnection.entitySlug ?? "").split("/")[1];
const issueImagePrefix = imagePrefix + workspaceConnection.workspaceId + "/" + credentials.user_id
const transformedGithubIssue = await transformPlaneIssue(issue, issueImagePrefix, labels, owner, repo, userMap);
@@ -146,7 +140,7 @@ const createOrUpdateGitHubIssue = async (
} else {
const createdIssue = await githubUserService.createIssue(transformedGithubIssue as GithubIssue);
const project = await planeClient.project.getProject(entityConnection.workspaceSlug, entityConnection.projectId);
const project = await planeClient.project.getProject(entityConnection.workspaceSlug, entityConnection.projectId ?? "");
const comment = `Synced Issue with [Plane](${env.APP_BASE_URL}) Workspace 🔄\n\n[${project.identifier}-${issue.sequence_id} ${issue.name}](${env.APP_BASE_URL}/${entityConnection.workspaceSlug}/projects/${entityConnection.projectId}/issues/${issue.id})`;
await githubService.createIssueComment(owner, repo, Number(createdIssue.data.number), comment);

View File

@@ -1,9 +1,9 @@
import { importTaskManger, integrationTaskManager } from "@/apps/engine/worker";
import { env } from "@/env";
import { Controller, Get, Post } from "@/lib";
import { Controller, Delete, Get, Post } from "@/lib";
import { ExIssueLabel } from "@plane/sdk";
import { Request, Response } from "express";
import { createGitLabAuth, createGitLabService, GitlabWebhookEvent } from "@plane/etl/gitlab";
import { createGitLabAuth, createGitLabService, GitlabWebhookEvent, GitlabWebhook, GitlabEntityType, IGitlabEntity, GitlabEntityData, EConnectionType } from "@plane/etl/gitlab";
import { verifyGitlabToken } from "../helpers";
import { createOrUpdateCredentials, getCredentialsByOnlyWorkspaceId, deleteCredentialsForWorkspace } from "@/db/query";
import {
@@ -12,8 +12,18 @@ import {
updateWorkspaceConnection,
deleteEntityConnectionByWorkspaceConnectionId,
deleteWorkspaceConnection,
createEntityConnectionByWorkspaceConnectionId,
getEntityConnectionByWorkspaceIdAndConnectionIdAndEntityId,
getEntityConnectionByEntityId,
getEntityConnectionByWorkspaceIdAndConnectionId,
deleteEntityConnection,
getEntityConnection,
getEntityConnectionByWorkspaceAndProjectId,
getEntityConnectionByWorkspaceConnectionAndProjectId,
} from "@/db/query/connection";
import { logger } from "@/logger";
import { EIntegrationType } from "@/types";
import { gitlabAuthService, getGitlabClientService } from "../services";
@Controller("/api/gitlab")
export class GitlabController {
@@ -168,7 +178,7 @@ export class GitlabController {
});
}
res.send({ message: "Successfully connected to GitLab" });
res.redirect(`${env.APP_BASE_URL}/${authState.workspace_slug}/settings/integrations/gitlab/`);
}
@Post("/gitlab-webhook")
@@ -243,4 +253,231 @@ export class GitlabController {
Number(env.DEDUP_INTERVAL)
);
}
@Post("/entity-connections/:workspaceId/:workspaceConnectionId")
async addEntityConnection(req: Request, res: Response, next: any) {
try {
const { workspaceId, workspaceConnectionId } = req.params;
const { entityId, entityType, workspaceSlug, entitySlug } = req.body;
if (!workspaceId || !workspaceConnectionId || !entityId || !entityType || !workspaceSlug || !entitySlug) {
return res.status(400).send({
message: "Bad Request, expected workspaceId, workspaceConnectionId, and entityId to be present.",
});
}
// Check for existing connections
const connections = await getEntityConnectionByWorkspaceIdAndConnectionIdAndEntityId(
workspaceId,
workspaceConnectionId,
entityId
);
if (connections.length > 0) {
return res.status(201).json({ error: "Entity connection already exists" });
}
// Add webhook to gitlab
const { url, token } = this.getWorkspaceWebhookData(workspaceId)
const gitlabClientService = await getGitlabClientService(workspaceId)
// based on enum either add to project or group
let webhookId;
console.log("inside add webhook to project", entityType, entityId, url, token)
if (entityType === GitlabEntityType.PROJECT) {
const { id: hookId } = await gitlabClientService.addWebhookToProject(entityId, url, token);
webhookId = hookId;
} else {
const { id: hookId } = await gitlabClientService.addWebhookToGroup(entityId, url, token);
webhookId = hookId;
}
const connection = await createEntityConnectionByWorkspaceConnectionId(
workspaceId,
workspaceConnectionId,
{
workspaceId,
workspaceConnectionId,
connectionType: EConnectionType.ENTITY,
workspaceSlug,
entityId,
entitySlug,
entityData: {
id: entityId,
type: entityType,
webhookId
}
}
);
res.status(200).json(connection);
} catch (error) {
next(error);
}
}
@Post("/entity-project-connections/:workspaceId/:workspaceConnectionId")
async addProjectConnection(req: Request, res: Response, next: any) {
try {
const { workspaceId, workspaceConnectionId } = req.params;
const { projectId, workspaceSlug, config } = req.body;
if (!workspaceId || !workspaceConnectionId || !projectId || !workspaceSlug) {
return res.status(400).send({
message: "Bad Request, expected workspaceId, workspaceConnectionId, and projectId to be present.",
});
}
// Check for existing connections
const connections = await getEntityConnectionByWorkspaceConnectionAndProjectId(
workspaceId,
projectId
);
if (connections.length > 0) {
return res.status(201).json({ error: "Entity connection already exists" });
}
const connection = await createEntityConnectionByWorkspaceConnectionId(
workspaceId,
workspaceConnectionId,
{
workspaceId,
workspaceConnectionId,
connectionType: EConnectionType.PLANE_PROJECT,
projectId,
workspaceSlug,
config
}
);
res.status(200).json(connection);
} catch (error) {
next(error);
}
}
@Delete("/entity-connections/:workspaceId/:connectionId")
async removeEntityConnection(req: Request, res: Response, next: any) {
try {
const { workspaceId, connectionId } = req.params;
const [entityConnection] = await getEntityConnection(connectionId);
if (!entityConnection) {
return res.status(400).json({ error: "Entity connection not found" });
}
const gitlabClientService = await getGitlabClientService(workspaceId)
const entityData = entityConnection.entityData as GitlabEntityData
if (entityData.type === GitlabEntityType.PROJECT) {
await gitlabClientService.removeWebhookFromProject(entityData.id, entityData.webhookId);
} else if (entityData.type === GitlabEntityType.GROUP) {
await gitlabClientService.removeWebhookFromGroup(entityData.id, entityData.webhookId);
}
const connection = await deleteEntityConnection(connectionId);
res.status(200).json(connection);
} catch (error) {
next(error);
}
}
@Post("/webhook/:workspaceId")
async webhook(req: Request, res: Response, next: any) {
try {
const workspaceId = req.params.workspaceId;
const webhookSecret = req.headers["x-gitlab-token"]?.toString();
if (!gitlabAuthService.verifyGitlabWebhookSecret(workspaceId, webhookSecret ?? "")) {
return res.status(400).send({
message: "Webhook received",
});
} else {
res.status(202).send({
message: "Webhook received",
});
const webhookEvent = req.body as GitlabWebhookEvent;
// Generate a unique job ID
const jobId = `gitlab-${webhookEvent.object_kind}-${Date.now()}`;
// Forward the event to the task manager to process
await integrationTaskManager.registerTask(
{
route: "gitlab-webhook",
jobId: jobId,
type: webhookEvent.object_kind,
},
webhookEvent
);
}
} catch (error) {
next(error)
}
}
@Get("/entity-connections/:workspaceId")
async getAllEntityConnections(req: Request, res: Response, next: any) {
try {
const workspaceId = req.params.workspaceId;
const [workspaceConnections] = await getWorkspaceConnections(workspaceId, EIntegrationType.GITLAB);
const entityConnections = await getEntityConnectionByWorkspaceIdAndConnectionId(workspaceId, workspaceConnections.id);
res.status(200).json(entityConnections);
} catch (error) {
console.error(error)
next(error)
}
}
@Get("/entities/:workspaceId")
async getProjectAndGroups(req: Request, res: Response, next: any) {
try {
const workspaceId = req.params.workspaceId;
const entities = [];
const gitlabClientService = await getGitlabClientService(workspaceId)
const [projects, groups] = await Promise.all([
gitlabClientService.getProjects(),
gitlabClientService.getGroups(),
]);
if (projects.length) {
entities.push(...projects.map((project: IGitlabEntity) => ({ id: project.id, name: project.name, type: GitlabEntityType.PROJECT })));
}
if (groups.length) {
entities.push(...groups.map((group: IGitlabEntity) => ({ id: group.id, name: group.name, type: GitlabEntityType.GROUP })));
}
res.status(200).json(entities);
} catch (error) {
console.error(error)
next(error)
}
}
getWorkspaceWebhookData(workspaceId: string) {
try {
if (!workspaceId) {
throw new Error("workspaceId is not defined");
}
const workspaceWebhookSecret = gitlabAuthService.getWorkspaceWebhookSecret(workspaceId);
const webhookURL = `${env.SILO_API_BASE_URL}/silo/api/gitlab/webhook/${workspaceId}`;
const gitlabWebhook: GitlabWebhook = {
url: webhookURL,
token: workspaceWebhookSecret,
};
return gitlabWebhook;
} catch (error) {
console.error("error getWorkspaceWebhook", error);
throw error;
}
}
}

View File

@@ -1,20 +1,28 @@
import {
EConnectionType,
gitlabEntityConnectionSchema,
GitlabMergeRequestEvent,
gitlabWorkspaceConnectionSchema,
MergeRequestEvent,
} from "@plane/etl/gitlab";
import { GitlabConnectionDetails } from "../types";
import { getEntityConnectionByEntityId, getWorkspaceConnectionById } from "@/db/query/connection";
import { GitlabConnectionDetails, GitlabEntityConnection } from "../types";
import { getAllEntityConnectionsByEntityIds, getEntityConnectionByEntityId, getEntityConnectionByWorkspaceConnectionAndProjectId, getWorkspaceConnectionById } from "@/db/query/connection";
import { logger } from "@/logger";
import { verifyEntityConnection, verifyWorkspaceConnection } from "@/types";
import { verifyEntityConnection, verifyEntityConnections, verifyWorkspaceConnection } from "@/types";
export const getGitlabConnectionDetails = async (
data: GitlabMergeRequestEvent
): Promise<GitlabConnectionDetails | undefined> => {
const entityConnectionSet = await getEntityConnectionByEntityId(data.project.id.toString());
// for connection now user can also just have a group connection
// project payload has array of groups attached to it so we need to check
// if we have any group connections among them and use that or not then check for project connection
const projectRelatedGroups = data.project.shared_with_groups.map((group) => group.group_id?.toString());
const entityConnectionIds = projectRelatedGroups.concat(data.project.id.toString());
const entityConnectionSet = await getAllEntityConnectionsByEntityIds(entityConnectionIds);
if (!entityConnectionSet || entityConnectionSet.length === 0) {
logger.error(`[GITLAB] Entity connection not found for project ${data.project.id}, skipping...`);
logger.error(`[GITLAB] Entity connection not found for project ${entityConnectionIds}, skipping...`);
return;
}
@@ -30,6 +38,15 @@ export const getGitlabConnectionDetails = async (
const workspaceConnection = workspaceConnectionSet[0];
// project connections for this workspace connection for target state mapping
let projectConnectionSet = await getEntityConnectionByWorkspaceConnectionAndProjectId(workspaceConnection.id);
projectConnectionSet = projectConnectionSet.filter((connection) => connection.connectionType === EConnectionType.PLANE_PROJECT);
if (projectConnectionSet.length === 0) {
logger.error(`[GITLAB] Plane Project connection not found for project ${data.project.id}, skipping...`);
return;
}
const verifiedWorkspaceConnection = verifyWorkspaceConnection(
gitlabWorkspaceConnectionSchema,
workspaceConnection as any
@@ -37,8 +54,11 @@ export const getGitlabConnectionDetails = async (
const verifiedEntityConnection = verifyEntityConnection(gitlabEntityConnectionSchema, entityConnection as any);
const verifiedProjectConnection = verifyEntityConnections(gitlabEntityConnectionSchema, projectConnectionSet as any);
return {
workspaceConnection: verifiedWorkspaceConnection,
entityConnection: verifiedEntityConnection,
projectConnections: verifiedProjectConnection,
};
};

View File

@@ -0,0 +1,8 @@
import { env } from "@/env";
import { createGitLabAuth } from "@plane/etl/gitlab";
export const gitlabAuthService = createGitLabAuth({
clientId: env.GITLAB_CLIENT_ID,
clientSecret: env.GITLAB_CLIENT_SECRET,
redirectUri: `${env.SILO_API_BASE_URL}/silo/api/gitlab/auth/callback`,
});

View File

@@ -0,0 +1,2 @@
export * from "./auth";
export * from "./service"

View File

@@ -0,0 +1,32 @@
import { createOrUpdateCredentials, getCredentialsByOnlyWorkspaceId, getCredentialsByWorkspaceId } from "@/db/query";
import { createGitLabService } from "@plane/etl/gitlab";
export const getGitlabClientService = async (workspaceId: string, ) => {
try {
// Create or update credentials
const credentials = await getCredentialsByOnlyWorkspaceId(workspaceId, "GITLAB",);
const { source_access_token, source_refresh_token, target_access_token, user_id: userId } = credentials[0];
if (!source_access_token || !source_refresh_token || !userId || !target_access_token) {
throw new Error("No gitlab credentials available for the given workspaceId and userId");
}
const gitlabService = createGitLabService(
source_access_token,
source_refresh_token,
async (access_token, refresh_token) => {
await createOrUpdateCredentials(workspaceId, userId, {
source_access_token: access_token,
source_refresh_token: refresh_token,
target_access_token: target_access_token,
source: "GITLAB",
});
}
);
return gitlabService;
} catch (error) {
console.error(error);
throw error;
}
}

View File

@@ -7,4 +7,5 @@ export type GitlabEntityConnection = EntityConnection<typeof gitlabEntityConnect
export type GitlabConnectionDetails = {
workspaceConnection: GitlabWorkspaceConnection;
entityConnection: GitlabEntityConnection;
projectConnections: GitlabEntityConnection[];
};

View File

@@ -122,7 +122,7 @@ export const handleMergeRequest = async (data: GitlabMergeRequestEvent) => {
const result = await getConnectionAndCredentials(data);
if (!result) return;
const [{ workspaceConnection, entityConnection }, credentials] = result;
const [{ workspaceConnection, entityConnection, projectConnections }, credentials] = result;
const { closingReferences, nonClosingReferences } = getReferredIssues(
data.object_attributes.title,
@@ -133,9 +133,6 @@ export const handleMergeRequest = async (data: GitlabMergeRequestEvent) => {
return;
}
const targetState = getTargetState(data, entityConnection);
if (!targetState) return;
const referredIssues = ["MR_CLOSED", "MR_MERGED"].includes(classifyMergeRequestEvent(data))
? closingReferences
: [...closingReferences, ...nonClosingReferences];
@@ -162,10 +159,31 @@ export const handleMergeRequest = async (data: GitlabMergeRequestEvent) => {
workspaceConnection.sourceHostname!
);
// we need to get the plane project attached to referred issues and then get target state for each and then do the updates
// get the exissues from identifiers it'll have the project attached
// loop through the referred issues, check if it has a plane project attached and then update the state using project connection target state
const allReferredIssues = await Promise.all(
referredIssues.map(async (reference) => {
const issue = await planeClient.issue.getIssueByIdentifier(
entityConnection.workspaceSlug,
reference.identifier,
reference.sequence
);
return { reference, issue };
})
);
const updatedIssues = await Promise.all(
referredIssues.map((reference) =>
updateIssue(planeClient, entityConnection, reference, targetState, data.project.id)
)
allReferredIssues.map(async (referredIssue) => {
const targetProject = projectConnections.find((project) => project.projectId === referredIssue.issue.project);
if (targetProject) {
const targetState = getTargetState(data, targetProject);
if (!targetState) return null;
return updateIssue(planeClient, entityConnection, referredIssue.reference, targetState, data.project.id);
}
return null;
})
);
const validIssues = updatedIssues.filter((issue): issue is IssueWithReference => issue !== null);

View File

@@ -32,13 +32,13 @@ export const handleMessageEvent = async (data: SlackEventPayload) => {
const eConnection = entityConnection[0];
const members = await planeClient.users.list(workspaceConnection.workspaceSlug, eConnection.projectId);
const members = await planeClient.users.list(workspaceConnection.workspaceSlug, eConnection.projectId ?? "");
const userInfo = await slackService.getUserInfo(data.event.user);
const issueId = eConnection.entitySlug;
const planeUser = members.find((member) => member.email === userInfo?.user.profile.email);
await planeClient.issueComment.create(workspaceConnection.workspaceSlug, eConnection.projectId, issueId, {
await planeClient.issueComment.create(workspaceConnection.workspaceSlug, eConnection.projectId ?? "", issueId ?? "", {
comment_html: `<p>${data.event.text}</p>`,
external_source: "SLACK_COMMENT",
external_id: data.event.event_ts,

View File

@@ -26,12 +26,12 @@ const handleCommentSync = async (payload: WebhookIssueCommentPayload) => {
if (credentials && credentials.length > 0 && credentials[0].source_access_token) {
await slackService.sendMessageAsUser(
slackData.channel,
entityConnection[0].entityId,
entityConnection[0].entityId ?? "",
data.data.comment_stripped,
credentials[0].source_access_token
);
} else {
await slackService.sendThreadMessage(slackData.channel, entityConnection[0].entityId, data.data.comment_stripped);
await slackService.sendThreadMessage(slackData.channel, entityConnection[0].entityId ?? "", data.data.comment_stripped);
}
}
};

View File

@@ -0,0 +1,4 @@
ALTER TABLE "silo"."entity_connections" ALTER COLUMN "entity_id" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "silo"."entity_connections" ALTER COLUMN "entity_slug" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "silo"."entity_connections" ALTER COLUMN "project_id" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "silo"."entity_connections" ADD COLUMN "connection_type" varchar(50) DEFAULT 'ENTITY';

View File

@@ -0,0 +1,537 @@
{
"id": "8db1d4b5-7542-4785-85fe-ddfa487cdc87",
"prevId": "1bb1de16-c54b-43a3-874e-dc5d0a9ced7c",
"version": "7",
"dialect": "postgresql",
"tables": {
"silo.entity_connections": {
"name": "entity_connections",
"schema": "silo",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"connection_type": {
"name": "connection_type",
"type": "varchar(50)",
"primaryKey": false,
"notNull": false,
"default": "'ENTITY'"
},
"entity_id": {
"name": "entity_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"entity_slug": {
"name": "entity_slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"entity_data": {
"name": "entity_data",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'{}'::jsonb"
},
"project_id": {
"name": "project_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workspace_id": {
"name": "workspace_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workspace_slug": {
"name": "workspace_slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"workspace_connection_id": {
"name": "workspace_connection_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"config": {
"name": "config",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'{}'::jsonb"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"entity_connections_workspace_id_idx": {
"name": "entity_connections_workspace_id_idx",
"columns": [
{
"expression": "workspace_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"entity_connections_workspace_connection_id_workspace_connections_id_fk": {
"name": "entity_connections_workspace_connection_id_workspace_connections_id_fk",
"tableFrom": "entity_connections",
"tableTo": "workspace_connections",
"schemaTo": "silo",
"columnsFrom": [
"workspace_connection_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"silo.workspace_connections": {
"name": "workspace_connections",
"schema": "silo",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workspace_id": {
"name": "workspace_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workspace_slug": {
"name": "workspace_slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"target_hostname": {
"name": "target_hostname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"source_hostname": {
"name": "source_hostname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"connection_type": {
"name": "connection_type",
"type": "varchar(50)",
"primaryKey": false,
"notNull": true
},
"connection_slug": {
"name": "connection_slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"connection_id": {
"name": "connection_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"connection_data": {
"name": "connection_data",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'{}'::jsonb"
},
"credentials_id": {
"name": "credentials_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"config": {
"name": "config",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'{}'::jsonb"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"workspace_connections_workspace_id_idx": {
"name": "workspace_connections_workspace_id_idx",
"columns": [
{
"expression": "workspace_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"workspace_connections_credentials_id_credentials_id_fk": {
"name": "workspace_connections_credentials_id_credentials_id_fk",
"tableFrom": "workspace_connections",
"tableTo": "credentials",
"schemaTo": "silo",
"columnsFrom": [
"credentials_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"silo.credentials": {
"name": "credentials",
"schema": "silo",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"workspace_id": {
"name": "workspace_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"user_email": {
"name": "user_email",
"type": "text",
"primaryKey": false,
"notNull": false
},
"source_access_token": {
"name": "source_access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"source_refresh_token": {
"name": "source_refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"source_hostname": {
"name": "source_hostname",
"type": "text",
"primaryKey": false,
"notNull": false
},
"target_access_token": {
"name": "target_access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"is_pat": {
"name": "is_pat",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"is_active": {
"name": "is_active",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
}
},
"indexes": {
"workspace_id_idx": {
"name": "workspace_id_idx",
"columns": [
{
"expression": "workspace_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"silo.job_configs": {
"name": "job_configs",
"schema": "silo",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"meta": {
"name": "meta",
"type": "json",
"primaryKey": false,
"notNull": false,
"default": "'{}'::json"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"silo.jobs": {
"name": "jobs",
"schema": "silo",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"config_id": {
"name": "config_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"migration_type": {
"name": "migration_type",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"project_id": {
"name": "project_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workspace_id": {
"name": "workspace_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workspace_slug": {
"name": "workspace_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"initiator_id": {
"name": "initiator_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"initiator_email": {
"name": "initiator_email",
"type": "text",
"primaryKey": false,
"notNull": false
},
"completed_batch_count": {
"name": "completed_batch_count",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"transformed_batch_count": {
"name": "transformed_batch_count",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"total_batch_count": {
"name": "total_batch_count",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"start_time": {
"name": "start_time",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"end_time": {
"name": "end_time",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "varchar",
"primaryKey": false,
"notNull": false,
"default": "'CREATED'"
},
"is_cancelled": {
"name": "is_cancelled",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"error": {
"name": "error",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
}
},
"indexes": {
"project_idx": {
"name": "project_idx",
"columns": [
{
"expression": "project_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"jobs_config_id_job_configs_id_fk": {
"name": "jobs_config_id_job_configs_id_fk",
"tableFrom": "jobs",
"tableTo": "job_configs",
"schemaTo": "silo",
"columnsFrom": [
"config_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {
"silo": "silo"
},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -22,6 +22,13 @@
"when": 1734517027583,
"tag": "0002_lazy_spyke",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1735053738861,
"tag": "0003_typical_eternity",
"breakpoints": true
}
]
}

View File

@@ -1,4 +1,4 @@
import { SQL, and, eq } from "drizzle-orm";
import { SQL, and, eq, inArray } from "drizzle-orm";
import { db } from "../config/db.config";
import { entityConnections, workspaceConnections } from "../schema/connection.schema";
@@ -62,6 +62,13 @@ export async function createEntityConnection(data: typeof entityConnections.$inf
return await db.insert(entityConnections).values(data).returning();
}
export async function getAllEntityConnectionsByEntityIds(entityIds: string[]) {
return await db
.select()
.from(entityConnections)
.where(inArray(entityConnections.entityId, entityIds));
}
export async function getEntityConnectionByWorkspaceId(workspaceId: string) {
return await db.select().from(entityConnections).where(eq(entityConnections.workspaceId, workspaceId));
}
@@ -87,6 +94,19 @@ export async function getEntityConnectionByWorkspaceAndProjectId(workspaceId: st
.where(and(...conditions));
}
export async function getEntityConnectionByWorkspaceConnectionAndProjectId(workspaceConnectionId: string, projectId?: string) {
const conditions: SQL[] = [eq(entityConnections.workspaceConnectionId, workspaceConnectionId)];
if (projectId) {
conditions.push(eq(entityConnections.projectId, projectId));
}
return await db
.select()
.from(entityConnections)
.where(and(...conditions));
}
export async function getEntityConnectionByEntitySlug(workspaceId: string, projectId: string, entitySlug: string) {
return await db
.select()
@@ -134,16 +154,21 @@ export async function deleteEntityConnectionByWorkspaceConnectionId(workspaceCon
// Get entity connection by workspaceId, and workspaceConnectionId
export async function getEntityConnectionByWorkspaceIdAndConnectionId(
workspaceId: string,
workspaceConnectionId: string
workspaceConnectionId: string,
connectionType?: string
) {
const conditions: SQL[] = [eq(entityConnections.workspaceId, workspaceId),
eq(entityConnections.workspaceConnectionId, workspaceConnectionId)];
if (connectionType) {
conditions.push(eq(entityConnections.connectionType, connectionType));
}
return await db
.select()
.from(entityConnections)
.where(
and(
eq(entityConnections.workspaceId, workspaceId),
eq(entityConnections.workspaceConnectionId, workspaceConnectionId)
)
and(...conditions)
);
}

View File

@@ -51,13 +51,16 @@ export const entityConnections = schema.table(
{
id: uuid("id").defaultRandom().primaryKey(),
// connection type can be a plane project connection or a secondary entity connection or a mapping in both
connectionType: varchar("connection_type", { length: 50 }).default("ENTITY"),
// Entity Type can be a github repository, and entity id can be the repository id
entityId: varchar("entity_id", { length: 255 }).notNull(),
entitySlug: varchar("entity_slug", { length: 255 }).notNull(),
entityId: varchar("entity_id", { length: 255 }),
entitySlug: varchar("entity_slug", { length: 255 }),
entityData: jsonb("entity_data").default({}),
// Project and WorkspaceId of Plane
projectId: uuid("project_id").notNull(),
projectId: uuid("project_id"),
workspaceId: uuid("workspace_id").notNull(),
workspaceSlug: varchar("workspace_slug", { length: 255 }).notNull(),

View File

@@ -40,6 +40,18 @@ export function verifyEntityConnection<T extends z.ZodType>(schema: T, data: Ent
const parsedConfig = schema.parse(data.config);
return { ...data, config: parsedConfig };
}
export function verifyEntityConnections<T extends z.ZodType>(
schema: T,
dataArray: EntityConnection<T>[]
): EntityConnection<T>[] {
return dataArray.map((data) => {
const parsedConfig = schema.parse(data.config);
return { ...data, config: parsedConfig };
});
}
export type Credentials = typeof credentials.$inferInsert;
export type BulkIssuePayload = ExIssue & {
@@ -52,3 +64,10 @@ export type BulkIssuePayload = ExIssue & {
values: ExIssuePropertyValue;
}[];
};
export enum EIntegrationType {
GITLAB = "GITLAB",
GITHUB = "GITHUB",
SLACK = "SLACK",
}

View File

@@ -59,7 +59,7 @@ export function PersonalAccountConnectView(props: TPersonalAccountConnectProps)
return (
<div className="flex flex-col border border-custom-border-200 rounded-s p-4 mb-2 justify-center">
<div className="flex items-center gap-2">
<provider.icon className="w-8 h-8" />
{provider.icon && <provider.icon className="w-8 h-8" />}
<div className="text-lg font-medium">{provider.name}</div>
</div>
<div className="text-sm text-gray-500 pt-2 pb-4">{provider.description}</div>

View File

@@ -36,7 +36,13 @@ export const UserConnectionsView = observer(({ workspaceId }: { workspaceId: str
useEffect(() => {
if (selectedWorkspace && !isLoading) {
const response = getConnectionsByWorkspaceSlug(selectedWorkspace.slug);
let response = getConnectionsByWorkspaceSlug(selectedWorkspace.slug);
response = (response || []).reduce((allConnections: any, connection: any) => {
if (connection.connectionType !== "GITLAB") {
allConnections.push(connection);
}
return allConnections;
}, []);
setConnections(response);
}

View File

@@ -79,13 +79,10 @@ export const ConnectOrganization: FC = observer(() => {
<img
src={workspaceConnection?.connectionData?.avatar_url}
alt={workspaceConnection?.connectionData?.login}
className="object-contain w-full h-full overflow-hidden"
className="object-contain w-10 h-10 overflow-hidden rounded"
/>
</div>
<div className="space-y-0.5 w-full">
<div className="text-base font-medium">{workspaceConnection?.connectionData?.login}</div>
<div className="text-sm text-custom-text-200">Gitlab org added by and time</div>
</div>
<div className="text-sm text-custom-text-200 font-medium">{workspaceConnection?.connectionData?.organization || workspaceConnection?.connectionData?.name}</div>
</div>
) : (
<div className="space-y-0.5 w-full">

View File

@@ -1,11 +1,11 @@
"use client";
import { FC, useState } from "react";
import { Component, FC, ReactElement, useState } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import { IProject } from "@plane/types";
import { Button, ModalCore } from "@plane/ui";
// plane web components
import { Button, ModalCore } from "@plane/ui";
import { Logo } from "@/components/common";
import { FormEdit } from "@/plane-web/components/integrations/gitlab";
// plane web hooks
import { useGitlabIntegration } from "@/plane-web/hooks/store";
@@ -13,19 +13,20 @@ import { useGitlabIntegration } from "@/plane-web/hooks/store";
import { TGitlabEntityConnection } from "@/plane-web/types/integrations/gitlab";
// public images
import GitlabLogo from "@/public/services/gitlab.svg";
import { EConnectionType } from "@plane/etl/gitlab";
type TEntityConnectionItem = {
project: IProject;
entityConnection: TGitlabEntityConnection;
};
export const EntityConnectionItem: FC<TEntityConnectionItem> = observer((props) => {
// props
const { project, entityConnection } = props;
const { entityConnection } = props;
// hooks
const {
entity: { deleteEntity },
getProjectById,
entityConnection: { deleteEntityConnection },
} = useGitlabIntegration();
// states
@@ -43,7 +44,7 @@ export const EntityConnectionItem: FC<TEntityConnectionItem> = observer((props)
const handleDeleteModalSubmit = async () => {
try {
setDeleteLoader(true);
await deleteEntity(entityConnection.id);
await deleteEntityConnection(entityConnection.id);
setDeleteModal(false);
} catch (error) {
console.error("handleDeleteModalSubmit", error);
@@ -52,16 +53,35 @@ export const EntityConnectionItem: FC<TEntityConnectionItem> = observer((props)
}
};
return (
<>
{/* entity edit */}
const getEntityName = (entityconnection: TGitlabEntityConnection): string => {
if (entityconnection.connectionType === EConnectionType.PLANE_PROJECT) {
return getProjectById(entityconnection.projectId)?.name || "";
} else if (entityconnection.connectionType === EConnectionType.ENTITY) {
return entityconnection.entitySlug;
}
return "";
}
const getEntityLogo = (entityconnection: TGitlabEntityConnection): ReactElement => {
if (entityconnection.connectionType === EConnectionType.PLANE_PROJECT) {
const project = getProjectById(entityconnection.projectId);
if (!project) return <></>;
return <Logo logo={project.logo_props} size={14} />;
} else if (entityconnection.connectionType === EConnectionType.ENTITY) {
return <Image src={GitlabLogo} layout="fill" objectFit="contain" alt="Gitlab Logo" />;
}
return <></>;
};
return (
<>
{/* entity delete */}
<ModalCore isOpen={deleteModal} handleClose={handleDeleteClose}>
<div className="space-y-5 p-5">
<div className="space-y-2">
<div className="text-xl font-medium text-custom-text-200">Remove entity</div>
<div className="text-sm text-custom-text-300">Are you sure you want to remove this entity?</div>
<div className="text-xl font-medium text-custom-text-200">Remove Connection</div>
<div className="text-sm text-custom-text-300">Are you sure you want to remove this connection?</div>
</div>
<div className="relative flex justify-end items-center gap-2">
<Button variant="neutral-primary" size="sm" onClick={handleDeleteClose} disabled={deleteLoader}>
@@ -80,24 +100,24 @@ export const EntityConnectionItem: FC<TEntityConnectionItem> = observer((props)
</div>
</ModalCore>
{/* entity edit */}
<FormEdit modal={editModal} handleModal={setEditModal} data={entityConnection} />
<div className="relative flex items-center gap-2 p-2 bg-custom-background-90/20">
<div className="flex-shrink-0 relative flex justify-center items-center w-8 h-8 rounded">
<Image src={GitlabLogo} layout="fill" objectFit="contain" alt="Gitlab Logo" />
{getEntityLogo(entityConnection)}
</div>
<div className="w-full">
<div className="text-sm font-medium">
{entityConnection?.entityData?.name} ({entityConnection?.entityData?.full_name})
</div>
<div className="text-xs text-custom-text-200">
Issues are synced to <b>{project?.name || "Project"}</b>
{getEntityName(entityConnection)}
</div>
</div>
<div className="relative flex items-center gap-2">
<Button variant="neutral-primary" size="sm" onClick={handleEditOpen}>
Edit
</Button>
{entityConnection.connectionType === EConnectionType.PLANE_PROJECT && (
<Button variant="neutral-primary" size="sm" onClick={handleEditOpen}>
Edit
</Button>
)}
<Button variant="link-danger" size="sm" onClick={handleDeleteOpen}>
Remove
</Button>

View File

@@ -0,0 +1,84 @@
"use client";
import { Dispatch, FC, SetStateAction, useState } from "react";
import { observer } from "mobx-react";
import { Button, ModalCore } from "@plane/ui";
// plane web components
import { EntityForm } from "@/plane-web/components/integrations/gitlab";
// plane web hooks
import { useGitlabIntegration } from "@/plane-web/hooks/store";
// plane web types
import { TGitlabEntityConnection, TProjectMap } from "@/plane-web/types/integrations/gitlab";
// local imports
import { projectMapInit } from "../root";
type TEntityFormCreate = {
modal: boolean;
handleModal: Dispatch<SetStateAction<boolean>>;
};
export const EntityFormCreate: FC<TEntityFormCreate> = observer((props) => {
// props
const { modal, handleModal } = props;
// hooks
const {
entityConnection: { createEntityConnection },
} = useGitlabIntegration();
// states
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [projectMap, setProjectMap] = useState<TProjectMap>(projectMapInit);
// handlers
const handleProjectMapChange = <T extends keyof TProjectMap>(key: T, value: TProjectMap[T]) => {
setProjectMap((prev) => ({ ...prev, [key]: value }));
};
const handleSubmit = async () => {
try {
setIsSubmitting(true);
const payload: Partial<TGitlabEntityConnection> = {
entityId: projectMap.entityId,
projectId: projectMap.projectId,
};
await createEntityConnection(payload);
handleModal(false);
} catch (error) {
console.error("handleSubmit", error);
} finally {
setIsSubmitting(false);
}
};
return (
<ModalCore isOpen={modal} handleClose={() => handleModal(false)}>
<div className="space-y-5 p-5">
<div className="text-xl font-medium text-custom-text-200">Link Gitlab Project or Group</div>
<div className="space-y-4">
<EntityForm value={projectMap} handleChange={handleProjectMapChange} />
<div className="border border-custom-border-200 divide-y divide-custom-border-200 rounded">
<div className="relative space-y-1 p-3">
<div className="text-base">Pull request automation</div>
<div className="text-xs text-custom-text-200">
With Gitlab integration Enabled, you can automate issue workflows
</div>
</div>
</div>
<div className="relative flex justify-end items-center gap-2">
<Button variant="neutral-primary" size="sm" onClick={() => handleModal(false)}>
Cancel
</Button>
<Button variant="primary" size="sm" onClick={handleSubmit} loading={isSubmitting} disabled={isSubmitting}>
{isSubmitting ? "Processing" : "Continue"}
</Button>
</div>
</div>
</div>
</ModalCore>
);
});

View File

@@ -0,0 +1,117 @@
"use client";
import { Dispatch, FC, SetStateAction, useState } from "react";
import { observer } from "mobx-react";
import { Button, ModalCore } from "@plane/ui";
// plane web components
import { ProjectForm, StateForm } from "@/plane-web/components/integrations/gitlab";
// plane web hooks
import { useGitlabIntegration } from "@/plane-web/hooks/store";
// plane web types
import { TGitlabEntityConnection, TProjectMap, TStateMap } from "@/plane-web/types/integrations/gitlab";
// local imports
import { projectMapInit, stateMapInit } from "../root";
type TProjectEntityFormCreate = {
modal: boolean;
handleModal: Dispatch<SetStateAction<boolean>>;
};
export const ProjectEntityFormCreate: FC<TProjectEntityFormCreate> = observer((props) => {
// props
const { modal, handleModal } = props;
// hooks
const {
workspace,
fetchStates,
entityConnection: { createProjectConnection },
} = useGitlabIntegration();
// states
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [projectMap, setProjectMap] = useState<TProjectMap>(projectMapInit);
const [stateMap, setStateMap] = useState<TStateMap>(stateMapInit);
// derived values
const workspaceSlug = workspace?.slug || undefined;
// handlers
const handleProjectMapChange = <T extends keyof TProjectMap>(key: T, value: TProjectMap[T]) => {
if (key === "projectId") {
setProjectMap((prev) => ({ ...prev, [key]: value }));
if (workspaceSlug && value && value != projectMap.projectId) {
fetchStates(workspaceSlug, value);
setStateMap(stateMapInit);
}
} else {
setProjectMap((prev) => ({ ...prev, [key]: value }));
}
};
const handleStateMapChange = <T extends keyof TStateMap>(key: T, value: (typeof stateMap)[T]) => {
setStateMap((prev) => ({ ...prev, [key]: value }));
};
const handleSubmit = async () => {
try {
setIsSubmitting(true);
const payload: Partial<TGitlabEntityConnection> = {
entityId: projectMap.entityId,
projectId: projectMap.projectId,
config: {
states: { mergeRequestEventMapping: stateMap },
},
};
await createProjectConnection(payload);
handleModal(false);
} catch (error) {
console.error("handleSubmit", error);
} finally {
setIsSubmitting(false);
}
};
return (
<ModalCore isOpen={modal} handleClose={() => handleModal(false)}>
<div className="space-y-5 p-5">
<div className="text-xl font-medium text-custom-text-200">Link Gitlab repository and Plane project</div>
<div className="space-y-4">
<ProjectForm value={projectMap} handleChange={handleProjectMapChange} />
<div className="border border-custom-border-200 divide-y divide-custom-border-200 rounded">
<div className="relative space-y-1 p-3">
<div className="text-base">Pull request automation</div>
<div className="text-xs text-custom-text-200">
Configure pull request state mapping from Gitlab to Plane
</div>
</div>
{
projectMap.projectId && (
<div className="p-3">
<StateForm
projectId={projectMap.projectId}
value={stateMap}
handleChange={handleStateMapChange}
/>
</div>
)
}
</div>
<div className="relative flex justify-end items-center gap-2">
<Button variant="neutral-primary" size="sm" onClick={() => handleModal(false)}>
Cancel
</Button>
<Button variant="primary" size="sm" onClick={handleSubmit} loading={isSubmitting} disabled={isSubmitting}>
{isSubmitting ? "Processing" : "Continue"}
</Button>
</div>
</div>
</div>
</ModalCore>
);
});

View File

@@ -4,18 +4,19 @@ import { Dispatch, FC, SetStateAction, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { Button, ModalCore } from "@plane/ui";
// plane web components
import { ProjectForm, StateForm } from "@/plane-web/components/integrations/github";
import { ProjectForm, StateForm } from "@/plane-web/components/integrations/gitlab";
// plane web hooks
import { useGithubIntegration } from "@/plane-web/hooks/store";
import { useGitlabIntegration } from "@/plane-web/hooks/store";
// plane web types
import { E_STATE_MAP_KEYS, TGithubEntityConnection, TProjectMap, TStateMap } from "@/plane-web/types/integrations";
import { E_STATE_MAP_KEYS, TProjectMap, TStateMap } from "@/plane-web/types/integrations";
// local imports
import { projectMapInit, stateMapInit } from "../root";
import { TGitlabEntityConnection } from "@/plane-web/types/integrations/gitlab";
type TFormEdit = {
modal: boolean;
handleModal: Dispatch<SetStateAction<boolean>>;
data: TGithubEntityConnection;
data: TGitlabEntityConnection;
};
export const FormEdit: FC<TFormEdit> = observer((props) => {
@@ -26,8 +27,8 @@ export const FormEdit: FC<TFormEdit> = observer((props) => {
const {
workspace,
fetchStates,
entity: { updateEntity },
} = useGithubIntegration();
entityConnection: { updateEntityConnection },
} = useGitlabIntegration();
// states
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
@@ -58,14 +59,13 @@ export const FormEdit: FC<TFormEdit> = observer((props) => {
try {
setIsSubmitting(true);
const payload: Partial<TGithubEntityConnection> = {
entityId: projectMap.entityId,
const payload: Partial<TGitlabEntityConnection> = {
projectId: projectMap.projectId,
config: {
states: { mergeRequestEventMapping: stateMap },
},
};
await updateEntity(data.id, payload);
await updateEntityConnection(data.id, payload);
handleModal(false);
} catch (error) {
@@ -112,7 +112,7 @@ export const FormEdit: FC<TFormEdit> = observer((props) => {
<div className="relative space-y-1 p-3">
<div className="text-base">Pull request automation</div>
<div className="text-xs text-custom-text-200">
With Github integration Enabled, you can automate issue workflows
With Gitlab integration Enabled, you can automate issue workflows
</div>
</div>
<div className="p-3">

View File

@@ -0,0 +1,61 @@
"use client";
import { FC } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import { Briefcase } from "lucide-react";
// components
import { Logo } from "@/components/common";
// plane web components
import { Dropdown } from "@/plane-web/components/importers/ui";
// plane web hooks
import { useGitlabIntegration } from "@/plane-web/hooks/store";
// plane web types
import { TProjectMap } from "@/plane-web/types/integrations";
// public images
import GitlabLogo from "@/public/services/gitlab.svg";
type TEntityForm = {
value: TProjectMap;
handleChange: <T extends keyof TProjectMap>(key: T, value: TProjectMap[T]) => void;
};
export const EntityForm: FC<TEntityForm> = observer((props) => {
// props
const { value, handleChange } = props;
// hooks
const { data: { gitlabEntityIds, gitlabEntityById } } = useGitlabIntegration();
// derived values
const entities = (gitlabEntityIds || [])
.map((id) => {
const entity = gitlabEntityById(id);
return entity || undefined;
})
.filter((entity) => entity !== undefined && entity !== null);
return (
<div className="relative space-y-4 text-sm">
<div className="space-y-1">
<div className="text-custom-text-200">Gitlab Project or Group</div>
<Dropdown
dropdownOptions={(entities || [])?.map((entity) => ({
key: entity?.id.toString() || "",
label: entity?.name || "",
value: entity?.id.toString() || "",
data: entity,
}))}
value={value?.entityId || undefined}
placeHolder="Choose Entity..."
onChange={(value: string | undefined) => handleChange("entityId", value || undefined)}
iconExtractor={() => (
<div className="w-4 h-4 flex-shrink-0 overflow-hidden relative flex justify-center items-center">
<Image src={GitlabLogo} layout="fill" objectFit="contain" alt="Gitlab Logo" />
</div>
)}
queryExtractor={(option) => option.name}
/>
</div>
</div>
);
});

View File

@@ -1,4 +1,6 @@
export * from "./create";
export * from "./create-project-entity";
export * from "./edit";
export * from "./project-form";
export * from "./state-form";
export * from "./create-entity";
export * from "./entity-form";

View File

@@ -13,7 +13,6 @@ import { useGitlabIntegration } from "@/plane-web/hooks/store";
// plane web types
import { TProjectMap } from "@/plane-web/types/integrations";
// public images
import GitlabLogo from "@/public/services/gitlab.svg";
type TProjectForm = {
value: TProjectMap;
@@ -29,17 +28,9 @@ export const ProjectForm: FC<TProjectForm> = observer((props) => {
workspace,
projectIdsByWorkspaceSlug,
getProjectById,
data: { gitlabRepositoryIds, gitlabRepositoryById },
} = useGitlabIntegration();
// derived values
const repositories = (gitlabRepositoryIds || [])
.map((id) => {
const repository = gitlabRepositoryById(id);
return repository || undefined;
})
.filter((repo) => repo !== undefined && repo !== null);
const workspaceSlug = workspace?.slug || undefined;
const planeProjectIds = (workspaceSlug && projectIdsByWorkspaceSlug(workspaceSlug)) || [];
const planeProjects = planeProjectIds
@@ -48,27 +39,6 @@ export const ProjectForm: FC<TProjectForm> = observer((props) => {
return (
<div className="relative space-y-4 text-sm">
<div className="space-y-1">
<div className="text-custom-text-200">Gitlab Repository</div>
<Dropdown
dropdownOptions={(repositories || [])?.map((repo) => ({
key: repo?.id.toString() || "",
label: repo?.name || "",
value: repo?.id.toString() || "",
data: repo,
}))}
value={value?.entityId || undefined}
placeHolder="Choose Repository..."
onChange={(value: string | undefined) => handleChange("entityId", value || undefined)}
iconExtractor={() => (
<div className="w-4 h-4 flex-shrink-0 overflow-hidden relative flex justify-center items-center">
<Image src={GitlabLogo} layout="fill" objectFit="contain" alt="Gitlab Logo" />
</div>
)}
queryExtractor={(option) => option.name}
/>
</div>
<div className="space-y-1">
<div className="text-custom-text-200">Plane Project</div>
<Dropdown

View File

@@ -57,7 +57,7 @@ export const StateForm: FC<TStateForm> = observer((props) => {
return (
<div className="w-full min-h-44 max-h-full overflow-y-auto">
{planeProjectStates &&
{planeProjectStates && projectId &&
GIT_PR_DATA.map((gitState) => (
<StateFormSelection
title={gitState.title}

View File

@@ -3,11 +3,11 @@
import { FC, useState } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
import { Briefcase } from "lucide-react";
import { Button } from "@plane/ui";
import { Briefcase, BriefcaseBusiness } from "lucide-react";
import { Button, Loader } from "@plane/ui";
// plane web components
import { Logo } from "@/components/common";
import { EntityConnectionItem, FormCreate } from "@/plane-web/components/integrations/gitlab";
import { EntityConnectionItem, EntityFormCreate, ProjectEntityFormCreate } from "@/plane-web/components/integrations/gitlab";
// plane web hooks
import { useGitlabIntegration } from "@/plane-web/hooks/store";
// plane web types
@@ -17,6 +17,7 @@ import {
TProjectMap,
TStateMap,
} from "@/plane-web/types/integrations/gitlab";
import { EConnectionType, GitlabEntityType } from "@plane/etl/gitlab";
export const projectMapInit: TProjectMap = {
entityId: undefined,
@@ -37,38 +38,40 @@ export const RepositoryMappingRoot: FC = observer(() => {
const {
workspace,
fetchProjects,
getProjectById,
auth: { workspaceConnectionIds },
data: { fetchGitlabRepositories },
entity: { entityIds, entityById, fetchEntities },
data: { fetchGitlabEntities },
entityConnection: { entityConnectionIds, entityConnectionById, fetchEntityConnections },
} = useGitlabIntegration();
// states
const [modalCreateOpen, setModalCreateOpen] = useState<boolean>(false);
const [modalProjectCreateOpen, setModalProjectCreateOpen] = useState<boolean>(false);
// derived values
const workspaceId = workspace?.id || undefined;
const workspaceSlug = workspace?.slug || undefined;
const workspaceConnectionId = workspaceConnectionIds[0] || undefined;
const entityConnectionMap = entityIds.map((id) => entityById(id));
const entityConnection = entityConnectionMap.reduce(
(result: { [key: string]: TGitlabEntityConnection[] }, entity) => {
if (!entity) return result;
const entityConnections = entityConnectionIds.map((id) => {
const entityConnection = entityConnectionById(id);
if (!entityConnection || (entityConnection.connectionType !== EConnectionType.ENTITY)) {
return;
}
return entityConnection;
});
const projectId = entity?.projectId || "default";
if (!result[projectId]) result[projectId] = [];
result[projectId].push(entity);
return result;
},
{}
);
const projectEntityConnections = entityConnectionIds.map((id) => {
const entityConnection = entityConnectionById(id);
if (!entityConnection || (entityConnection.connectionType !== EConnectionType.PLANE_PROJECT)) {
return;
}
return entityConnection;
});
// fetching external api token
const { isLoading: isGitlabReposLoading } = useSWR(
workspaceConnectionId && workspaceId ? `INTEGRATION_GITLAB_REPOS_${workspaceId}_${workspaceConnectionId}` : null,
workspaceConnectionId && workspaceId ? async () => fetchGitlabRepositories() : null,
const { isLoading: isGitlabEntitiesLoading } = useSWR(
workspaceConnectionId && workspaceId ? `INTEGRATION_GITLAB_ENTITIES_${workspaceId}_${workspaceConnectionId}` : null,
workspaceConnectionId && workspaceId ? async () => fetchGitlabEntities() : null,
{ errorRetryCount: 0 }
);
@@ -82,55 +85,84 @@ export const RepositoryMappingRoot: FC = observer(() => {
// fetching entity connections
const { isLoading: isEntitiesLoading } = useSWR(
workspaceId ? `INTEGRATION_GITLAB_ENTITY_CONNECTIONS_${workspaceId}` : null,
workspaceId ? async () => fetchEntities() : null,
workspaceId ? async () => fetchEntityConnections() : null,
{ errorRetryCount: 0 }
);
return (
<div className="relative border border-custom-border-200 rounded p-4 space-y-4">
{/* heading */}
<div className="relative flex justify-between items-center gap-4">
<div className="space-y-1">
<div className="text-base font-medium">Repository Mapping</div>
<div className="text-sm text-custom-text-200">Sync issues from Gitlab repository to Plane projects</div>
<div className="space-y-4">
<div className="relative border border-custom-border-200 rounded p-4 space-y-4">
{/* heading */}
<div className="relative flex justify-between items-start gap-4">
<div className="space-y-1">
<div className="text-base font-medium">Gitlab Project & Group Connections</div>
<div className="text-sm text-custom-text-200">Sync issues from Gitlab projects or groups to Plane projects</div>
</div>
<Button variant="neutral-primary" size="sm" onClick={() => setModalCreateOpen(true)}>
Add
</Button>
</div>
<Button variant="neutral-primary" size="sm" onClick={() => setModalCreateOpen(true)}>
Add
</Button>
{/* entity connection list */}
{
isEntitiesLoading && <Loader className="space-y-8">
<Loader.Item height="50px" width="40%" />
<div className="w-2/3 grid grid-cols-2 gap-x-8 gap-y-4">
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</div>
<Loader.Item height="50px" width="20%" />
</Loader>
}
{entityConnectionIds && entityConnectionIds.length > 0 && (
<div className="relative space-y-2">
{entityConnections.map((entityConnection, index) => {
if (!entityConnection) return null;
return (
<div className="space-y-2" key={index}>
<EntityConnectionItem key={index} entityConnection={entityConnection} />
</div>
);
})}
</div>
)}
<EntityFormCreate modal={modalCreateOpen} handleModal={setModalCreateOpen} />
</div>
{/* mapped blocks */}
{entityIds && entityIds.length > 0 && (
<div className="relative space-y-2">
{Object.keys(entityConnection).map((projectId, index) => {
const project = projectId ? getProjectById(projectId) : undefined;
if (!project) return null;
return (
<div className="space-y-2" key={index}>
<div className="relative flex items-center gap-2 rounded bg-custom-background-90/50 text-base p-2">
<div className="flex-shrink-0 relative flex justify-center items-center !w-5 !h-5 rounded-sm bg-custom-background-100">
{project && project?.logo_props ? (
<Logo logo={project?.logo_props} size={14} />
) : (
<Briefcase className="w-4 h-4" />
)}
</div>
<div className="text-sm">{project?.name || "Project"}</div>
</div>
<div className="space-y-1">
{(entityConnection[projectId] || []).map((connection, index) => (
<EntityConnectionItem key={index} project={project} entityConnection={connection} />
))}
</div>
</div>
);
})}
{/* Add project state mapping blocks */}
<div className="relative border border-custom-border-200 rounded p-4 space-y-4">
{/* heading */}
<div className="relative flex justify-between items-center gap-4">
<div className="space-y-1">
<div className="text-base font-medium">Plane Project Connections</div>
<div className="text-sm text-custom-text-200">Configure pull requests state mapping from Gitlab to Plane projects</div>
</div>
<Button variant="neutral-primary" size="sm" onClick={() => setModalProjectCreateOpen(true)}>
Add
</Button>
</div>
)}
<FormCreate modal={modalCreateOpen} handleModal={setModalCreateOpen} />
{/* Project mapping list */}
{entityConnectionIds && entityConnectionIds.length > 0 && (
<div className="relative space-y-2">
{projectEntityConnections.map((entityConnection, index) => {
if (!entityConnection) return null;
return (
<div className="space-y-2" key={index}>
<EntityConnectionItem key={index} entityConnection={entityConnection} />
</div>
);
})}
</div>
)}
{/* project entity form */}
<ProjectEntityFormCreate modal={modalProjectCreateOpen} handleModal={setModalProjectCreateOpen} />
</div>
</div>
);
});

View File

@@ -1,6 +1,7 @@
import axios, { AxiosInstance } from "axios";
// plane web types
import { TGitlabRepository } from "@/plane-web/types/integrations/gitlab";
import { IGitlabEntity } from "@plane/etl/gitlab";
export class GitlabDataService {
protected baseURL: string;
@@ -23,4 +24,17 @@ export class GitlabDataService {
.catch((error) => {
throw error?.response?.data;
});
/**
* @description fetch gitlab entities
* @param { string } workspaceId
* @returns { Promise<IGitlabEntity[]> }
*/
fetchGitlabEntities = async (workspaceId: string): Promise<IGitlabEntity[]> =>
await this.axiosInstance
.get(`/api/gitlab/entities/${workspaceId}`)
.then((res) => res.data)
.catch((error) => {
throw error?.response?.data;
});
}

View File

@@ -14,15 +14,14 @@ export class GitlabEntityService {
/**
* @description fetch entity connections
* @param { string } workspaceId
* @param { string } workspaceConnectionId
* @param { string } connectionType
* @returns { Promise<TGitlabEntityConnection[] | undefined> }
*/
fetchEntityConnections = async (
workspaceId: string,
workspaceConnectionId: string
): Promise<TGitlabEntityConnection[] | undefined> =>
await this.axiosInstance
.get(`/api/entity-connections/${workspaceId}/${workspaceConnectionId}`)
.get(`/api/gitlab/entity-connections/${workspaceId}`)
.then((res) => res.data)
.catch((error) => {
throw error?.response?.data;
@@ -30,18 +29,14 @@ export class GitlabEntityService {
/**
* @description fetch entity connection
* @param { string } workspaceId
* @param { string } workspaceConnectionId
* @param { string } entityId
* @param { string } connectionId
* @returns { Promise<TGitlabEntityConnection | undefined> }
*/
fetchEntityConnection = async (
workspaceId: string,
workspaceConnectionId: string,
entityId: string
connectionId: string,
): Promise<TGitlabEntityConnection | undefined> =>
await this.axiosInstance
.get(`/api/entity-connections/${workspaceId}/${workspaceConnectionId}/${entityId}`)
.get(`/api/entity-connections/${connectionId}`)
.then((res) => res.data)
.catch((error) => {
throw error?.response?.data;
@@ -60,7 +55,7 @@ export class GitlabEntityService {
entityConnection: Partial<TGitlabEntityConnection>
): Promise<TGitlabEntityConnection | undefined> =>
await this.axiosInstance
.post(`/api/entity-connections/${workspaceId}/${workspaceConnectionId}`, entityConnection)
.post(`/api/gitlab/entity-connections/${workspaceId}/${workspaceConnectionId}`, entityConnection)
.then((res) => res.data)
.catch((error) => {
throw error?.response?.data;
@@ -68,20 +63,16 @@ export class GitlabEntityService {
/**
* @description update entity connection
* @param { string } workspaceId
* @param { string } workspaceConnectionId
* @param { string } entityId
* @param { string } connectionId
* @param { Partial<TGitlabEntityConnection> } entityConnection
* @returns { Promise<TGitlabEntityConnection | undefined> }
*/
updateEntityConnection = async (
workspaceId: string,
workspaceConnectionId: string,
entityId: string,
connectionId: string,
entityConnection: Partial<TGitlabEntityConnection>
): Promise<TGitlabEntityConnection | undefined> =>
await this.axiosInstance
.put(`/api/entity-connections/${workspaceId}/${workspaceConnectionId}/${entityId}`, entityConnection)
.put(`/api/entity-connections/${connectionId}`, entityConnection)
.then((res) => res.data)
.catch((error) => {
throw error?.response?.data;
@@ -89,20 +80,35 @@ export class GitlabEntityService {
/**
* @description delete entity connection
* @param { string } workspaceId
* @param { string } workspaceConnectionId
* @param { string } entityId
* @param { string } connectionId
* @returns { Promise<void> }
*/
deleteEntityConnection = async (
workspaceId: string,
workspaceConnectionId: string,
entityId: string
connectionId: string,
): Promise<void> =>
await this.axiosInstance
.delete(`/api/entity-connections/${workspaceId}/${workspaceConnectionId}/${entityId}`)
.delete(`/api/entity-connections/${connectionId}`)
.then(() => undefined)
.catch((error) => {
throw error?.response?.data;
});
/**
* @description create project entity connection
* @param { string } workspaceId
* @param { string } workspaceConnectionId
* @param { Partial<TGitlabEntityConnection> } entityConnection
* @returns { Promise<TGitlabEntityConnection | undefined> }
*/
createProjectEntityConnection = async (
workspaceId: string,
workspaceConnectionId: string,
entityConnection: Partial<TGitlabEntityConnection>
): Promise<TGitlabEntityConnection | undefined> =>
await this.axiosInstance
.post(`/api/gitlab/entity-project-connections/${workspaceId}/${workspaceConnectionId}`, entityConnection)
.then((res) => res.data)
.catch((error) => {
throw error?.response?.data;
});
}

View File

@@ -9,26 +9,33 @@ import { GitlabDataService } from "@/plane-web/services/integrations/gitlab";
import { IGitlabStore } from "@/plane-web/store/integrations";
// plane web types
import { TGitlabRepository } from "@/plane-web/types/integrations/gitlab";
import { IGitlabEntity } from "@plane/etl/gitlab";
export interface IGitlabDataStore {
// store instances
gitlabRepositories: Record<string, Record<string, TGitlabRepository>>; // organizationId -> gitlabRepositoryId -> TGitlabRepository
gitlabUsers: Record<string, Record<string, object>>; // organizationId -> gitlabUserId -> gitlabUser
gitlabEntities: Record<string, Record<string, IGitlabEntity>>; // organizationId -> gitlabRepositoryId -> IGitlabEntity
// computed
gitlabRepositoryIds: string[];
gitlabUserIds: string[] | undefined;
gitlabEntityIds: string[] | undefined;
// computed functions
gitlabRepositoryById: (gitlabRepositoryId: string) => TGitlabRepository | undefined;
gitlabUserById: (gitlabUserId: string) => object | undefined;
gitlabEntityById: (gitlabEntityId: string) => IGitlabEntity | undefined;
// actions
fetchGitlabRepositories: () => Promise<object | undefined>;
fetchGitlabUsers: () => Promise<object | undefined>;
fetchGitlabEntities: () => Promise<IGitlabEntity[] | undefined>;
}
export class GitlabDataStore implements IGitlabDataStore {
// observables
gitlabRepositories: Record<string, Record<string, TGitlabRepository>> = {}; // organizationId -> gitlabRepositoryId -> TGitlabRepository
gitlabUsers: Record<string, Record<string, object>> = {}; // organizationId -> gitlabUserId -> gitlabUser
gitlabEntities: Record<string, Record<string, IGitlabEntity>> = {}; // organizationId -> gitlabRepositoryId -> IGitlabEntity
// service
private service: GitlabDataService;
@@ -37,12 +44,15 @@ export class GitlabDataStore implements IGitlabDataStore {
// observables
gitlabRepositories: observable,
gitlabUsers: observable,
gitlabEntities: observable,
// computed
gitlabRepositoryIds: computed,
gitlabUserIds: computed,
gitlabEntityIds: computed,
// computed functions
fetchGitlabRepositories: action,
fetchGitlabUsers: action,
fetchGitlabEntities: action,
});
this.service = new GitlabDataService(encodeURI(SILO_BASE_URL + SILO_BASE_PATH));
@@ -77,6 +87,20 @@ export class GitlabDataStore implements IGitlabDataStore {
return Object.keys(gitlabUsers);
}
/**
* @description get gitlab entity ids
* @returns { string[] | undefined }
*/
get gitlabEntityIds(): string[] | undefined {
const organizationId = this.store.auth.workspaceConnectionIds[0];
if (!organizationId) return;
const gitlabEntities = this.gitlabEntities[organizationId];
if (!gitlabEntities) return;
return Object.keys(gitlabEntities);
}
// computed functions
/**
* @description get gitlab repository by id
@@ -108,6 +132,22 @@ export class GitlabDataStore implements IGitlabDataStore {
return gitlabUsers[gitlabUserId] || undefined;
});
/**
* @description get gitlab entity by id
* @param { string } gitlabEntityId
* @returns { IGitlabEntity | undefined }
*/
gitlabEntityById = computedFn((gitlabEntityId: string): IGitlabEntity | undefined => {
const organizationId = this.store.auth.workspaceConnectionIds[0];
if (!organizationId) return;
const gitlabEntities = this.gitlabEntities[organizationId];
if (!gitlabEntities) return;
return gitlabEntities[gitlabEntityId] || undefined;
});
// actions
/**
* @description fetch gitlab repositories
@@ -151,4 +191,32 @@ export class GitlabDataStore implements IGitlabDataStore {
throw error;
}
};
/**
* @description fetch gitlab entities
* @returns { Promise<IGitlabEntity[]> }
*/
fetchGitlabEntities = async (): Promise<IGitlabEntity[]> => {
try {
const workspaceId = this.store.workspace?.id;
const organizationId = this.store.auth.workspaceConnectionIds[0];
if (!workspaceId || !organizationId) return [];
const response = await this.service.fetchGitlabEntities(workspaceId);
if (response) {
runInAction(() => {
response.forEach((data) => {
this.gitlabEntities[organizationId] = this.gitlabEntities[organizationId] || {};
this.gitlabEntities[organizationId][data.id] = data;
});
});
}
return response;
} catch (error) {
throw error;
}
};
}

View File

@@ -11,43 +11,46 @@ import { GitlabEntityService } from "@/plane-web/services/integrations/gitlab";
import { IGitlabStore } from "@/plane-web/store/integrations";
// plane web types
import { TGitlabEntityConnection } from "@/plane-web/types/integrations/gitlab";
import { EConnectionType, GitlabEntityType } from "@plane/etl/gitlab";
export interface IGitlabEntityStore {
export interface IGitlabEntityConnectionStore {
// store instances
entityMap: Record<string, Record<string, Record<string, TGitlabEntityConnection>>>; // workspaceId -> workspaceConnectionId -> entityId -> entity
// computed
entityIds: string[];
entityConnectionMap: Record<string, Record<string, Record<string, TGitlabEntityConnection>>>; // workspaceId -> workspaceConnectionId -> connectionId -> entity
// computed
entityConnectionIds: string[];
// computed functions
entityById: (entityId: string) => TGitlabEntityConnection | undefined;
entityConnectionById: (entityId: string) => TGitlabEntityConnection | undefined;
// actions
fetchEntities: () => Promise<TGitlabEntityConnection[] | undefined>;
fetchEntity: (entityId: string) => Promise<TGitlabEntityConnection | undefined>;
createEntity: (entity: Partial<TGitlabEntityConnection>) => Promise<TGitlabEntityConnection | undefined>;
updateEntity: (
entityId: string,
fetchEntityConnections: () => Promise<TGitlabEntityConnection[] | undefined>;
fetchEntityConnection: (entityId: string) => Promise<TGitlabEntityConnection | undefined>;
createEntityConnection: (entity: Partial<TGitlabEntityConnection>) => Promise<TGitlabEntityConnection | undefined>;
createProjectConnection: (entity: Partial<TGitlabEntityConnection>) => Promise<TGitlabEntityConnection | undefined>;
updateEntityConnection: (
connectionId: string,
entity: Partial<TGitlabEntityConnection>
) => Promise<TGitlabEntityConnection | undefined>;
deleteEntity: (entityId: string) => Promise<void | undefined>;
deleteEntityConnection: (connectionId: string) => Promise<void | undefined>;
}
export class GitlabEntityStore implements IGitlabEntityStore {
export class GitlabEntityStore implements IGitlabEntityConnectionStore {
// observables
entityMap: Record<string, Record<string, Record<string, TGitlabEntityConnection>>> = {}; // workspaceId -> workspaceConnectionId -> entityId -> entity
entityConnectionMap: Record<string, Record<string, Record<string, TGitlabEntityConnection>>> = {}; // workspaceId -> workspaceConnectionId -> connectionId -> entity
// service
private service: GitlabEntityService;
constructor(protected store: IGitlabStore) {
makeObservable(this, {
// observables
entityMap: observable,
entityConnectionMap: observable,
// computed
entityIds: computed,
entityConnectionIds: computed,
// actions
fetchEntities: action,
fetchEntity: action,
createEntity: action,
updateEntity: action,
deleteEntity: action,
fetchEntityConnections: action,
fetchEntityConnection: action,
createEntityConnection: action,
createProjectConnection: action,
updateEntityConnection: action,
deleteEntityConnection: action,
});
this.service = new GitlabEntityService(encodeURI(SILO_BASE_URL + SILO_BASE_PATH));
@@ -58,27 +61,27 @@ export class GitlabEntityStore implements IGitlabEntityStore {
* @description get entity ids
* @returns { string[] }
*/
get entityIds(): string[] {
get entityConnectionIds(): string[] {
const workspaceId = this.store.workspace?.id || undefined;
const workspaceConnectionId = this.store.auth.workspaceConnectionIds[0] || undefined;
if (!workspaceId || !workspaceConnectionId || !this.entityMap?.[workspaceId]?.[workspaceConnectionId]) return [];
if (!workspaceId || !workspaceConnectionId || !this.entityConnectionMap?.[workspaceId]?.[workspaceConnectionId]) return [];
return Object.keys(this.entityMap[workspaceId][workspaceConnectionId]);
return Object.keys(this.entityConnectionMap[workspaceId][workspaceConnectionId]);
}
// computed functions
/**
* @description get entity by id
* @param { string } entityId
* @param { string } connectionId
* @returns { TGitlabEntityConnection | undefined }
*/
entityById = computedFn((entityId: string): TGitlabEntityConnection | undefined => {
entityConnectionById = computedFn((connectionId: string): TGitlabEntityConnection | undefined => {
const workspaceId = this.store.workspace?.id || undefined;
const workspaceConnectionId = this.store.auth.workspaceConnectionIds[0] || undefined;
if (!workspaceId || !workspaceConnectionId || !entityId || !this.entityMap?.[workspaceId]?.[workspaceConnectionId])
if (!workspaceId || !workspaceConnectionId || !connectionId || !this.entityConnectionMap?.[workspaceId]?.[workspaceConnectionId])
return undefined;
return this.entityMap[workspaceId][workspaceConnectionId][entityId];
return this.entityConnectionMap[workspaceId][workspaceConnectionId][connectionId];
});
// actions
@@ -86,18 +89,18 @@ export class GitlabEntityStore implements IGitlabEntityStore {
* @description fetch entities
* @returns { Promise<TGitlabEntityConnection[] | undefined> }
*/
fetchEntities = async (): Promise<TGitlabEntityConnection[] | undefined> => {
fetchEntityConnections = async (): Promise<TGitlabEntityConnection[] | undefined> => {
try {
const workspaceId = this.store.workspace?.id || undefined;
const workspaceConnectionId = this.store.auth.workspaceConnectionIds[0] || undefined;
if (!workspaceId || !workspaceConnectionId) return;
const response = await this.service.fetchEntityConnections(workspaceId, workspaceConnectionId);
const response = await this.service.fetchEntityConnections(workspaceId);
if (response) {
runInAction(() => {
response.forEach((data) => {
set(this.entityMap, [workspaceId, workspaceConnectionId, data.id], data);
set(this.entityConnectionMap, [workspaceId, workspaceConnectionId, data.id], data);
});
});
}
@@ -110,20 +113,20 @@ export class GitlabEntityStore implements IGitlabEntityStore {
/**
* @description fetch entity
* @param { string } entityId
* @param { string } connectionId
* @returns { Promise<TGitlabEntityConnection | undefined> }
*/
fetchEntity = async (entityId: string): Promise<TGitlabEntityConnection | undefined> => {
fetchEntityConnection = async (connectionId: string): Promise<TGitlabEntityConnection | undefined> => {
try {
const workspaceId = this.store.workspace?.id || undefined;
const workspaceConnectionId = this.store.auth.workspaceConnectionIds[0] || undefined;
if (!workspaceId || !workspaceConnectionId || !entityId) return;
if (!workspaceId || !workspaceConnectionId || !connectionId) return;
const response = await this.service.fetchEntityConnection(workspaceId, workspaceConnectionId, entityId);
const response = await this.service.fetchEntityConnection(connectionId);
if (response) {
runInAction(() => {
set(this.entityMap, [workspaceId, workspaceConnectionId, response.id], response);
set(this.entityConnectionMap, [workspaceId, workspaceConnectionId, response.id], response);
});
}
@@ -138,18 +141,18 @@ export class GitlabEntityStore implements IGitlabEntityStore {
* @param { Partial<TGitlabEntityConnection> } entity
* @returns { Promise<TGitlabEntityConnection | undefined> }
*/
createEntity = async (entity: Partial<TGitlabEntityConnection>): Promise<TGitlabEntityConnection | undefined> => {
createEntityConnection = async (entity: Partial<TGitlabEntityConnection>): Promise<TGitlabEntityConnection | undefined> => {
try {
const workspaceId = this.store.workspace?.id || undefined;
const workspaceSlug = this.store.workspace?.slug || undefined;
const workspaceConnectionId = this.store.auth.workspaceConnectionIds[0] || undefined;
if (!workspaceId || !workspaceSlug || !workspaceConnectionId) return;
const gitlabRepoId = entity?.entityId || undefined;
if (!gitlabRepoId) return;
const gitlabEntityId = entity?.entityId || undefined;
if (!gitlabEntityId) return;
const gitlabRepo = this.store.data.gitlabRepositoryById(gitlabRepoId) || undefined;
if (!gitlabRepo) return;
const gitlabEntity = this.store.data.gitlabEntityById(gitlabEntityId) || undefined;
if (!gitlabEntity) return;
const payload: Partial<TGitlabEntityConnection> = {
workspaceId: workspaceId,
@@ -157,8 +160,9 @@ export class GitlabEntityStore implements IGitlabEntityStore {
projectId: entity.projectId,
workspaceConnectionId: workspaceConnectionId,
entityId: entity.entityId,
entitySlug: gitlabRepo.full_name,
entityData: gitlabRepo,
entityType: gitlabEntity.type,
entitySlug: gitlabEntity.name,
entityData: gitlabEntity,
config: entity.config,
};
@@ -166,11 +170,42 @@ export class GitlabEntityStore implements IGitlabEntityStore {
if (response) {
runInAction(() => {
set(this.entityMap, [workspaceId, workspaceConnectionId, response.id], response);
set(this.entityConnectionMap, [workspaceId, workspaceConnectionId, response.id], response);
});
}
await this.fetchEntities();
await this.fetchEntityConnections();
return response;
} catch (error) {
throw error;
}
};
createProjectConnection = async (entity: Partial<TGitlabEntityConnection>): Promise<TGitlabEntityConnection | undefined> => {
try {
const workspaceId = this.store.workspace?.id || undefined;
const workspaceSlug = this.store.workspace?.slug || undefined;
const workspaceConnectionId = this.store.auth.workspaceConnectionIds[0] || undefined;
if (!workspaceId || !workspaceSlug || !workspaceConnectionId) return;
const payload: Partial<TGitlabEntityConnection> = {
workspaceId: workspaceId,
workspaceSlug: workspaceSlug,
projectId: entity.projectId,
workspaceConnectionId: workspaceConnectionId,
config: entity.config,
};
const response = await this.service.createProjectEntityConnection(workspaceId, workspaceConnectionId, payload);
if (response) {
runInAction(() => {
set(this.entityConnectionMap, [workspaceId, workspaceConnectionId, response.id], response);
});
}
await this.fetchEntityConnections();
return response;
} catch (error) {
@@ -180,42 +215,33 @@ export class GitlabEntityStore implements IGitlabEntityStore {
/**
* @description update entity
* @param { string } entityId
* @param { string } connectionId
* @param { Partial<TGitlabEntityConnection> } entity
* @returns { Promise<TGitlabEntityConnection | undefined> }
*/
updateEntity = async (
entityId: string,
updateEntityConnection = async (
connectionId: string,
entity: Partial<TGitlabEntityConnection>
): Promise<TGitlabEntityConnection | undefined> => {
try {
const workspaceId = this.store.workspace?.id || undefined;
const workspaceSlug = this.store.workspace?.slug || undefined;
const workspaceConnectionId = this.store.auth.workspaceConnectionIds[0] || undefined;
if (!workspaceId || !workspaceSlug || !workspaceConnectionId || !entityId) return;
const gitlabRepoId = entity?.entityId || undefined;
if (!gitlabRepoId) return;
const gitlabRepo = this.store.data.gitlabRepositoryById(gitlabRepoId) || undefined;
if (!gitlabRepo) return;
if (!workspaceId || !workspaceSlug || !workspaceConnectionId || !connectionId) return;
const payload: Partial<TGitlabEntityConnection> = {
workspaceId: workspaceId,
workspaceSlug: workspaceSlug,
projectId: entity.projectId,
workspaceConnectionId: workspaceConnectionId,
entityId: entity.entityId,
entitySlug: gitlabRepo.full_name,
entityData: gitlabRepo,
config: entity.config,
};
const response = await this.service.updateEntityConnection(workspaceId, workspaceConnectionId, entityId, payload);
const response = await this.service.updateEntityConnection(connectionId, payload);
if (response) {
runInAction(() => {
set(this.entityMap, [workspaceId, workspaceConnectionId, response.id], response);
set(this.entityConnectionMap, [workspaceId, workspaceConnectionId, response.id], response);
});
}
@@ -227,17 +253,17 @@ export class GitlabEntityStore implements IGitlabEntityStore {
/**
* @description delete entity
* @param { string } entityId
* @param { string } connectionId
* @returns { Promise<void | undefined> }
*/
deleteEntity = async (entityId: string): Promise<void | undefined> => {
deleteEntityConnection = async (connectionId: string): Promise<void | undefined> => {
try {
const workspaceId = this.store.workspace?.id || undefined;
const workspaceConnectionId = this.store.auth.workspaceConnectionIds[0] || undefined;
if (!workspaceId || !workspaceConnectionId || !entityId) return;
if (!workspaceId || !workspaceConnectionId || !connectionId) return;
await this.service.deleteEntityConnection(workspaceId, workspaceConnectionId, entityId);
unset(this.entityMap, [workspaceId, workspaceConnectionId, entityId]);
await this.service.deleteEntityConnection(connectionId);
unset(this.entityConnectionMap, [workspaceId, workspaceConnectionId, connectionId]);
return;
} catch (error) {

View File

@@ -8,7 +8,7 @@ import {
GitlabAuthStore,
IGitlabDataStore,
GitlabDataStore,
IGitlabEntityStore,
IGitlabEntityConnectionStore,
GitlabEntityStore,
} from "@/plane-web/store/integrations";
import { RootStore } from "@/plane-web/store/root.store";
@@ -17,14 +17,14 @@ export interface IGitlabStore extends IIntegrationBaseStore {
// store instances
auth: IGitlabAuthStore;
data: IGitlabDataStore;
entity: IGitlabEntityStore;
entityConnection: IGitlabEntityConnectionStore;
}
export class GitlabStore extends IntegrationBaseStore implements IGitlabStore {
// store instances
auth: IGitlabAuthStore;
data: IGitlabDataStore;
entity: IGitlabEntityStore;
entityConnection: IGitlabEntityConnectionStore;
constructor(protected store: RootStore) {
super(store);
@@ -33,6 +33,6 @@ export class GitlabStore extends IntegrationBaseStore implements IGitlabStore {
// store instances
this.auth = new GitlabAuthStore(this);
this.data = new GitlabDataStore(this);
this.entity = new GitlabEntityStore(this);
this.entityConnection = new GitlabEntityStore(this);
}
}

View File

@@ -1,4 +1,5 @@
import { TGitlabWorkspaceConnectionData, TWorkspaceConnection } from "@plane/etl/core";
import { EConnectionType, GitlabEntityType, IGitlabEntity } from "@plane/etl/gitlab";
import { IState } from "@plane/sdk";
// auth types
@@ -48,14 +49,13 @@ export type TGitlabEntityConnection = {
entityId: string;
entitySlug: string;
entityData: object & {
id: number;
name: string;
full_name: string;
};
entityData: IGitlabEntity;
entityType: GitlabEntityType;
config: TGitlabEntityConnectionConfig;
connectionType: keyof typeof EConnectionType;
createdAt: string;
updatedAt: string;
};

597
yarn.lock
View File

@@ -1815,62 +1815,62 @@
prop-types "^15.8.1"
react-is "^19.0.0"
"@next/env@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.22.tgz#8898ae47595badbfacebfc1585f42a4e06a97301"
integrity sha512-EQ6y1QeNQglNmNIXvwP/Bb+lf7n9WtgcWvtoFsHquVLCJUuxRs+6SfZ5EK0/EqkkLex4RrDySvKgKNN7PXip7Q==
"@next/env@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.21.tgz#09ff0813d29c596397e141205d4f5fd5c236bdd0"
integrity sha512-lXcwcJd5oR01tggjWJ6SrNNYFGuOOMB9c251wUNkjCpkoXOPkDeF/15c3mnVlBqrW4JJXb2kVxDFhC4GduJt2A==
"@next/eslint-plugin-next@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.22.tgz#4de757f8e430c18b1d1e1b92ff2fe45754d8c641"
integrity sha512-8xCmBMd+hUapMpviPp5g3oDhoWRtbE/QeN/Nvth+SZrdt7xt9TBsH8cePkRwRjXFpwHndpRDNVQROxR/1HiVbg==
"@next/eslint-plugin-next@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.21.tgz#3e5b96cec21ba76c1e316938f74e32ce1d09f554"
integrity sha512-bxfiExnMkpwo4bBhCqnDhdgFyxSp6Xt6xu4Ne7En6MpgqwiER95Or+q1WDUDX4e888taeIAdPIAVaY+Wv0kiwQ==
dependencies:
glob "10.3.10"
"@next/swc-darwin-arm64@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.22.tgz#2b3fcb42247ba951b19a48fc03f1d6fe65629baa"
integrity sha512-HUaLiehovgnqY4TMBZJ3pDaOsTE1spIXeR10pWgdQVPYqDGQmHJBj3h3V6yC0uuo/RoY2GC0YBFRkOX3dI9WVQ==
"@next/swc-darwin-arm64@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.21.tgz#32a31992aace1440981df9cf7cb3af7845d94fec"
integrity sha512-HwEjcKsXtvszXz5q5Z7wCtrHeTTDSTgAbocz45PHMUjU3fBYInfvhR+ZhavDRUYLonm53aHZbB09QtJVJj8T7g==
"@next/swc-darwin-x64@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.22.tgz#11ecc609e9530b3edf8ddfd1fd3bd6aca4e1bfda"
integrity sha512-ApVDANousaAGrosWvxoGdLT0uvLBUC+srqOcpXuyfglA40cP2LBFaGmBjhgpxYk5z4xmunzqQvcIgXawTzo2uQ==
"@next/swc-darwin-x64@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.21.tgz#5ab4b3f6685b6b52f810d0f5cf6e471480ddffdb"
integrity sha512-TSAA2ROgNzm4FhKbTbyJOBrsREOMVdDIltZ6aZiKvCi/v0UwFmwigBGeqXDA97TFMpR3LNNpw52CbVelkoQBxA==
"@next/swc-linux-arm64-gnu@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.22.tgz#4c08dd223e50c348f561af2285e27fb326ffabbf"
integrity sha512-3O2J99Bk9aM+d4CGn9eEayJXHuH9QLx0BctvWyuUGtJ3/mH6lkfAPRI4FidmHMBQBB4UcvLMfNf8vF0NZT7iKw==
"@next/swc-linux-arm64-gnu@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.21.tgz#8a0e1fa887aef19ca218af2af515d0a5ee67ba3f"
integrity sha512-0Dqjn0pEUz3JG+AImpnMMW/m8hRtl1GQCNbO66V1yp6RswSTiKmnHf3pTX6xMdJYSemf3O4Q9ykiL0jymu0TuA==
"@next/swc-linux-arm64-musl@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.22.tgz#0b285336f145887d421b3762f3d7c75f847ec1b3"
integrity sha512-H/hqfRz75yy60y5Eg7DxYfbmHMjv60Dsa6IWHzpJSz4MRkZNy5eDnEW9wyts9bkxwbOVZNPHeb3NkqanP+nGPg==
"@next/swc-linux-arm64-musl@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.21.tgz#ddad844406b42fa8965fe11250abc85c1fe0fd05"
integrity sha512-Ggfw5qnMXldscVntwnjfaQs5GbBbjioV4B4loP+bjqNEb42fzZlAaK+ldL0jm2CTJga9LynBMhekNfV8W4+HBw==
"@next/swc-linux-x64-gnu@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.22.tgz#a936b6cfea0364571102f0389c6368d6acf3e294"
integrity sha512-LckLwlCLcGR1hlI5eiJymR8zSHPsuruuwaZ3H2uudr25+Dpzo6cRFjp/3OR5UYJt8LSwlXv9mmY4oI2QynwpqQ==
"@next/swc-linux-x64-gnu@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.21.tgz#db55fd666f9ba27718f65caa54b622a912cdd16b"
integrity sha512-uokj0lubN1WoSa5KKdThVPRffGyiWlm/vCc/cMkWOQHw69Qt0X1o3b2PyLLx8ANqlefILZh1EdfLRz9gVpG6tg==
"@next/swc-linux-x64-musl@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.22.tgz#0359497840d0b7d8c095d0d9735bc6aec68cef5d"
integrity sha512-qGUutzmh0PoFU0fCSu0XYpOfT7ydBZgDfcETIeft46abPqP+dmePhwRGLhFKwZWxNWQCPprH26TjaTxM0Nv8mw==
"@next/swc-linux-x64-musl@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.21.tgz#dddb850353624efcd58c4c4e30ad8a1aab379642"
integrity sha512-iAEBPzWNbciah4+0yI4s7Pce6BIoxTQ0AGCkxn/UBuzJFkYyJt71MadYQkjPqCQCJAFQ26sYh7MOKdU+VQFgPg==
"@next/swc-win32-arm64-msvc@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.22.tgz#9fd249d49ffccf3388400ab24472c432cdd04c24"
integrity sha512-K6MwucMWmIvMb9GlvT0haYsfIPxfQD8yXqxwFy4uLFMeXIb2TcVYQimxkaFZv86I7sn1NOZnpOaVk5eaxThGIw==
"@next/swc-win32-arm64-msvc@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.21.tgz#290012ee57b196d3d2d04853e6bf0179cae9fbaf"
integrity sha512-plykgB3vL2hB4Z32W3ktsfqyuyGAPxqwiyrAi2Mr8LlEUhNn9VgkiAl5hODSBpzIfWweX3er1f5uNpGDygfQVQ==
"@next/swc-win32-ia32-msvc@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.22.tgz#70d8d5a48e78c7382c3e0544af28c2788ca6b551"
integrity sha512-5IhDDTPEbzPR31ZzqHe90LnNe7BlJUZvC4sA1thPJV6oN5WmtWjZ0bOYfNsyZx00FJt7gggNs6SrsX0UEIcIpA==
"@next/swc-win32-ia32-msvc@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.21.tgz#c959135a78cab18cca588d11d1e33bcf199590d4"
integrity sha512-w5bacz4Vxqrh06BjWgua3Yf7EMDb8iMcVhNrNx8KnJXt8t+Uu0Zg4JHLDL/T7DkTCEEfKXO/Er1fcfWxn2xfPA==
"@next/swc-win32-x64-msvc@14.2.22":
version "14.2.22"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.22.tgz#b034f544c1346093a235f6bba46497a1ba344fc1"
integrity sha512-nvRaB1PyG4scn9/qNzlkwEwLzuoPH3Gjp7Q/pLuwUgOTt1oPMlnCI3A3rgkt+eZnU71emOiEv/mR201HoURPGg==
"@next/swc-win32-x64-msvc@14.2.21":
version "14.2.21"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.21.tgz#21ff892286555b90538a7d1b505ea21a005d6ead"
integrity sha512-sT6+llIkzpsexGYZq8cjjthRyRGe5cJVhqh12FmlbxHqna6zsDDK8UNaV7g41T6atFHCJUPeLb3uyAwrBwy0NA==
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
version "2.1.8-no-fsevents.3"
@@ -3815,181 +3815,181 @@
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd"
integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==
"@tiptap/core@^2.1.13", "@tiptap/core@^2.11.0", "@tiptap/core@^2.4.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.11.0.tgz#72891ae9b3e4b2a67f79ab3bcb14483d30a9bd8a"
integrity sha512-0S3AWx6E2QqwdQqb6z0/q6zq2u9lA9oL3BLyAaITGSC9zt8OwjloS2k1zN6wLa9hp2rO0c0vDnWsTPeFaEaMdw==
"@tiptap/core@^2.1.13", "@tiptap/core@^2.10.4", "@tiptap/core@^2.4.0":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.10.4.tgz#0b3ad822d71f5834b281590809f430e6fd41523c"
integrity sha512-fExFRTRgb6MSpg2VvR5qO2dPTQAZWuUoU4UsBCurIVcPWcyVv4FG1YzgMyoLDKy44rebFtwUGJbfU9NzX7Q/bA==
"@tiptap/extension-blockquote@^2.1.13", "@tiptap/extension-blockquote@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.11.0.tgz#c80aec1ce7791be5a0b45071ffb7481f40c35666"
integrity sha512-DBjWbgmbAAR879WAsk0+5xxgqpOTweWNnY7kEqWv3EJtLUvECXN63smiv3o4fREwwbEJqgihBu5/YugRC5z1dg==
"@tiptap/extension-blockquote@^2.1.13", "@tiptap/extension-blockquote@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.10.4.tgz#0f9a26740009fc7430fabc423077c369feaebb42"
integrity sha512-4JSwAM3B92YWvGzu/Vd5rovPrCGwLSaSLD5rxcLyfxLSrTDQd3n7lp78pzVgGhunVECzaGF5A0ByWWpEyS0a3w==
"@tiptap/extension-bold@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.11.0.tgz#170e04df4396f3a3b830e34055b3a87f4e42a3b6"
integrity sha512-3x9BQZHYD5xFA0pCEneEMHZyIoxYo4NKcbhR4CLxGad1Xd+5g109nr1+eZ1JgvnChkeVf1eD6SaQE2A28lxR5g==
"@tiptap/extension-bold@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.10.4.tgz#ac4a791fe9c4ccf0c4fb70c304e663cc07ba9397"
integrity sha512-SdO4oFQKaERCGfwOc1CLYQRtThENam2KWfWmvpsymknokt5qYzU57ft0SE1HQV9vVYEzZ9HrWIgv2xrgu0g9kg==
"@tiptap/extension-bubble-menu@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.11.0.tgz#f2f596d01d24312962bd9c14a1c1a797931cf221"
integrity sha512-21KyB7+QSQjw72Oxzs3Duw9WErAUrigFZCyoCZNjp24wP7mFVsy1jAcnRiAi8pBVwlwHBZ29IW1PeavqCSFFVA==
"@tiptap/extension-bubble-menu@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.10.4.tgz#6d2611fd3dd4c6b848b5f028ba96567aec5bd1e3"
integrity sha512-GVtZwJaQyLBptMsmDtYl5GEobd1Uu7C9sc9Z+PdXwMuxmFfg+j07bCKCj5JJj/tjgXCSLVxWdTlDHxNrgzQHjw==
dependencies:
tippy.js "^6.3.7"
"@tiptap/extension-bullet-list@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.0.tgz#972f31f1fffc74327c3cb9795893c13736132de3"
integrity sha512-UALypJvO+cPSk/nC1HhkX/ImS9FxbKe2Pr0iDofakvZU1U1msumLVn2M/iq+ax1Mm9thodpvJv0hGDtFRwm7lQ==
"@tiptap/extension-bullet-list@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.10.4.tgz#0b86fb2782b4a7fc012a23d3e265bcc798e70a12"
integrity sha512-JVwDPgOBYRU2ivaadOh4IaQYXQEiSw6sB36KT/bwqJF2GnEvLiMwptdRMn9Uuh6xYR3imjIZtV6uZAoneZdd6g==
"@tiptap/extension-character-count@^2.6.5":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-character-count/-/extension-character-count-2.11.0.tgz#7d3992ceba72f1eeb2db3a1fc997c50dddd7cf8c"
integrity sha512-WbqVr1QY62vxpmDJP5k3bwyzoHha1sZTs0xj3L+4s1j/SB2A7tAlFdcNPPwfbPOINHQgomSAyClfTyd4Gor7HA==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-character-count/-/extension-character-count-2.10.4.tgz#dccf731a48eaf93075500709929a1bf0eff1d8f9"
integrity sha512-NoVLQI/zTEdA0EZe5+oinQ+F/WQMblZRdEWgfsUtQoLRloSAF+pFeQwDenpejdOuWqixT4vdzpboBocj4uQLsw==
"@tiptap/extension-code-block@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.11.0.tgz#ab8d19bb85ba9dccb14ad5dcc6559cb7965d173e"
integrity sha512-8of3qTOLjpveHBrrk8KVliSUVd6R2i2TNrBj0f/21HcFVAy0fP++02p6vI6UPOhwM3+p3CprGdSM48DFCu1rqw==
"@tiptap/extension-code-block@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.10.4.tgz#a86a4477acc995c5185e32dd60188f4e173f9341"
integrity sha512-qS4jnbJqghNMT2+B+GQ807ATgqkL9OQ//NlL+ZwVSe+DPDduNA9B6IB9SrWENDfOnzekpi7kcEcm+RenELARRQ==
"@tiptap/extension-code@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.11.0.tgz#eaf3a0a63ceeda462d1526e40a221223c7497b54"
integrity sha512-2roNZxcny1bGjyZ8x6VmGTuKbwfJyTZ1hiqPc/CRTQ1u42yOhbjF4ziA5kfyUoQlzygZrWH9LR5IMYGzPQ1N3w==
"@tiptap/extension-code@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.10.4.tgz#d6d1fd9e7f1b457700b6c47e115fb1cc9b9039f8"
integrity sha512-Vj/N0nbSQiV1o7X7pRySK9Fu72Dd266gm27TSlsts6IwJu5MklFvz7ezJUWoLjt2wmCV8/U/USmk/39ic9qjvg==
"@tiptap/extension-collaboration-cursor@^2.6.6":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.11.0.tgz#14f60c6d7774aa104dbc8b137320638a05ce0110"
integrity sha512-JZx9SWOxEYQIGaJKndsCPdQas8hniodSsfAFjJq1X62nl7Hlif//mrjAU8c5gRCd7KtTxf/Q8Y7cClLdfkjQmQ==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.10.4.tgz#2d6f177d5ffbdef40af3863e22466d811d6fe0ba"
integrity sha512-U5FJnvIveqLxKZ9aLPgXj1EDzfFfLlHX6J6VZNGAc0QsK5rWCdiU7HWuG2hNTSjHayqh/2V+x30PTMc5EuCadg==
"@tiptap/extension-collaboration@^2.3.2":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-collaboration/-/extension-collaboration-2.11.0.tgz#21ac1c8bc2ca6d8ba2a838e63457528627294888"
integrity sha512-pS3E//ODD80PwVXp7zOqek0q9z5AtZ6sMSK5nPneNioe7dSvCeQzToOD9V6EevK4KChUIN9wEryK8mkQs57ioA==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-collaboration/-/extension-collaboration-2.10.4.tgz#7764d430f6a3c2e1cca7d07ab6d4c140b322b322"
integrity sha512-IvVK/KGG3A0r/Du9ISv06v0VkKXB81RbCK3b/FSkV8+GPQXOqJ9L3aQ4N1LO7ZX5Cb0RbfVgSMK8WH2MIl6xKQ==
"@tiptap/extension-document@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.11.0.tgz#dcae7bc3a29b279ddbf88fc2b248817705ccb8a6"
integrity sha512-9YI0AT3mxyUZD7NHECHyV1uAjQ8KwxOS5ACwvrK1MU8TqY084LmodYNTXPKwpqbr51yvt3qZq1R7UIVu4/22Cg==
"@tiptap/extension-document@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.10.4.tgz#68575c122f6d71556dae6f9df6173903b7cdfdf9"
integrity sha512-1Pqrl6Rr9bVEHJ3zO2dM7UUA0Qn/r70JQ9YLlestjW1sbMaMuY3Ifvu2uSyUE7SAGV3gvxwNVQCrv8f0VlVEaA==
"@tiptap/extension-dropcursor@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.0.tgz#c81eea3f90c460d1eefed14f8e9735346340191b"
integrity sha512-p7tUtlz7KzBa+06+7W2LJ8AEiHG5chdnUIapojZ7SqQCrFRVw70R+orpkzkoictxNNHsun0A9FCUy4rz8L0+nQ==
"@tiptap/extension-dropcursor@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.10.4.tgz#6dea1c17878a8a0d68c7915f34b7b02980e91c90"
integrity sha512-0XEM/yNLaMc/sZlYOau7XpHyYiHT9LwXUe7kmze/L8eowIa/iLvmRbcnUd3rtlZ7x7wooE6UO9c7OtlREg4ZBw==
"@tiptap/extension-floating-menu@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.11.0.tgz#fa96fc2a75d94d2e21f4a6ed6b9fedd53d344a4a"
integrity sha512-dexhhUJm0x9OolbeVCa7RpxuALU3bJZC7dFpu/rPG3ZetXKhVw8hTrqUQD5w1DjXpczBzScnLgLrvnjxbG66pw==
"@tiptap/extension-floating-menu@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.10.4.tgz#e00bbeba8b67ce2511464b9b5203a399fc7b4311"
integrity sha512-K2MDiu6CwQ7+Jr6g1Lh3Tuxm1L6SefSHMpQO0UW3aRGwgEV5pjlrztnBFX4K9b7MNuQ4dJGCUK9u8Cv7Xss0qg==
dependencies:
tippy.js "^6.3.7"
"@tiptap/extension-gapcursor@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.0.tgz#01511c996656c43ed008825b66ccdf9fa365a674"
integrity sha512-1TVOthPkUYwTQnQwP0BzuIHVz09epOiXJQ3GqgNZsmTehwcMzz2vGCpx1JXhZ5DoMaREHNLCdraXb1n2FdhDNA==
"@tiptap/extension-gapcursor@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.10.4.tgz#697f0f599c5fe90f584e3f94e4866959211c8688"
integrity sha512-KbJfoaqTZePpkWAN+klpK5j0UVtELxN7H5B0J556/UCB/rnq+OsdEFHPks2Ss9TidqWzRUqcxUE50UZ7b8h7Ug==
"@tiptap/extension-hard-break@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.11.0.tgz#0e199a1c58171fe1879e730b87666e759fe55c04"
integrity sha512-7pMgPNk2FnPT0LcWaWNNxOLK3LQnRSYFgrdBGMXec3sy+y3Lit3hM+EZhbZcHpTIQTbWWs+eskh1waRMIt0ZaQ==
"@tiptap/extension-hard-break@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.10.4.tgz#57767f35950405c457789faa7b793830860096cc"
integrity sha512-nW9wubW1A/CO2Ssn9wNMP08tR9Oarg9VUGzJ5qNuz38DDNyntE1SyDS+XStkeMq5nKqJ3YKhukyAJH/PiRq4Mg==
"@tiptap/extension-heading@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.11.0.tgz#8b7530a325faffdf325d03d82a2d6947a440d662"
integrity sha512-vrYvxibsY7/Sd2wYQDZ8AfIORfFi/UHZAWI7JmaMtDkILuMLYQ+jXb7p4K2FFW/1nN7C8QqgLLFI5AfjZUusgw==
"@tiptap/extension-heading@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.10.4.tgz#6df708f74f2f2d0716f6e3d32100c29c8be3fe68"
integrity sha512-7D0h0MIvE97Gx3Qwuo2xnPDK07WfCnyh4tpOPBOus4e1g6sgxVkwDwhbkYWiwvIrf4BUVJflnke/DEDCVp6/Eg==
"@tiptap/extension-history@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.11.0.tgz#fc04cb418e4bad6c659b101d48812e43f04c3bbc"
integrity sha512-eEUEDoOtS17AHVEPbGfZ+x2L5A87SiIsppWYTkpfIH/8EnVQmzu+3i1tcT9cWvHC31d9JTG7TDptVuuHr30TJw==
"@tiptap/extension-history@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.10.4.tgz#58e1b819149354538526475896a9904e954d02a6"
integrity sha512-fg6BNxbpMMtgKaiNI/GLcCzkxIQMwSYBhO9LA0CxLvmsWGU+My4r9W3DK6HwNoRJ9+6OleDPSLo1P73fbSTtEA==
"@tiptap/extension-horizontal-rule@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.0.tgz#ba7dc213f93cf3920ebcff246a8aaf845cb0b683"
integrity sha512-ZbkILwmcccmwQB2VTA/dzHRMB+xoJQ8UJdafcUiaAUlQfvDgl898+AYMa2GRTZkLPvzCKjXMC9hybSyy54Lz3Q==
"@tiptap/extension-horizontal-rule@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.10.4.tgz#a2099c156c4c691f4951def225e1102ad1f69f22"
integrity sha512-s9ycm/BOGoW3L0Epnj541vdngHbFbMM488HoODd1CmVSw1C+wBWFgsukgqKjlyE3VGfZXuSb1ur9zinW0RiLJQ==
"@tiptap/extension-image@^2.1.13":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.11.0.tgz#e9611a6a032804279f9b12a483a7afe696358a3f"
integrity sha512-R+JkK5ocX35ag1c42aAw6rcb9QlLUBB0ju8A7b+8qZXN5yWKE0yO/oixYFmnZN7WSnBYtzuCVDX8cvRG+BPbgA==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.10.4.tgz#577a0e9b92be845b91165d29136327c951bb967e"
integrity sha512-fPdAqP4M1zwz5jyrQNIEL4OvvGeJso45svaaBLV342yRLOpbVIgAp/RsuWSGDQTUWoGhdkHdIrbH2bUGNEbMBg==
"@tiptap/extension-italic@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.11.0.tgz#fe36d1aac51d8d5f8c276116a1e4408396440d8e"
integrity sha512-T+jjS0gOsvNzQXVTSArmUp/kt2R9OikPQaV1DI60bfjO0rknOgtG0tbwZmfbugzwc07RbpxOYFy3vBxMLDsksA==
"@tiptap/extension-italic@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.10.4.tgz#000cd53a5d54efe1c8bfa51a2bd6003268dcd01d"
integrity sha512-8MIQ+wsbyxNCZDCFTVTOXrS2AvFyOhtlBNgVU2+6r6xnJV4AcfEA3qclysqrjOlL117ped/nzDeoB0AeX0CI+Q==
"@tiptap/extension-list-item@^2.1.13", "@tiptap/extension-list-item@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.11.0.tgz#15889fd8217b998bfef78cd9079c8405b5a9abcd"
integrity sha512-Jikcg0fccpM13a3hAFLtguMcpVg4eMWI8NnC0aUULD9rFhvWZQYQYQuoK3fO6vQrAQpNhsV4oa0dfSq1btu9kg==
"@tiptap/extension-list-item@^2.1.13", "@tiptap/extension-list-item@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.10.4.tgz#3ea8e597567d448b00ac4d7b0ef9d1b91bc1d306"
integrity sha512-8K3WUD5fPyw2poQKnJGGm7zlfeIbpld92+SRF4M9wkp95EzvgexTlodvxlrL3i8zKXcQQVyExWA8kCcGPFb9bA==
"@tiptap/extension-mention@^2.1.13":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-2.11.0.tgz#515f831975188ce33e3c0951f94a34396a3f35cd"
integrity sha512-5/Yk2rTpsoIZaNyo4f+CgsCCkQkSiNAp24HOvvCm9Dp9w1gIFm6y6dSj5RYqzEucGjOkoaBbfMcm1QxKWIj6/A==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-2.10.4.tgz#a01856024f948daf2e1f9ed820059f1bdb15ff73"
integrity sha512-pVouKWxSVQSy4zn6HrljPIP1AG826gkm/w18Asi8QnZvR0AMqGLh9q7qd9Kc0j8NKoCzlzK8hECGlYPEaBldow==
"@tiptap/extension-ordered-list@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.0.tgz#29d4afab66e85c701a7ab5b86ea1cc1f5ae7e3c7"
integrity sha512-i6pNsDHA2QvBAebwjAuvhHKwz+bZVJ929PCIJaN8mxg0ldiAmFbAsf+rwIIFHWogMp+5xEX2RBzux20usNVZ9w==
"@tiptap/extension-ordered-list@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.10.4.tgz#3da20e5087329a9999837b5860954bd14b14ef60"
integrity sha512-NaeEu+qFG2O0emc8WlwOM7DKNKOaqHWuNkuKrrmQzslgL+UQSEGlGMo6NEJ5sLLckPBDpIa0MuRm30407JE+cg==
"@tiptap/extension-paragraph@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.11.0.tgz#936bfbc6781acd5ac0dba6bcdc2fa67514ce6164"
integrity sha512-xLNC05An3SQq0bVHJtOTLa8As5r6NxDZFpK0NZqO2hTq/fAIRL/9VPeZ8E0tziXULwIvIPp+L0Taw3TvaUkRUg==
"@tiptap/extension-paragraph@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.10.4.tgz#e2c94eaee7c69b7c38f98abd50927a8a6811dc49"
integrity sha512-SRNVhT8OXqjpZtcyuOtofbtOpXXFrQrjqqCc/yXebda//2SfUTOvB16Lss77vQOWi6xr7TF1mZuowJgSTkcczw==
"@tiptap/extension-placeholder@^2.3.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.11.0.tgz#a330df0bc791df82b3a84490031a8db9a68f4061"
integrity sha512-ee8vz51pW6H+1rEDMFg2FnBs2Tj5rUHlJ1JgD7Dcp3+89SVHGB3UILGfbNpAnHZvhmsTY3NcfPAcZZ80QfQFMQ==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.10.4.tgz#706e30941e3232894db647ab4254e8ba65f54920"
integrity sha512-leWG4xP7cvddR6alGZS7yojOh9941bxehgAeQDLlEisaJcNa2Od5Vbap2zipjc5sXMxZakQVChL27oH1wWhHkQ==
"@tiptap/extension-strike@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.11.0.tgz#8507cfb501f4950d869f8ddd1adb47b66680765f"
integrity sha512-71i2IZT58kY2ohlhyO+ucyAioNNCkNkuPkrVERc9lXhmcCKOff5y6ekDHQHO2jNjnejkVE5ibyDO3Z7RUXjh1A==
"@tiptap/extension-strike@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.10.4.tgz#8c450d1cb26f6cb0b79403aed2bf0bd6d1d0cd98"
integrity sha512-OibipsomFpOJWTPVX/z4Z53HgwDA93lE/loHGa+ONJfML1dO6Zd6UTwzaVO1/g8WOwRgwkYu/6JnhxLKRlP8Lg==
"@tiptap/extension-task-item@^2.1.13":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.11.0.tgz#ff97f10bf39d6c27fd3f3f1ee3987d7015304c11"
integrity sha512-qu6VuRc8qF80Bwr82CItFcrKtC67LJkwpxESLEIi42zWZ5sXF/3DJEPPS/4Kk+nAc9UCBoEMFAULibPq7rRl/w==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.10.4.tgz#afc59e1632db1d7d1d8c2ff4e897a638337a949f"
integrity sha512-ucKGXdHdHCBanIJTB/nhmQ3iIL6BcSVKr7mN5BGEu6sSLYROflX7lmnMPVIRcTKJz+FGJeR6AqPFVagZAXVkGQ==
"@tiptap/extension-task-list@^2.1.13":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.11.0.tgz#2f197a70aafc0c23163ed70aa314cc0300534cf6"
integrity sha512-+dZRjeXLXxyliFt3J7uQADxfOwi6ntyepVM+ri1rnmIaqVZUHJbUFodOc0LivI+Z5iZZ10u3TId8gehqWJHD+w==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.10.4.tgz#4c22d70a3d7147ef5919d368ee619dc94654df16"
integrity sha512-21bFlHlvGr5hsXUEug9p+BWPLqdziFS/4mGG6nUnrSDI1e4eEC86WZczsG+If6FEpjcCS9Eb2RHgqaA4VoJEqg==
"@tiptap/extension-text-align@^2.8.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text-align/-/extension-text-align-2.11.0.tgz#b90b86ee3f6e14c9abf186cb8ab6ced20c0df14d"
integrity sha512-VRXBqO17po6ddqhoWLBa2aCX/tqHdzdKPLfjnBy1fF8hjQKbidzjMWhb4CMm31ApvJjKK/DTkM3EnyYS/XDhng==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text-align/-/extension-text-align-2.10.4.tgz#ffd9cd61bf7f00c6ed4b3b5f377a7d5bed56550b"
integrity sha512-rt2Hz3N081QAgkKKSMpyDZTKorBmXKpeHkYIw+ArVuvBYhi8x5wVyZgZ2SIMW9A5G4rx1M0czn7xNi+/P3NopQ==
"@tiptap/extension-text-style@^2.11.0", "@tiptap/extension-text-style@^2.7.1":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text-style/-/extension-text-style-2.11.0.tgz#2acb6207fe6acdb5479384682846980be15caa80"
integrity sha512-vuA16wMZ6J3fboL7FObwV2f5uN9Vg0WYmqU7971vxzJyaRj9VE1eeH8Kh5fq4RgwDzc13MZGvZZV4HcE1R8o8A==
"@tiptap/extension-text-style@^2.10.4", "@tiptap/extension-text-style@^2.7.1":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text-style/-/extension-text-style-2.10.4.tgz#07af922c266fef7da13cc83befbbd5da512b7402"
integrity sha512-ibq7avkcwHyUSG53Hf+P31rrwsKVbbiqbWZM4kXC7M2X3iUwFrtvaa+SWzyWQfE1jl2cCrD1+rfSkj/alcOKGg==
"@tiptap/extension-text@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.11.0.tgz#56ad3d9b14a34f67f7f8b82a499ef7a9a10ef0a6"
integrity sha512-LcyrP+7ZEVx3YaKzjMAeujq+4xRt4mZ3ITGph2CQ4vOKFaMI8bzSR909q18t7Qyyvek0a9VydEU1NHSaq4G5jw==
"@tiptap/extension-text@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.10.4.tgz#ca5c7618ec122cfd72399b8c593023cf4a5829d5"
integrity sha512-wPdVxCHrIS9S+8n08lgyyqRZPj9FBbyLlFt74/lV5yBC3LOorq1VKdjrTskmaj4jud7ImXoKDyBddAYTHdJ1xw==
"@tiptap/extension-underline@^2.1.13":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-2.11.0.tgz#473025d3ed01953fb2c23f8060ea90be4c231faa"
integrity sha512-DE1piq441y1+9Aj1pvvuq1dcc5B2HZ2d1SPtO4DTMjCxrhok12biTkMxxq0q1dzA5/BouLlUW6WTPpinhmrUWA==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-2.10.4.tgz#5892a3dc7997818c0bbc0dde4e48b036d6786975"
integrity sha512-KhlCndQFMe/Gsz+3qkVn9z1utDy8y1igvdePijMjA5B8PTu0hPs2Q1d6szfLTBdtoFNkCokknxzXhSY0OFJEyQ==
"@tiptap/html@^2.3.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/html/-/html-2.11.0.tgz#dd0b4195a07bbdca27aa13325b8ebac41480a6a0"
integrity sha512-9+8eSeey3gm6vMtbt+uKZfkvtwsWr577lhtTeGUsoThim9zyBnbhzHc1dQw2m1RefOoPcODrnnQPvi5nvA84Cw==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/html/-/html-2.10.4.tgz#f4cf5b93a9e5d47538c9935912006e51bbb513dc"
integrity sha512-s9gWJy2Z+mfG5s8ewodv6d/iZq3bcc6osT9zsIvQcfoHh4j6Nq/DcIuZ/7AodgRFGZrIAEu4X1U4K2/Fk+gplA==
dependencies:
zeed-dom "^0.15.1"
"@tiptap/pm@^2.1.13", "@tiptap/pm@^2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.11.0.tgz#c2ccb0c9b99fd5915d9390e90d3ab9e5fb408cd8"
integrity sha512-4RU6bpODkMY+ZshzdRFcuUc5jWlMW82LWXR6UOsHK/X/Mav41ZFS0Cyf+hQM6gxxTB09YFIICmGpEpULb+/CuA==
"@tiptap/pm@^2.1.13", "@tiptap/pm@^2.10.4":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.10.4.tgz#28909a528a3ac59e4f62055d5b9b2890f6f386fc"
integrity sha512-pZ4NEkRtYoDLe0spARvXZ1N3hNv/5u6vfPdPtEbmNpoOSjSNqDC1kVM+qJY0iaCYpxbxcv7cxn3kBumcFLQpJQ==
dependencies:
prosemirror-changeset "^2.2.1"
prosemirror-collab "^1.3.1"
@@ -4011,47 +4011,47 @@
prosemirror-view "^1.37.0"
"@tiptap/react@^2.1.13":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.11.0.tgz#28b9ca64bdeb6302eaca1941ef792e3acea2e40e"
integrity sha512-AALzHbqNq/gerJpkbXmN2OXFmHAs2bQENH7rXbnH70bpxVdIfQVtvjK4dIb+cQQvAuTWZvhsISnTrFY2BesT3Q==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.10.4.tgz#cdd898ff2f962ecd7be593606507f95b90fab2f8"
integrity sha512-JTeqDB+xgjo46QC9ILRXe2TcSfxKVRwhZ3vDvYoemN7giRk5a/WsCF1VQIT1fax+tCl6kfv3U1f4Mkx0DkbPkA==
dependencies:
"@tiptap/extension-bubble-menu" "^2.11.0"
"@tiptap/extension-floating-menu" "^2.11.0"
"@tiptap/extension-bubble-menu" "^2.10.4"
"@tiptap/extension-floating-menu" "^2.10.4"
"@types/use-sync-external-store" "^0.0.6"
fast-deep-equal "^3"
use-sync-external-store "^1"
"@tiptap/starter-kit@^2.1.13":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.11.0.tgz#3b4a9ca9daaffed3abd030d4cd72e8d9d3eb0649"
integrity sha512-lrYmkeaAFiuUjN5nGnCowdjponrsR7eRmeTf/15/5oZsNrMN7t/fvPb014AqhG/anNasa0ism4CKZns3D+4pKQ==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.10.4.tgz#37f95fae7ea36b29825720706c49dd0533274b0b"
integrity sha512-tu/WCs9Mkr5Nt8c3/uC4VvAbQlVX0OY7ygcqdzHGUeG9zP3twdW7o5xM3kyDKR2++sbVzqu5Ll5qNU+1JZvPGQ==
dependencies:
"@tiptap/core" "^2.11.0"
"@tiptap/extension-blockquote" "^2.11.0"
"@tiptap/extension-bold" "^2.11.0"
"@tiptap/extension-bullet-list" "^2.11.0"
"@tiptap/extension-code" "^2.11.0"
"@tiptap/extension-code-block" "^2.11.0"
"@tiptap/extension-document" "^2.11.0"
"@tiptap/extension-dropcursor" "^2.11.0"
"@tiptap/extension-gapcursor" "^2.11.0"
"@tiptap/extension-hard-break" "^2.11.0"
"@tiptap/extension-heading" "^2.11.0"
"@tiptap/extension-history" "^2.11.0"
"@tiptap/extension-horizontal-rule" "^2.11.0"
"@tiptap/extension-italic" "^2.11.0"
"@tiptap/extension-list-item" "^2.11.0"
"@tiptap/extension-ordered-list" "^2.11.0"
"@tiptap/extension-paragraph" "^2.11.0"
"@tiptap/extension-strike" "^2.11.0"
"@tiptap/extension-text" "^2.11.0"
"@tiptap/extension-text-style" "^2.11.0"
"@tiptap/pm" "^2.11.0"
"@tiptap/core" "^2.10.4"
"@tiptap/extension-blockquote" "^2.10.4"
"@tiptap/extension-bold" "^2.10.4"
"@tiptap/extension-bullet-list" "^2.10.4"
"@tiptap/extension-code" "^2.10.4"
"@tiptap/extension-code-block" "^2.10.4"
"@tiptap/extension-document" "^2.10.4"
"@tiptap/extension-dropcursor" "^2.10.4"
"@tiptap/extension-gapcursor" "^2.10.4"
"@tiptap/extension-hard-break" "^2.10.4"
"@tiptap/extension-heading" "^2.10.4"
"@tiptap/extension-history" "^2.10.4"
"@tiptap/extension-horizontal-rule" "^2.10.4"
"@tiptap/extension-italic" "^2.10.4"
"@tiptap/extension-list-item" "^2.10.4"
"@tiptap/extension-ordered-list" "^2.10.4"
"@tiptap/extension-paragraph" "^2.10.4"
"@tiptap/extension-strike" "^2.10.4"
"@tiptap/extension-text" "^2.10.4"
"@tiptap/extension-text-style" "^2.10.4"
"@tiptap/pm" "^2.10.4"
"@tiptap/suggestion@^2.0.13":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.11.0.tgz#826140c6f1a76af2d91713dafb4b142c803eabef"
integrity sha512-f+KcczhzEEy2f7/0N/RSID+Z6NjxCX6ab26NLfWZxdaEm/J+vQ2Pqh/e5Z59vMfKiC0DJXVcO0rdv2LBh23qDw==
version "2.10.4"
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.10.4.tgz#732320b65bc55bcc2a33a8f32072f9478ffb7436"
integrity sha512-7Bzcn1REA7OmVRxiMF2kVK9EhosXotdLAGaEvSbn4zQtHCJG0tREuYvPy53LGzVuPkBDR6Pf6sp1QbGvSne/8g==
"@todesktop/client-core@^1.12.4":
version "1.12.4"
@@ -4731,15 +4731,15 @@
integrity sha512-FZJgC5Bxuqg7Rhsm/bx6gAruHHhDQ55r+s0JhDh8CQ16fD7NsJJ+p8YMMQDhSQoIrSmjpqqYWA96oQVMNkjRyA==
"@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/eslint-plugin@^8.6.0":
version "8.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz#2b1e1b791e21d5fc27ddc93884db066444f597b5"
integrity sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==
version "8.18.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz#992e5ac1553ce20d0d46aa6eccd79dc36dedc805"
integrity sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==
dependencies:
"@eslint-community/regexpp" "^4.10.0"
"@typescript-eslint/scope-manager" "8.19.0"
"@typescript-eslint/type-utils" "8.19.0"
"@typescript-eslint/utils" "8.19.0"
"@typescript-eslint/visitor-keys" "8.19.0"
"@typescript-eslint/scope-manager" "8.18.1"
"@typescript-eslint/type-utils" "8.18.1"
"@typescript-eslint/utils" "8.18.1"
"@typescript-eslint/visitor-keys" "8.18.1"
graphemer "^1.4.0"
ignore "^5.3.1"
natural-compare "^1.4.0"
@@ -4762,14 +4762,14 @@
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser@^8.6.0":
version "8.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.19.0.tgz#f1512e6e5c491b03aabb2718b95becde22b15292"
integrity sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==
version "8.18.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.18.1.tgz#c258bae062778b7696793bc492249027a39dfb95"
integrity sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==
dependencies:
"@typescript-eslint/scope-manager" "8.19.0"
"@typescript-eslint/types" "8.19.0"
"@typescript-eslint/typescript-estree" "8.19.0"
"@typescript-eslint/visitor-keys" "8.19.0"
"@typescript-eslint/scope-manager" "8.18.1"
"@typescript-eslint/types" "8.18.1"
"@typescript-eslint/typescript-estree" "8.18.1"
"@typescript-eslint/visitor-keys" "8.18.1"
debug "^4.3.4"
"@typescript-eslint/scope-manager@5.62.0":
@@ -4780,13 +4780,13 @@
"@typescript-eslint/types" "5.62.0"
"@typescript-eslint/visitor-keys" "5.62.0"
"@typescript-eslint/scope-manager@8.19.0":
version "8.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz#28fa413a334f70e8b506a968531e0a7c9c3076dc"
integrity sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==
"@typescript-eslint/scope-manager@8.18.1":
version "8.18.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz#52cedc3a8178d7464a70beffed3203678648e55b"
integrity sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==
dependencies:
"@typescript-eslint/types" "8.19.0"
"@typescript-eslint/visitor-keys" "8.19.0"
"@typescript-eslint/types" "8.18.1"
"@typescript-eslint/visitor-keys" "8.18.1"
"@typescript-eslint/type-utils@5.62.0":
version "5.62.0"
@@ -4798,13 +4798,13 @@
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/type-utils@8.19.0":
version "8.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz#41abd7d2e4cf93b6854b1fe6cbf416fab5abf89f"
integrity sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==
"@typescript-eslint/type-utils@8.18.1":
version "8.18.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz#10f41285475c0bdee452b79ff7223f0e43a7781e"
integrity sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==
dependencies:
"@typescript-eslint/typescript-estree" "8.19.0"
"@typescript-eslint/utils" "8.19.0"
"@typescript-eslint/typescript-estree" "8.18.1"
"@typescript-eslint/utils" "8.18.1"
debug "^4.3.4"
ts-api-utils "^1.3.0"
@@ -4813,10 +4813,10 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
"@typescript-eslint/types@8.19.0":
version "8.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.19.0.tgz#a190a25c5484a42b81eaad06989579fdeb478cbb"
integrity sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==
"@typescript-eslint/types@8.18.1":
version "8.18.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.18.1.tgz#d7f4f94d0bba9ebd088de840266fcd45408a8fff"
integrity sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==
"@typescript-eslint/typescript-estree@5.62.0":
version "5.62.0"
@@ -4831,13 +4831,13 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@8.19.0":
version "8.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz#6b4f48f98ffad6597379951b115710f4d68c9ccb"
integrity sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==
"@typescript-eslint/typescript-estree@8.18.1":
version "8.18.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz#2a86cd64b211a742f78dfa7e6f4860413475367e"
integrity sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==
dependencies:
"@typescript-eslint/types" "8.19.0"
"@typescript-eslint/visitor-keys" "8.19.0"
"@typescript-eslint/types" "8.18.1"
"@typescript-eslint/visitor-keys" "8.18.1"
debug "^4.3.4"
fast-glob "^3.3.2"
is-glob "^4.0.3"
@@ -4859,15 +4859,15 @@
eslint-scope "^5.1.1"
semver "^7.3.7"
"@typescript-eslint/utils@8.19.0":
version "8.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.19.0.tgz#33824310e1fccc17f27fbd1030fd8bbd9a674684"
integrity sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==
"@typescript-eslint/utils@8.18.1":
version "8.18.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.18.1.tgz#c4199ea23fc823c736e2c96fd07b1f7235fa92d5"
integrity sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==
dependencies:
"@eslint-community/eslint-utils" "^4.4.0"
"@typescript-eslint/scope-manager" "8.19.0"
"@typescript-eslint/types" "8.19.0"
"@typescript-eslint/typescript-estree" "8.19.0"
"@typescript-eslint/scope-manager" "8.18.1"
"@typescript-eslint/types" "8.18.1"
"@typescript-eslint/typescript-estree" "8.18.1"
"@typescript-eslint/visitor-keys@5.62.0":
version "5.62.0"
@@ -4877,12 +4877,12 @@
"@typescript-eslint/types" "5.62.0"
eslint-visitor-keys "^3.3.0"
"@typescript-eslint/visitor-keys@8.19.0":
version "8.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz#dc313f735e64c4979c9073f51ffcefb6d9be5c77"
integrity sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==
"@typescript-eslint/visitor-keys@8.18.1":
version "8.18.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz#344b4f6bc83f104f514676facf3129260df7610a"
integrity sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==
dependencies:
"@typescript-eslint/types" "8.19.0"
"@typescript-eslint/types" "8.18.1"
eslint-visitor-keys "^4.2.0"
"@typescript/vfs@^1.5.0":
@@ -6202,9 +6202,9 @@ concurrently@^9.0.1:
yargs "^17.7.2"
consola@^3.2.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/consola/-/consola-3.3.3.tgz#0dd8a2314b0f7bf18a49064138ad685f3346543d"
integrity sha512-Qil5KwghMzlqd51UXM0b6fyaGHtOC22scxrwrz4A2882LyUMwQjnvaedN1HAeXzphspQ6CpHkzMAWxBTUruDLg==
version "3.3.1"
resolved "https://registry.yarnpkg.com/consola/-/consola-3.3.1.tgz#e60b4bfcd9e36cd766cc9afe2c93c14fe64c9356"
integrity sha512-GyKnPG3/I+a4RtJxgHquJXWr70g9I3c4NT3dvqh0LPHQP2nZFQBOBszb7a5u/pGzqr40AKplQA6UxM1BSynSXg==
constant-case@^3.0.4:
version "3.0.4"
@@ -6963,9 +6963,9 @@ ejs@^3.1.10:
jake "^10.8.5"
electron-to-chromium@^1.5.73:
version "1.5.76"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz#db20295c5061b68f07c8ea4dfcbd701485d94a3d"
integrity sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==
version "1.5.75"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz#bba96eabf0e8ca36324679caa38b982800acc87d"
integrity sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==
element-resize-detector@^1.1.9:
version "1.2.4"
@@ -7058,9 +7058,9 @@ error-ex@^1.3.1:
is-arrayish "^0.2.1"
es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6:
version "1.23.8"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.8.tgz#99754723118355d82fcef9ce4c90ccbcd5d2a285"
integrity sha512-lfab8IzDn6EpI1ibZakcgS6WsfEBiB+43cuJo+wgylx1xKXf+Sp+YR3vFuQwC/u3sxYwV8Cxe3B0DpVUu/WiJQ==
version "1.23.7"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.7.tgz#36e3da46fdb0d2ae3b9df4235e3a3167c1605b36"
integrity sha512-OygGC8kIcDhXX+6yAZRGLqwi2CmEXCbLQixeGUgYeR+Qwlppqmo7DIDr8XibtEBZp+fJcoYpoatp5qwLMEdcqQ==
dependencies:
array-buffer-byte-length "^1.0.2"
arraybuffer.prototype.slice "^1.0.4"
@@ -7097,10 +7097,8 @@ es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23
object-inspect "^1.13.3"
object-keys "^1.1.1"
object.assign "^4.1.7"
own-keys "^1.0.0"
regexp.prototype.flags "^1.5.3"
safe-array-concat "^1.1.3"
safe-push-apply "^1.0.0"
safe-regex-test "^1.1.0"
string.prototype.trim "^1.2.10"
string.prototype.trimend "^1.0.9"
@@ -7297,11 +7295,11 @@ escape-string-regexp@^1.0.5:
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
eslint-config-next@^14.1.0:
version "14.2.22"
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-14.2.22.tgz#c33cb4d1fc54673c8df475d6bca9f1f0249c69fe"
integrity sha512-4C26Xkqh5RWO9ieNOg7flfWsGiIfzblhXWQHUCa4wgswfjeFm4ku4M/Zc2IGBwA2BmrSn5kyJ8vt+JQg55g65Q==
version "14.2.21"
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-14.2.21.tgz#2bc6869d5c3ddc267143d41cb4a4a2bff8817e58"
integrity sha512-bi1Mn6LxWdQod9qvOBuhBhN4ZpBYH5DuyDunbZt6lye3zlohJyM0T5/oFokRPNl2Mqt3/+uwHxr8XKOkPe852A==
dependencies:
"@next/eslint-plugin-next" "14.2.22"
"@next/eslint-plugin-next" "14.2.21"
"@rushstack/eslint-patch" "^1.3.3"
"@typescript-eslint/eslint-plugin" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0"
"@typescript-eslint/parser" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0"
@@ -10040,11 +10038,11 @@ next-themes@^0.2.1:
integrity sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==
next@^14.2.20:
version "14.2.22"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.22.tgz#0cd664916ef4c725f31fa812d870348cffd0115b"
integrity sha512-Ps2caobQ9hlEhscLPiPm3J3SYhfwfpMqzsoCMZGWxt9jBRK9hoBZj2A37i8joKhsyth2EuVKDVJCTF5/H4iEDw==
version "14.2.21"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.21.tgz#f6da9e2abba1a0e4ca7a5273825daf06632554ba"
integrity sha512-rZmLwucLHr3/zfDMYbJXbw0ZeoBpirxkXuvsJbk7UPorvPYZhP7vq7aHbKnU7dQNCYIimRrbB2pp3xmf+wsYUg==
dependencies:
"@next/env" "14.2.22"
"@next/env" "14.2.21"
"@swc/helpers" "0.5.5"
busboy "1.6.0"
caniuse-lite "^1.0.30001579"
@@ -10052,15 +10050,15 @@ next@^14.2.20:
postcss "8.4.31"
styled-jsx "5.1.1"
optionalDependencies:
"@next/swc-darwin-arm64" "14.2.22"
"@next/swc-darwin-x64" "14.2.22"
"@next/swc-linux-arm64-gnu" "14.2.22"
"@next/swc-linux-arm64-musl" "14.2.22"
"@next/swc-linux-x64-gnu" "14.2.22"
"@next/swc-linux-x64-musl" "14.2.22"
"@next/swc-win32-arm64-msvc" "14.2.22"
"@next/swc-win32-ia32-msvc" "14.2.22"
"@next/swc-win32-x64-msvc" "14.2.22"
"@next/swc-darwin-arm64" "14.2.21"
"@next/swc-darwin-x64" "14.2.21"
"@next/swc-linux-arm64-gnu" "14.2.21"
"@next/swc-linux-arm64-musl" "14.2.21"
"@next/swc-linux-x64-gnu" "14.2.21"
"@next/swc-linux-x64-musl" "14.2.21"
"@next/swc-win32-arm64-msvc" "14.2.21"
"@next/swc-win32-ia32-msvc" "14.2.21"
"@next/swc-win32-x64-msvc" "14.2.21"
no-case@^3.0.4:
version "3.0.4"
@@ -10369,15 +10367,6 @@ overlap-area@^1.1.0:
dependencies:
"@daybrush/utils" "^1.7.1"
own-keys@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358"
integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==
dependencies:
get-intrinsic "^1.2.6"
object-keys "^1.1.1"
safe-push-apply "^1.0.0"
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@@ -10927,9 +10916,9 @@ postgres@^3.4.4:
integrity sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==
posthog-js@^1.131.3:
version "1.203.2"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.203.2.tgz#ccbdb50f906ab10124cd8a9d31bd5ab2df542184"
integrity sha512-3aLpEhM4i9sQQtobRmDttJ3rTW1+gwQ9HL7QiOeDueE2T7CguYibYS7weY1UhXMerx5lh1A7+szlOJTTibifLQ==
version "1.203.1"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.203.1.tgz#e1ce0ac65227d18b615f727ea4ec593694c6a33f"
integrity sha512-r/WiSyz6VNbIKEV/30+aD5gdrYkFtmZwvqNa6h9frl8hG638v098FrXaq3EYzMcCdkQf3phaZTDIAFKegpiTjw==
dependencies:
core-js "^3.38.1"
fflate "^0.4.8"
@@ -10937,9 +10926,9 @@ posthog-js@^1.131.3:
web-vitals "^4.2.0"
preact@^10.19.3:
version "10.25.4"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.25.4.tgz#c1d00bee9d7b9dcd06a2311d9951973b506ae8ac"
integrity sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==
version "10.25.3"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.25.3.tgz#22dfb072b088dda9a2bc6d4ca41bf46b588d325e"
integrity sha512-dzQmIFtM970z+fP9ziQ3yG4e3ULIbwZzJ734vaMVUTaKQ2+Ru1Ou/gjshOYVHCcd1rpAelC6ngjvjDXph98unQ==
prebuild-install@^7.1.1:
version "7.1.2"
@@ -12020,14 +12009,6 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-push-apply@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5"
integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==
dependencies:
es-errors "^1.3.0"
isarray "^2.0.5"
safe-regex-test@^1.0.3, safe-regex-test@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1"
@@ -12979,17 +12960,17 @@ tiptap-markdown@^0.8.9:
markdown-it-task-lists "^2.1.1"
prosemirror-markdown "^1.11.1"
tldts-core@^6.1.70:
version "6.1.70"
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.70.tgz#a954e93237ece2e1705b438600793c86a25f8c00"
integrity sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==
tldts-core@^6.1.69:
version "6.1.69"
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.69.tgz#079ffcac8a4407bc74567e292aecf30b943674e1"
integrity sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==
tldts@^6.1.32:
version "6.1.70"
resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.70.tgz#b571e5645ab9dc6f289453115d52602b8a384cfe"
integrity sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==
version "6.1.69"
resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.69.tgz#0fe1fcb1ad09510459693e72f96062cee2411f1f"
integrity sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==
dependencies:
tldts-core "^6.1.70"
tldts-core "^6.1.69"
tmp@^0.0.33:
version "0.0.33"