Skip to content

Commit

Permalink
refactor: conversion result for better success/error messages with ne…
Browse files Browse the repository at this point in the history
…w approach
  • Loading branch information
amanharwara committed Dec 10, 2023
1 parent 5686088 commit 60c8e72
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 118 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NativeFeatureIdentifier, NoteType } from '@standardnotes/features'
import { Converter } from '../Converter'
import { ConversionResult, Converter } from '../Converter'

type AegisData = {
db: {
Expand Down Expand Up @@ -45,7 +45,7 @@ export class AegisToAuthenticatorConverter implements Converter {
return false
}

convert: Converter['convert'] = async (file, { insertNote: createNote, readFileAsText }) => {
convert: Converter['convert'] = async (file, { insertNote, readFileAsText }) => {
const content = await readFileAsText(file)

const entries = this.parseEntries(content)
Expand All @@ -59,17 +59,22 @@ export class AegisToAuthenticatorConverter implements Converter {
const title = file.name.split('.')[0]
const text = JSON.stringify(entries)

return [
createNote({
createdAt,
updatedAt,
title,
text,
noteType: NoteType.Authentication,
editorIdentifier: NativeFeatureIdentifier.TYPES.TokenVaultEditor,
useSuperIfPossible: false,
}),
]
const note = await insertNote({
createdAt,
updatedAt,
title,
text,
noteType: NoteType.Authentication,
editorIdentifier: NativeFeatureIdentifier.TYPES.TokenVaultEditor,
useSuperIfPossible: false,
})

const successful: ConversionResult['successful'] = [note]

return {
successful,
errored: [],
}
}

parseEntries(data: string): AuthenticatorEntry[] | null {
Expand Down
10 changes: 9 additions & 1 deletion packages/ui-services/src/Import/Converter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { NoteType } from '@standardnotes/features'
import { DecryptedItemInterface, FileItem, ItemContent, NoteContent, SNNote, SNTag } from '@standardnotes/models'

export type ConversionResult = {
successful: DecryptedItemInterface[]
errored: {
name: string
error: Error
}[]
}

export interface Converter {
getImportType(): string

Expand All @@ -26,7 +34,7 @@ export interface Converter {
): Promise<void>
cleanupItems(items: DecryptedItemInterface<ItemContent>[]): Promise<void>
},
): Promise<void>
): Promise<ConversionResult>
}

export type InsertNoteFn = (options: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import { ContentType } from '@standardnotes/domain-core'
import { DecryptedTransferPayload, NoteContent, TagContent } from '@standardnotes/models'
import { SNNote, SNTag } from '@standardnotes/models'
import { EvernoteConverter, EvernoteResource } from './EvernoteConverter'
import { createTestResourceElement, enex, enexWithNoNoteOrTag } from './testData'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
Expand Down Expand Up @@ -33,26 +33,30 @@ describe('EvernoteConverter', () => {
const readFileAsText = async (file: File) => file as unknown as string

const dependencies: Parameters<Converter['convert']>[1] = {
createNote: ({ text }) =>
insertNote: async ({ text }) =>
({
content_type: ContentType.TYPES.Note,
content: {
text,
references: [],
},
}) as unknown as DecryptedTransferPayload<NoteContent>,
insertTag: ({ title }) =>
}) as unknown as SNNote,
insertTag: async ({ title }) =>
({
content_type: ContentType.TYPES.Tag,
content: {
title,
references: [],
},
}) as unknown as DecryptedTransferPayload<TagContent>,
}) as unknown as SNTag,
convertHTMLToSuper: (data) => data,
convertMarkdownToSuper: jest.fn(),
readFileAsText,
canUseSuper: false,
canUploadFiles: false,
uploadFile: async () => void 0,
linkItems: async () => void 0,
cleanupItems: async () => void 0,
}

it('should throw error if no note or tag in enex', () => {
Expand All @@ -64,46 +68,44 @@ describe('EvernoteConverter', () => {
it('should parse and strip html', async () => {
const converter = new EvernoteConverter(generateUuid)

const result = await converter.convert(enex as unknown as File, dependencies)

expect(result).not.toBeNull()
expect(result?.length).toBe(3)
expect(result?.[0].content_type).toBe(ContentType.TYPES.Note)
expect((result?.[0] as DecryptedTransferPayload<NoteContent>).content.text).toBe('This is a test.\nh e ')
expect(result?.[1].content_type).toBe(ContentType.TYPES.Note)
expect((result?.[1] as DecryptedTransferPayload<NoteContent>).content.text).toBe(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
)
expect(result?.[2].content_type).toBe(ContentType.TYPES.Tag)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.title).toBe('distant reading')
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references.length).toBe(2)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references[0].uuid).toBe(result?.[0].uuid)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references[1].uuid).toBe(result?.[1].uuid)
const { successful } = await converter.convert(enex as unknown as File, dependencies)

expect(successful).not.toBeNull()
expect(successful?.length).toBe(3)
expect(successful?.[0].content_type).toBe(ContentType.TYPES.Note)
expect((successful?.[0] as SNNote).content.text).toBe('This is a test.\nh e ')
expect(successful?.[1].content_type).toBe(ContentType.TYPES.Note)
expect((successful?.[1] as SNNote).content.text).toBe('Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
expect(successful?.[2].content_type).toBe(ContentType.TYPES.Tag)
expect((successful?.[2] as SNTag).content.title).toBe('distant reading')
expect((successful?.[2] as SNTag).content.references.length).toBe(2)
expect((successful?.[2] as SNTag).content.references[0].uuid).toBe(successful?.[0].uuid)
expect((successful?.[2] as SNTag).content.references[1].uuid).toBe(successful?.[1].uuid)
})

it('should parse and not strip html', async () => {
const converter = new EvernoteConverter(generateUuid)

const result = await converter.convert(enex as unknown as File, {
const { successful } = await converter.convert(enex as unknown as File, {
...dependencies,
canUseSuper: true,
})

expect(result).not.toBeNull()
expect(result?.length).toBe(3)
expect(result?.[0].content_type).toBe(ContentType.TYPES.Note)
expect((result?.[0] as DecryptedTransferPayload<NoteContent>).content.text).toBe(
expect(successful).not.toBeNull()
expect(successful?.length).toBe(3)
expect(successful?.[0].content_type).toBe(ContentType.TYPES.Note)
expect((successful?.[0] as SNNote).content.text).toBe(
'<p>This is a test.</p><ul></ul><ol></ol><font><span>h </span><span>e </span></font>',
)
expect(result?.[1].content_type).toBe(ContentType.TYPES.Note)
expect((result?.[1] as DecryptedTransferPayload<NoteContent>).content.text).toBe(
expect(successful?.[1].content_type).toBe(ContentType.TYPES.Note)
expect((successful?.[1] as SNNote).content.text).toBe(
'<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>',
)
expect(result?.[2].content_type).toBe(ContentType.TYPES.Tag)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.title).toBe('distant reading')
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references.length).toBe(2)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references[0].uuid).toBe(result?.[0].uuid)
expect((result?.[2] as DecryptedTransferPayload<TagContent>).content.references[1].uuid).toBe(result?.[1].uuid)
expect(successful?.[2].content_type).toBe(ContentType.TYPES.Tag)
expect((successful?.[2] as SNTag).content.title).toBe('distant reading')
expect((successful?.[2] as SNTag).content.references.length).toBe(2)
expect((successful?.[2] as SNTag).content.references[0].uuid).toBe(successful?.[0].uuid)
expect((successful?.[2] as SNTag).content.references[1].uuid).toBe(successful?.[1].uuid)
})

it('should convert lists to super format if applicable', () => {
Expand Down Expand Up @@ -152,7 +154,7 @@ describe('EvernoteConverter', () => {
const array = [mediaElement1, mediaElement2, mediaElement3]

const converter = new EvernoteConverter(generateUuid)
const replacedCount = converter.replaceMediaElementsWithResources(array, resources)
const replacedCount = converter.replaceMediaElementsWithResources(array, resources, false, dependencies.uploadFile)

expect(replacedCount).toBe(1)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import utc from 'dayjs/plugin/utc'
import { GenerateUuid } from '@standardnotes/services'
import MD5 from 'crypto-js/md5'
import Base64 from 'crypto-js/enc-base64'
import { Converter, UploadFileFn } from '../Converter'
import { ConversionResult, Converter, UploadFileFn } from '../Converter'
dayjs.extend(customParseFormat)
dayjs.extend(utc)

Expand Down Expand Up @@ -59,6 +59,9 @@ export class EvernoteConverter implements Converter {
})[0]
}

const successful: ConversionResult['successful'] = []
const errored: ConversionResult['errored'] = []

for (const [index, xmlNote] of Array.from(xmlNotes).entries()) {
const filesToPotentiallyCleanup: FileItem[] = []
try {
Expand Down Expand Up @@ -128,8 +131,11 @@ export class EvernoteConverter implements Converter {
useSuperIfPossible: canUseSuper,
})

successful.push(note)

for (const uploadedFile of uploadedFiles) {
await linkItems(note, uploadedFile)
successful.push(uploadedFile)
}

const xmlTags = xmlNote.getElementsByTagName('tag')
Expand All @@ -152,10 +158,19 @@ export class EvernoteConverter implements Converter {
}
} catch (error) {
console.error(error)
errored.push({
name: xmlNote.getElementsByTagName('title')?.[0]?.textContent || `${file.name} - Note #${index}`,
error: error as Error,
})
cleanupItems(filesToPotentiallyCleanup).catch(console.error)
continue
}
}

return {
successful,
errored,
}
}

getXmlStringFromContentElement(contentElement: Element) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

import { jsonTextContentData, htmlTestData, jsonListContentData } from './testData'
import { GoogleKeepConverter } from './GoogleKeepConverter'
import { ContentType, DecryptedTransferPayload, NoteContent } from '@standardnotes/snjs'
import { CreateNoteFn } from '../Converter'
import { ContentType, SNNote } from '@standardnotes/snjs'
import { InsertNoteFn } from '../Converter'

describe('GoogleKeepConverter', () => {
const createNote: CreateNoteFn = ({ title, text, createdAt, updatedAt, trashed, archived, pinned }) =>
const insertNote: InsertNoteFn = async ({ title, text, createdAt, updatedAt, trashed, archived, pinned }) =>
({
uuid: Math.random().toString(),
created_at: createdAt,
Expand All @@ -22,12 +22,12 @@ describe('GoogleKeepConverter', () => {
pinned,
references: [],
},
}) as unknown as DecryptedTransferPayload<NoteContent>
}) as unknown as SNNote

it('should parse json data', () => {
it('should parse json data', async () => {
const converter = new GoogleKeepConverter()

const textContent = converter.tryParseAsJson(jsonTextContentData, createNote, (md) => md)
const textContent = await converter.tryParseAsJson(jsonTextContentData, insertNote, (md) => md)

expect(textContent).not.toBeNull()
expect(textContent?.created_at).toBeInstanceOf(Date)
Expand All @@ -40,7 +40,7 @@ describe('GoogleKeepConverter', () => {
expect(textContent?.content.archived).toBe(false)
expect(textContent?.content.pinned).toBe(false)

const listContent = converter.tryParseAsJson(jsonListContentData, createNote, (md) => md)
const listContent = await converter.tryParseAsJson(jsonListContentData, insertNote, (md) => md)

expect(listContent).not.toBeNull()
expect(listContent?.created_at).toBeInstanceOf(Date)
Expand All @@ -54,15 +54,15 @@ describe('GoogleKeepConverter', () => {
expect(textContent?.content.pinned).toBe(false)
})

it('should parse html data', () => {
it('should parse html data', async () => {
const converter = new GoogleKeepConverter()

const result = converter.tryParseAsHtml(
const result = await converter.tryParseAsHtml(
htmlTestData,
{
name: 'note-2.html',
},
createNote,
insertNote,
(html) => html,
false,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DecryptedTransferPayload, NoteContent } from '@standardnotes/models'
import { SNNote } from '@standardnotes/models'
import { Converter, InsertNoteFn } from '../Converter'

type Content =
Expand Down Expand Up @@ -49,28 +49,27 @@ export class GoogleKeepConverter implements Converter {
) => {
const content = await readFileAsText(file)

const possiblePayloadFromJson = this.tryParseAsJson(content, createNote, convertMarkdownToSuper)
const note =
(await this.tryParseAsJson(content, createNote, convertMarkdownToSuper)) ||
(await this.tryParseAsHtml(content, file, createNote, convertHTMLToSuper, canUseSuper))

if (possiblePayloadFromJson) {
return [possiblePayloadFromJson]
}

const possiblePayloadFromHtml = this.tryParseAsHtml(content, file, createNote, convertHTMLToSuper, canUseSuper)

if (possiblePayloadFromHtml) {
return [possiblePayloadFromHtml]
if (note) {
return {
successful: [note],
errored: [],
}
}

throw new Error('Could not parse Google Keep backup file')
}

tryParseAsHtml(
async tryParseAsHtml(
data: string,
file: { name: string },
createNote: InsertNoteFn,
insertNote: InsertNoteFn,
convertHTMLToSuper: (html: string) => string,
canUseSuper: boolean,
): DecryptedTransferPayload<NoteContent> {
): Promise<SNNote> {
const rootElement = document.createElement('html')
rootElement.innerHTML = data

Expand Down Expand Up @@ -119,7 +118,7 @@ export class GoogleKeepConverter implements Converter {

const title = rootElement.getElementsByClassName('title')[0]?.textContent || file.name

return createNote({
return await insertNote({
createdAt: date,
updatedAt: date,
title: title,
Expand Down Expand Up @@ -150,11 +149,11 @@ export class GoogleKeepConverter implements Converter {
)
}

tryParseAsJson(
async tryParseAsJson(
data: string,
createNote: InsertNoteFn,
convertMarkdownToSuper: (md: string) => string,
): DecryptedTransferPayload<NoteContent> | null {
): Promise<SNNote | null> {
try {
const parsed = JSON.parse(data) as GoogleKeepJsonNote
if (!GoogleKeepConverter.isValidGoogleKeepJson(parsed)) {
Expand All @@ -172,7 +171,7 @@ export class GoogleKeepConverter implements Converter {
.join('\n')
}
text = convertMarkdownToSuper(text)
return createNote({
return await createNote({
createdAt: date,
updatedAt: date,
title: parsed.title,
Expand Down
Loading

0 comments on commit 60c8e72

Please sign in to comment.