Revert "[WEB-1435] dev: conflict free issue descriptions (#5912)" (#6000)

This reverts commit e9680cab74.
This commit is contained in:
Aaryan Khandelwal 2024-11-15 17:13:31 +05:30 committed by GitHub
parent e9680cab74
commit 9408e92e44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 361 additions and 1469 deletions

View file

@ -0,0 +1,59 @@
import { getSchema } from "@tiptap/core";
import { generateHTML, generateJSON } from "@tiptap/html";
import { prosemirrorJSONToYDoc, yXmlFragmentToProseMirrorRootNode } from "y-prosemirror";
import * as Y from "yjs"
// plane editor
import { CoreEditorExtensionsWithoutProps, DocumentEditorExtensionsWithoutProps } from "@plane/editor/lib";
const DOCUMENT_EDITOR_EXTENSIONS = [
...CoreEditorExtensionsWithoutProps,
...DocumentEditorExtensionsWithoutProps,
];
const documentEditorSchema = getSchema(DOCUMENT_EDITOR_EXTENSIONS);
export const getAllDocumentFormatsFromBinaryData = (description: Uint8Array): {
contentBinaryEncoded: string;
contentJSON: object;
contentHTML: string;
} => {
// encode binary description data
const base64Data = Buffer.from(description).toString("base64");
const yDoc = new Y.Doc();
Y.applyUpdate(yDoc, description);
// convert to JSON
const type = yDoc.getXmlFragment("default");
const contentJSON = yXmlFragmentToProseMirrorRootNode(
type,
documentEditorSchema
).toJSON();
// convert to HTML
const contentHTML = generateHTML(contentJSON, DOCUMENT_EDITOR_EXTENSIONS);
return {
contentBinaryEncoded: base64Data,
contentJSON,
contentHTML,
};
}
export const getBinaryDataFromHTMLString = (descriptionHTML: string): {
contentBinary: Uint8Array
} => {
// convert HTML to JSON
const contentJSON = generateJSON(
descriptionHTML ?? "<p></p>",
DOCUMENT_EDITOR_EXTENSIONS
);
// convert JSON to Y.Doc format
const transformedData = prosemirrorJSONToYDoc(
documentEditorSchema,
contentJSON,
"default"
);
// convert Y.Doc to Uint8Array format
const encodedData = Y.encodeStateAsUpdate(transformedData);
return {
contentBinary: encodedData
}
}

View file

@ -1,8 +1,8 @@
// plane editor
// helpers
import {
getAllDocumentFormatsFromDocumentEditorBinaryData,
getBinaryDataFromDocumentEditorHTMLString,
} from "@plane/editor/lib";
getAllDocumentFormatsFromBinaryData,
getBinaryDataFromHTMLString,
} from "@/core/helpers/page.js";
// services
import { PageService } from "@/core/services/page.service.js";
import { manualLogger } from "../helpers/logger.js";
@ -12,10 +12,12 @@ export const updatePageDescription = async (
params: URLSearchParams,
pageId: string,
updatedDescription: Uint8Array,
cookie: string | undefined
cookie: string | undefined,
) => {
if (!(updatedDescription instanceof Uint8Array)) {
throw new Error("Invalid updatedDescription: must be an instance of Uint8Array");
throw new Error(
"Invalid updatedDescription: must be an instance of Uint8Array",
);
}
const workspaceSlug = params.get("workspaceSlug")?.toString();
@ -23,7 +25,7 @@ export const updatePageDescription = async (
if (!workspaceSlug || !projectId || !cookie) return;
const { contentBinaryEncoded, contentHTML, contentJSON } =
getAllDocumentFormatsFromDocumentEditorBinaryData(updatedDescription);
getAllDocumentFormatsFromBinaryData(updatedDescription);
try {
const payload = {
description_binary: contentBinaryEncoded,
@ -31,7 +33,13 @@ export const updatePageDescription = async (
description: contentJSON,
};
await pageService.updateDescription(workspaceSlug, projectId, pageId, payload, cookie);
await pageService.updateDescription(
workspaceSlug,
projectId,
pageId,
payload,
cookie,
);
} catch (error) {
manualLogger.error("Update error:", error);
throw error;
@ -42,16 +50,26 @@ const fetchDescriptionHTMLAndTransform = async (
workspaceSlug: string,
projectId: string,
pageId: string,
cookie: string
cookie: string,
) => {
if (!workspaceSlug || !projectId || !cookie) return;
try {
const pageDetails = await pageService.fetchDetails(workspaceSlug, projectId, pageId, cookie);
const contentBinary = getBinaryDataFromDocumentEditorHTMLString(pageDetails.description_html ?? "<p></p>");
const pageDetails = await pageService.fetchDetails(
workspaceSlug,
projectId,
pageId,
cookie,
);
const { contentBinary } = getBinaryDataFromHTMLString(
pageDetails.description_html ?? "<p></p>",
);
return contentBinary;
} catch (error) {
manualLogger.error("Error while transforming from HTML to Uint8Array", error);
manualLogger.error(
"Error while transforming from HTML to Uint8Array",
error,
);
throw error;
}
};
@ -59,18 +77,28 @@ const fetchDescriptionHTMLAndTransform = async (
export const fetchPageDescriptionBinary = async (
params: URLSearchParams,
pageId: string,
cookie: string | undefined
cookie: string | undefined,
) => {
const workspaceSlug = params.get("workspaceSlug")?.toString();
const projectId = params.get("projectId")?.toString();
if (!workspaceSlug || !projectId || !cookie) return null;
try {
const response = await pageService.fetchDescriptionBinary(workspaceSlug, projectId, pageId, cookie);
const response = await pageService.fetchDescriptionBinary(
workspaceSlug,
projectId,
pageId,
cookie,
);
const binaryData = new Uint8Array(response);
if (binaryData.byteLength === 0) {
const binary = await fetchDescriptionHTMLAndTransform(workspaceSlug, projectId, pageId, cookie);
const binary = await fetchDescriptionHTMLAndTransform(
workspaceSlug,
projectId,
pageId,
cookie,
);
if (binary) {
return binary;
}

View file

@ -1,49 +0,0 @@
// plane editor
import {
applyUpdates,
convertBase64StringToBinaryData,
getAllDocumentFormatsFromRichTextEditorBinaryData,
} from "@plane/editor/lib";
export type TResolveConflictsRequestBody = {
original_document: string;
updates: string;
};
export type TResolveConflictsResponse = {
description_binary: string;
description_html: string;
description: object;
};
export const resolveDocumentConflicts = (body: TResolveConflictsRequestBody): TResolveConflictsResponse => {
const { original_document, updates } = body;
try {
// convert from base64 to buffer
const originalDocumentBuffer = original_document ? convertBase64StringToBinaryData(original_document) : null;
const updatesBuffer = updates ? convertBase64StringToBinaryData(updates) : null;
// decode req.body
const decodedOriginalDocument = originalDocumentBuffer ? new Uint8Array(originalDocumentBuffer) : new Uint8Array();
const decodedUpdates = updatesBuffer ? new Uint8Array(updatesBuffer) : new Uint8Array();
// resolve conflicts
let resolvedDocument: Uint8Array;
if (decodedOriginalDocument.length === 0) {
// use updates to create the document id original_description is null
resolvedDocument = applyUpdates(decodedUpdates);
} else {
// use original document and updates to resolve conflicts
resolvedDocument = applyUpdates(decodedOriginalDocument, decodedUpdates);
}
const { contentBinaryEncoded, contentHTML, contentJSON } =
getAllDocumentFormatsFromRichTextEditorBinaryData(resolvedDocument);
return {
description_binary: contentBinaryEncoded,
description_html: contentHTML,
description: contentJSON,
};
} catch (error) {
throw new Error("Internal server error");
}
};

View file

@ -5,13 +5,16 @@ import expressWs from "express-ws";
import * as Sentry from "@sentry/node";
import compression from "compression";
import helmet from "helmet";
// cors
import cors from "cors";
// core hocuspocus server
import { getHocusPocusServer } from "@/core/hocuspocus-server.js";
// helpers
import { errorHandler } from "@/core/helpers/error-handler.js";
import { logger, manualLogger } from "@/core/helpers/logger.js";
import { resolveDocumentConflicts, TResolveConflictsRequestBody } from "@/core/resolve-conflicts.js";
import { errorHandler } from "@/core/helpers/error-handler.js";
const app = express();
expressWs(app);
@ -26,7 +29,7 @@ app.use(
compression({
level: 6,
threshold: 5 * 1000,
})
}),
);
// Logging middleware
@ -59,25 +62,6 @@ router.ws("/collaboration", (ws, req) => {
}
});
app.post("/resolve-document-conflicts", (req, res) => {
const { original_document, updates } = req.body as TResolveConflictsRequestBody;
try {
if (original_document === undefined || updates === undefined) {
res.status(400).send({
message: "Missing required fields",
});
return;
}
const resolvedDocument = resolveDocumentConflicts(req.body);
res.status(200).json(resolvedDocument);
} catch (error) {
manualLogger.error("Error in /resolve-document-conflicts endpoint:", error);
res.status(500).send({
message: "Internal server error",
});
}
});
app.use(process.env.LIVE_BASE_PATH || "/live", router);
app.use((_req, res) => {
@ -98,7 +82,9 @@ const gracefulShutdown = async () => {
try {
// Close the HocusPocus server WebSocket connections
await HocusPocusServer.destroy();
manualLogger.info("HocusPocus server WebSocket connections closed gracefully.");
manualLogger.info(
"HocusPocus server WebSocket connections closed gracefully.",
);
// Close the Express server
liveServer.close(() => {