Skip to content

Commit

Permalink
chore: convert legacy tasks to TipTap
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Krick <[email protected]>
  • Loading branch information
mattkrick committed Nov 27, 2024
1 parent a8cfa03 commit e8bedff
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 26 deletions.
26 changes: 0 additions & 26 deletions packages/server/graphql/public/types/Task.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import AzureDevOpsIssueId from 'parabol-client/shared/gqlIds/AzureDevOpsIssueId'
import JiraServerIssueId from '~/shared/gqlIds/JiraServerIssueId'
import GitHubRepoId from '../../../../client/shared/gqlIds/GitHubRepoId'
import {isDraftJSContent} from '../../../../client/shared/tiptap/isDraftJSContent'
import GitLabServerManager from '../../../integrations/gitlab/GitLabServerManager'
import {IGetLatestTaskEstimatesQueryResult} from '../../../postgres/queries/generated/getLatestTaskEstimatesQuery'
import getSimilarTaskEstimate from '../../../postgres/queries/getSimilarTaskEstimate'
import insertTaskEstimate from '../../../postgres/queries/insertTaskEstimate'
import {GetIssueLabelsQuery, GetIssueLabelsQueryVariables} from '../../../types/githubTypes'
import {getUserId} from '../../../utils/authorization'
import {convertKnownDraftToTipTap} from '../../../utils/convertToTipTap'
import getGitHubRequest from '../../../utils/getGitHubRequest'
import getIssueLabels from '../../../utils/githubQueries/getIssueLabels.graphql'
import sendToSentry from '../../../utils/sendToSentry'
Expand All @@ -30,30 +28,6 @@ const Task: Omit<ReqResolvers<'Task'>, 'replies'> = {
return integration?.service ?? null
},

content: async ({content}) => {
// cheaply check to see if it might be draft-js content
if (!content.includes('entityMap')) return content

// actually check if it's draft-js content
const contentJSON = JSON.parse(content)
if (!isDraftJSContent(contentJSON)) return content

// this is Draft-JS Content. convert it, save it, send it down
const tipTapContent = convertKnownDraftToTipTap(contentJSON)
const contentStr = JSON.stringify(tipTapContent)

// HACK we shouldn't be writing to the DB in a query,
// but we're doing it here just until we can migrate all tasks over to TipTap
// const pg = getKysely()
// await pg
// .updateTable('Task')
// .set({
// content: contentStr
// })
// .where('id', '=', taskId)
// .execute()
return contentStr
},
createdByUser: ({createdBy}, _args, {dataLoader}) => {
return dataLoader.get('users').loadNonNull(createdBy)
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {mergeAttributes} from '@tiptap/core'
import BaseLink from '@tiptap/extension-link'
import Mention from '@tiptap/extension-mention'
import {generateJSON} from '@tiptap/html'
import StarterKit from '@tiptap/starter-kit'
import {convertFromRaw, RawDraftContentState} from 'draft-js'
import {Options, stateToHTML} from 'draft-js-export-html'
import type {Kysely} from 'kysely'

export const serverTipTapExtensions = [
StarterKit,
Mention.configure({
renderText({node}) {
return node.attrs.label
},
renderHTML({options, node}) {
return ['span', options.HTMLAttributes, `${node.attrs.label ?? node.attrs.id}`]
}
}),
Mention.extend({name: 'taskTag'}).configure({
renderHTML({options, node}) {
return ['span', options.HTMLAttributes, `#${node.attrs.id}`]
}
}),
BaseLink.extend({
parseHTML() {
return [{tag: 'a[href]:not([data-type="button"]):not([href *= "javascript:" i])'}]
},

renderHTML({HTMLAttributes}) {
return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {class: 'link'}), 0]
}
})
]

const getNameFromEntity = (content: RawDraftContentState, userId: string) => {
const {blocks, entityMap} = content
const entityKey = Number(
Object.keys(entityMap).find((key) => entityMap[key]!.data?.userId === userId)
)
for (let i = 0; i < blocks.length; i++) {
const block = blocks[i]!
const {entityRanges, text} = block
const entityRange = entityRanges.find((range) => range.key === entityKey)
if (!entityRange) continue
const {length, offset} = entityRange
return text.slice(offset, offset + length)
}
console.log('found unknown for', userId, JSON.stringify(content))
return 'Unknown User'
}

export const convertKnownDraftToTipTap = (content: RawDraftContentState) => {
const contentState = convertFromRaw(content)
const options: Options = {
entityStyleFn: (entity) => {
const entityType = entity.getType().toLowerCase()
const data = entity.getData()
if (entityType === 'tag') {
return {
element: 'span',
attributes: {
'data-id': data.value,
'data-type': 'taskTag'
}
}
}
if (entityType === 'mention') {
const label = getNameFromEntity(content, data.userId)
return {
element: 'span',
attributes: {
'data-id': data.userId.toWellFormed(),
'data-label': label.toWellFormed(),
'data-type': 'mention'
}
}
}
return
}
}
const html = stateToHTML(contentState, options)
const json = generateJSON(html, serverTipTapExtensions)
return json
}

export async function up(db: Kysely<any>): Promise<void> {
let lastId = ''

for (let i = 0; i < 1e6; i++) {
const tasks = await db
.selectFrom('Task')
.select(['id', 'content'])
.where('id', '>', lastId)
.orderBy('id asc')
.limit(1000)
.execute()
console.log('converting tasks', i * 1000)
if (tasks.length === 0) break
const updatePromises = [] as Promise<any>[]
for (const task of tasks) {
const {id, content} = task
if ('blocks' in content) {
// this is draftjs
const tipTapContent = convertKnownDraftToTipTap(content)
const contentStr = JSON.stringify(tipTapContent)
const doPromise = async () => {
try {
return await db
.updateTable('Task')
.set({content: contentStr})
.where('id', '=', id)
.execute()
} catch (e) {
console.log('GOT ERR', id, contentStr, e)
throw e
}
}
updatePromises.push(doPromise())
}
}
await Promise.all(updatePromises)
lastId = tasks.at(-1)!.id
}
}

export async function down(): Promise<void> {
// noop
}

0 comments on commit e8bedff

Please sign in to comment.