diff --git a/app/scenes/Settings/Import.tsx b/app/scenes/Settings/Import.tsx index 0d5d60dff..9819c19b0 100644 --- a/app/scenes/Settings/Import.tsx +++ b/app/scenes/Settings/Import.tsx @@ -83,7 +83,7 @@ function Import() { subtitle={t("Import pages from a Confluence instance")} actions={ } /> diff --git a/app/scenes/Settings/components/FileOperationListItem.tsx b/app/scenes/Settings/components/FileOperationListItem.tsx index 012143b19..0f2e475fb 100644 --- a/app/scenes/Settings/components/FileOperationListItem.tsx +++ b/app/scenes/Settings/components/FileOperationListItem.tsx @@ -21,6 +21,7 @@ const FileOperationListItem = ({ fileOperation, handleDelete }: Props) => { const user = useCurrentUser(); const theme = useTheme(); const stateMapping = { + complete: t("Completed"), creating: t("Processing"), expired: t("Expired"), uploading: t("Processing"), @@ -46,9 +47,7 @@ const FileOperationListItem = ({ fileOperation, handleDelete }: Props) => { image={iconMapping[fileOperation.state]} subtitle={ <> - {fileOperation.state !== "complete" && ( - <>{stateMapping[fileOperation.state]} •  - )} + {stateMapping[fileOperation.state]} •  {fileOperation.error && <>{fileOperation.error} • } {t(`{{userName}} requested`, { userName: diff --git a/server/commands/documentCreator.ts b/server/commands/documentCreator.ts index bc0fbbaa2..935ffe327 100644 --- a/server/commands/documentCreator.ts +++ b/server/commands/documentCreator.ts @@ -9,6 +9,7 @@ export default async function documentCreator({ collectionId, parentDocumentId, templateDocument, + importId, createdAt, // allows override for import updatedAt, @@ -26,6 +27,7 @@ export default async function documentCreator({ publish?: boolean; collectionId: string; parentDocumentId?: string; + importId?: string; templateDocument?: Document | null; publishedAt?: Date; template?: boolean; @@ -54,6 +56,7 @@ export default async function documentCreator({ template, templateId, publishedAt, + importId, title: templateDocument ? templateDocument.title : title, text: templateDocument ? templateDocument.text : text, }, diff --git a/server/migrations/20221112152649-import-document-relationship.js b/server/migrations/20221112152649-import-document-relationship.js new file mode 100644 index 000000000..fffde7621 --- /dev/null +++ b/server/migrations/20221112152649-import-document-relationship.js @@ -0,0 +1,48 @@ +"use strict"; + +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.sequelize.transaction(async (transaction) => { + await queryInterface.addColumn("documents", "importId", { + type: Sequelize.UUID, + allowNull: true, + references: { + model: "file_operations", + }, + transaction, + }); + await queryInterface.addColumn("collections", "importId", { + type: Sequelize.UUID, + allowNull: true, + references: { + model: "file_operations", + }, + transaction, + }); + await queryInterface.addIndex("documents", ["importId"], { + transaction + }); + await queryInterface.addIndex("collections", ["importId"], { + transaction + }); + }); + + }, + + async down(queryInterface) { + await queryInterface.sequelize.transaction(async (transaction) => { + await queryInterface.removeIndex("collections", ["importId"], { + transaction + }); + await queryInterface.removeIndex("documents", ["importId"], { + transaction + }); + await queryInterface.removeColumn("collections", "importId", { + transaction + }); + await queryInterface.removeColumn("documents", "importId", { + transaction + }); + }); + } +}; diff --git a/server/models/Collection.ts b/server/models/Collection.ts index 1426540e3..1660defa1 100644 --- a/server/models/Collection.ts +++ b/server/models/Collection.ts @@ -30,6 +30,7 @@ import type { NavigationNode, CollectionSort } from "~/types"; import CollectionGroup from "./CollectionGroup"; import CollectionUser from "./CollectionUser"; import Document from "./Document"; +import FileOperation from "./FileOperation"; import Group from "./Group"; import GroupUser from "./GroupUser"; import Team from "./Team"; @@ -272,6 +273,13 @@ class Collection extends ParanoidModel { // associations + @BelongsTo(() => FileOperation, "importId") + import: FileOperation | null; + + @ForeignKey(() => FileOperation) + @Column(DataType.UUID) + importId: string | null; + @HasMany(() => Document, "collectionId") documents: Document[]; diff --git a/server/models/Document.ts b/server/models/Document.ts index 84b4e9ee3..6cd0c794e 100644 --- a/server/models/Document.ts +++ b/server/models/Document.ts @@ -39,6 +39,7 @@ import { DocumentValidation } from "@shared/validations"; import slugify from "@server/utils/slugify"; import Backlink from "./Backlink"; import Collection from "./Collection"; +import FileOperation from "./FileOperation"; import Revision from "./Revision"; import Star from "./Star"; import Team from "./Team"; @@ -342,6 +343,13 @@ class Document extends ParanoidModel { // associations + @BelongsTo(() => FileOperation, "importId") + import: FileOperation | null; + + @ForeignKey(() => FileOperation) + @Column(DataType.UUID) + importId: string | null; + @BelongsTo(() => Document, "parentDocumentId") parentDocument: Document | null; diff --git a/server/presenters/fileOperation.ts b/server/presenters/fileOperation.ts index 3e9438bc1..13f568d4f 100644 --- a/server/presenters/fileOperation.ts +++ b/server/presenters/fileOperation.ts @@ -6,6 +6,7 @@ export default function present(data: FileOperation) { return { id: data.id, type: data.type, + format: data.format, name: data.collection?.name || path.basename(data.key || ""), state: data.state, error: data.error, diff --git a/server/queues/tasks/ImportTask.ts b/server/queues/tasks/ImportTask.ts index cb0dc30af..2155200c5 100644 --- a/server/queues/tasks/ImportTask.ts +++ b/server/queues/tasks/ImportTask.ts @@ -274,6 +274,7 @@ export default abstract class ImportTask extends BaseTask { }), createdById: fileOperation.userId, permission: CollectionPermission.ReadWrite, + importId: fileOperation.id, }, transaction, }); @@ -294,6 +295,7 @@ export default abstract class ImportTask extends BaseTask { createdById: fileOperation.userId, name, permission: CollectionPermission.ReadWrite, + importId: fileOperation.id, }, { transaction } ); @@ -357,6 +359,7 @@ export default abstract class ImportTask extends BaseTask { updatedAt: item.updatedAt ?? item.createdAt, publishedAt: item.updatedAt ?? item.createdAt ?? new Date(), parentDocumentId: item.parentDocumentId, + importId: fileOperation.id, user, ip, transaction, diff --git a/server/routes/api/fileOperations.ts b/server/routes/api/fileOperations.ts index 5d0ca697c..f0483de75 100644 --- a/server/routes/api/fileOperations.ts +++ b/server/routes/api/fileOperations.ts @@ -51,13 +51,13 @@ router.post( authorize(user, "manage", team); const [exports, total] = await Promise.all([ - await FileOperation.findAll({ + FileOperation.findAll({ where, order: [[sort, direction]], offset: ctx.state.pagination.offset, limit: ctx.state.pagination.limit, }), - await FileOperation.count({ + FileOperation.count({ where, }), ]); diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index 54bb2f1b0..51aa05cf0 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -586,6 +586,7 @@ "Please choose a single file to import": "Please choose a single file to import", "Your import is being processed, you can safely leave this page": "Your import is being processed, you can safely leave this page", "File not supported – please upload a valid ZIP file": "File not supported – please upload a valid ZIP file", + "Completed": "Completed", "Processing": "Processing", "Expired": "Expired", "Failed": "Failed", @@ -673,7 +674,7 @@ "Import data": "Import data", "Import pages exported from Notion": "Import pages exported from Notion", "Import pages from a Confluence instance": "Import pages from a Confluence instance", - "Coming soon": "Coming soon", + "Enterprise": "Enterprise", "Recent imports": "Recent imports", "Everyone that has signed into Outline appears here. It’s possible that there are other users who have access through {team.signinMethods} but haven’t signed in yet.": "Everyone that has signed into Outline appears here. It’s possible that there are other users who have access through {team.signinMethods} but haven’t signed in yet.", "Filter": "Filter",