Display import source data on documents (#6651)

* Display import source in Insights

* Ensure sourceMetadata is not returned on public requests

* Support createdByName

* Prefer display source name
This commit is contained in:
Tom Moor
2024-03-07 17:33:56 -07:00
committed by GitHub
parent 2d879d0939
commit df9f8cbabc
9 changed files with 111 additions and 4 deletions

View File

@@ -96,7 +96,16 @@ const DocumentMeta: React.FC<Props> = ({
</span> </span>
); );
} else if (createdAt === updatedAt) { } else if (createdAt === updatedAt) {
content = ( content = document.sourceMetadata ? (
<span>
{document.sourceMetadata.createdByName
? t("{{ userName }} created", {
userName: document.sourceMetadata.createdByName,
})
: t("Imported")}{" "}
<Time dateTime={createdAt} addSuffix />
</span>
) : (
<span> <span>
{lastUpdatedByCurrentUser {lastUpdatedByCurrentUser
? t("You created") ? t("You created")

View File

@@ -1,8 +1,13 @@
import { addDays, differenceInDays } from "date-fns"; import { addDays, differenceInDays } from "date-fns";
import i18n, { t } from "i18next"; import i18n, { t } from "i18next";
import capitalize from "lodash/capitalize";
import floor from "lodash/floor"; import floor from "lodash/floor";
import { action, autorun, computed, observable, set } from "mobx"; import { action, autorun, computed, observable, set } from "mobx";
import { ExportContentType, NotificationEventType } from "@shared/types"; import {
ExportContentType,
FileOperationFormat,
NotificationEventType,
} from "@shared/types";
import type { JSONObject, NavigationNode } from "@shared/types"; import type { JSONObject, NavigationNode } from "@shared/types";
import Storage from "@shared/utils/Storage"; import Storage from "@shared/utils/Storage";
import { isRTL } from "@shared/utils/rtl"; import { isRTL } from "@shared/utils/rtl";
@@ -56,6 +61,43 @@ export default class Document extends ParanoidModel {
@observable @observable
id: string; id: string;
/**
* The original data source of the document, if imported.
*/
sourceMetadata?: {
/**
* The type of importer that was used, if any. This can also be empty if an individual file was
* imported through drag-and-drop, for example.
*/
importType?: FileOperationFormat;
/** The date this document was imported. */
importedAt?: string;
/** The name of the user the created the original source document. */
createdByName?: string;
/** The name of the file this document was imported from. */
fileName?: string;
};
/**
* The name of the original data source, if imported.
*/
get sourceName() {
if (!this.sourceMetadata) {
return undefined;
}
switch (this.sourceMetadata.importType) {
case FileOperationFormat.MarkdownZip:
return "Markdown";
case FileOperationFormat.JSON:
return "JSON";
case FileOperationFormat.Notion:
return "Notion";
default:
return capitalize(this.sourceMetadata.importType);
}
}
/** /**
* The id of the collection that this document belongs to, if any. * The id of the collection that this document belongs to, if any.
*/ */

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { useHistory, useRouteMatch } from "react-router-dom"; import { useHistory, useRouteMatch } from "react-router-dom";
import styled from "styled-components"; import styled from "styled-components";
import { s } from "@shared/styles"; import { s } from "@shared/styles";
import { stringToColor } from "@shared/utils/color";
import User from "~/models/User"; import User from "~/models/User";
import Avatar from "~/components/Avatar"; import Avatar from "~/components/Avatar";
import { useDocumentContext } from "~/components/DocumentContext"; import { useDocumentContext } from "~/components/DocumentContext";
@@ -56,6 +57,20 @@ function Insights() {
> >
<div> <div>
<Content column> <Content column>
{document.sourceMetadata && (
<>
<Heading>{t("Source")}</Heading>
{
<Text as="p" type="secondary" size="small">
{t("Imported from {{ source }}", {
source:
document.sourceName ??
`${document.sourceMetadata.fileName}`,
})}
</Text>
}
</>
)}
<Heading>{t("Stats")}</Heading> <Heading>{t("Stats")}</Heading>
<Text as="p" type="secondary" size="small"> <Text as="p" type="secondary" size="small">
<List> <List>
@@ -108,6 +123,26 @@ function Insights() {
<Time dateTime={document.updatedAt} addSuffix />. <Time dateTime={document.updatedAt} addSuffix />.
</Text> </Text>
<ListSpacing> <ListSpacing>
{document.sourceMetadata?.createdByName && (
<ListItem
title={document.sourceMetadata?.createdByName}
image={
<Avatar
model={{
color: stringToColor(
document.sourceMetadata.createdByName
),
avatarUrl: null,
initial: document.sourceMetadata.createdByName[0],
}}
size={32}
/>
}
subtitle={t("Creator")}
border={false}
small
/>
)}
<PaginatedList <PaginatedList
aria-label={t("Contributors")} aria-label={t("Contributors")}
items={document.collaborators} items={document.collaborators}
@@ -118,7 +153,9 @@ function Insights() {
image={<Avatar model={model} size={32} />} image={<Avatar model={model} size={32} />}
subtitle={ subtitle={
model.id === document.createdBy?.id model.id === document.createdBy?.id
? t("Creator") ? document.sourceMetadata?.createdByName
? t("Imported")
: t("Creator")
: model.id === document.updatedBy?.id : model.id === document.updatedBy?.id
? t("Last edited") ? t("Last edited")
: t("Previously edited") : t("Previously edited")

View File

@@ -49,6 +49,8 @@ async function presentDocument(
} }
if (!options.isPublic) { if (!options.isPublic) {
const source = await document.$get("import");
data.collectionId = document.collectionId; data.collectionId = document.collectionId;
data.parentDocumentId = document.parentDocumentId; data.parentDocumentId = document.parentDocumentId;
data.createdBy = presentUser(document.createdBy); data.createdBy = presentUser(document.createdBy);
@@ -57,6 +59,14 @@ async function presentDocument(
data.templateId = document.templateId; data.templateId = document.templateId;
data.template = document.template; data.template = document.template;
data.insightsEnabled = document.insightsEnabled; data.insightsEnabled = document.insightsEnabled;
data.sourceMetadata = document.sourceMetadata
? {
importedAt: source?.createdAt ?? document.createdAt,
importType: source?.format,
createdByName: document.sourceMetadata.createdByName,
fileName: document.sourceMetadata?.fileName,
}
: undefined;
} }
return data; return data;

View File

@@ -125,6 +125,7 @@ export default class ExportJSONTask extends ExportTask {
title: document.title, title: document.title,
data: DocumentHelper.toProsemirror(document), data: DocumentHelper.toProsemirror(document),
createdById: document.createdById, createdById: document.createdById,
createdByName: document.createdBy.name,
createdByEmail: document.createdBy.email, createdByEmail: document.createdBy.email,
createdAt: document.createdAt.toISOString(), createdAt: document.createdAt.toISOString(),
updatedAt: document.updatedAt.toISOString(), updatedAt: document.updatedAt.toISOString(),

View File

@@ -78,6 +78,7 @@ export type StructuredImportData = {
publishedAt?: Date | null; publishedAt?: Date | null;
parentDocumentId?: string | null; parentDocumentId?: string | null;
createdById?: string; createdById?: string;
createdByName?: string;
createdByEmail?: string | null; createdByEmail?: string | null;
path: string; path: string;
mimeType: string; mimeType: string;
@@ -467,6 +468,7 @@ export default abstract class ImportTask extends BaseTask<Props> {
fileName: path.basename(item.path), fileName: path.basename(item.path),
mimeType: item.mimeType, mimeType: item.mimeType,
externalId: item.externalId, externalId: item.externalId,
createdByName: item.createdByName,
}, },
id: item.id, id: item.id,
title: item.title, title: item.title,

View File

@@ -467,6 +467,7 @@ export type DocumentJSONExport = {
title: string; title: string;
data: Record<string, any>; data: Record<string, any>;
createdById: string; createdById: string;
createdByName: string;
createdByEmail: string | null; createdByEmail: string | null;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;

View File

@@ -166,8 +166,9 @@
"{{ userName }} deleted": "{{ userName }} deleted", "{{ userName }} deleted": "{{ userName }} deleted",
"You archived": "You archived", "You archived": "You archived",
"{{ userName }} archived": "{{ userName }} archived", "{{ userName }} archived": "{{ userName }} archived",
"You created": "You created",
"{{ userName }} created": "{{ userName }} created", "{{ userName }} created": "{{ userName }} created",
"Imported": "Imported",
"You created": "You created",
"You published": "You published", "You published": "You published",
"{{ userName }} published": "{{ userName }} published", "{{ userName }} published": "{{ userName }} published",
"You saved": "You saved", "You saved": "You saved",
@@ -561,6 +562,8 @@
"Done editing": "Done editing", "Done editing": "Done editing",
"Restore version": "Restore version", "Restore version": "Restore version",
"No history yet": "No history yet", "No history yet": "No history yet",
"Source": "Source",
"Imported from {{ source }}": "Imported from {{ source }}",
"Stats": "Stats", "Stats": "Stats",
"{{ count }} minute read": "{{ count }} minute read", "{{ count }} minute read": "{{ count }} minute read",
"{{ count }} minute read_plural": "{{ count }} minute read", "{{ count }} minute read_plural": "{{ count }} minute read",

View File

@@ -148,6 +148,8 @@ export type SourceMetadata = {
fileName?: string; fileName?: string;
/** The original source mime type. */ /** The original source mime type. */
mimeType?: string; mimeType?: string;
/** The creator of the original external source. */
createdByName?: string;
/** An ID in the external source. */ /** An ID in the external source. */
externalId?: string; externalId?: string;
/** Whether the item was created through a trial license. */ /** Whether the item was created through a trial license. */