mirror of
https://github.com/nasa/openmct.git
synced 2024-12-22 06:27:48 +00:00
Simple text export of Notebook (#6510)
* add simple prototype * tags and metadata now exported * add form for options * revert notebook * add simple e2e test * add test stubs * death to debug
This commit is contained in:
parent
20789601b4
commit
3007b28b0f
@ -377,4 +377,31 @@ test.describe('Notebook entry tests', () => {
|
|||||||
expect.soft(await sanitizedLink.count()).toBe(1);
|
expect.soft(await sanitizedLink.count()).toBe(1);
|
||||||
expect(await unsanitizedLink.count()).toBe(0);
|
expect(await unsanitizedLink.count()).toBe(0);
|
||||||
});
|
});
|
||||||
|
test('can export notebook as text', async ({ page }) => {
|
||||||
|
await nbUtils.enterTextEntry(page, `Foo bar entry`);
|
||||||
|
// Click on 3 Dot Menu
|
||||||
|
await page.locator('button[title="More options"]').click();
|
||||||
|
const downloadPromise = page.waitForEvent('download');
|
||||||
|
|
||||||
|
await page.getByRole('menuitem', { name: /Export Notebook as Text/ }).click();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
const download = await downloadPromise;
|
||||||
|
const readStream = await download.createReadStream();
|
||||||
|
const exportedText = await streamToString(readStream);
|
||||||
|
expect(exportedText).toContain('Foo bar entry');
|
||||||
|
});
|
||||||
|
test.fixme('can export multiple notebook entries as text ', async ({ page }) => {});
|
||||||
|
test.fixme('can export all notebook entry metdata', async ({ page }) => {});
|
||||||
|
test.fixme('can export all notebook tags', async ({ page }) => {});
|
||||||
|
test.fixme('can export all notebook snapshots', async ({ page }) => {});
|
||||||
|
|
||||||
|
async function streamToString(readable) {
|
||||||
|
let result = '';
|
||||||
|
for await (const chunk of readable) {
|
||||||
|
result += chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
155
src/plugins/notebook/actions/ExportNotebookAsTextAction.js
Normal file
155
src/plugins/notebook/actions/ExportNotebookAsTextAction.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import {saveAs} from 'saveAs';
|
||||||
|
import Moment from 'moment';
|
||||||
|
|
||||||
|
const UNKNOWN_USER = 'Unknown';
|
||||||
|
const UNKNOWN_TIME = 'Unknown';
|
||||||
|
|
||||||
|
export default class ExportNotebookAsTextAction {
|
||||||
|
|
||||||
|
constructor(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
|
||||||
|
this.cssClass = 'icon-export';
|
||||||
|
this.description = 'Exports notebook contents as a text file';
|
||||||
|
this.group = "action";
|
||||||
|
this.key = 'exportNotebookAsText';
|
||||||
|
this.name = 'Export Notebook as Text';
|
||||||
|
this.priority = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(objectPath) {
|
||||||
|
this.showForm(objectPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTagName(tagId, availableTags) {
|
||||||
|
const foundTag = availableTags.find(tag => tag.id === tagId);
|
||||||
|
if (foundTag) {
|
||||||
|
return foundTag.label;
|
||||||
|
} else {
|
||||||
|
return tagId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTagsForEntry(entry, domainObjectKeyString, annotations) {
|
||||||
|
const foundTags = [];
|
||||||
|
annotations.forEach(annotation => {
|
||||||
|
const target = annotation.targets?.[domainObjectKeyString];
|
||||||
|
if (target?.entryId === entry.id) {
|
||||||
|
annotation.tags.forEach(tag => {
|
||||||
|
if (!foundTags.includes(tag)) {
|
||||||
|
foundTags.push(tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return foundTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatTimeStamp(timestamp) {
|
||||||
|
if (timestamp) {
|
||||||
|
return `${Moment.utc(timestamp).format('YYYY-MM-DD HH:mm:ss')} UTC`;
|
||||||
|
} else {
|
||||||
|
return UNKNOWN_TIME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appliesTo(objectPath) {
|
||||||
|
const domainObject = objectPath[0];
|
||||||
|
const type = this.openmct.types.get(domainObject.type);
|
||||||
|
|
||||||
|
return type?.definition?.name === 'Notebook';
|
||||||
|
}
|
||||||
|
|
||||||
|
async onSave(changes, objectPath) {
|
||||||
|
const availableTags = this.openmct.annotation.getAvailableTags();
|
||||||
|
const identifier = objectPath[0].identifier;
|
||||||
|
const domainObject = await this.openmct.objects.get(identifier);
|
||||||
|
let foundAnnotations = [];
|
||||||
|
// only load annotations if there are tags
|
||||||
|
if (availableTags.length) {
|
||||||
|
foundAnnotations = await this.openmct.annotation.getAnnotations(domainObject.identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
let notebookAsText = `# ${domainObject.name}\n\n`;
|
||||||
|
|
||||||
|
if (changes.exportMetaData) {
|
||||||
|
const createdTimestamp = domainObject.created;
|
||||||
|
const createdBy = domainObject.createdBy ?? UNKNOWN_USER;
|
||||||
|
const modifiedBy = domainObject.modifiedBy ?? UNKNOWN_USER;
|
||||||
|
const modifiedTimestamp = domainObject.modified ?? domainObject.created;
|
||||||
|
notebookAsText += `Created on ${this.formatTimeStamp(createdTimestamp)} by user ${createdBy}\n\n`;
|
||||||
|
notebookAsText += `Updated on ${this.formatTimeStamp(modifiedTimestamp)} by user ${modifiedBy}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notebookSections = domainObject.configuration.sections;
|
||||||
|
const notebookEntries = domainObject.configuration.entries;
|
||||||
|
|
||||||
|
notebookSections.forEach(section => {
|
||||||
|
notebookAsText += `## ${section.name}\n\n`;
|
||||||
|
|
||||||
|
const notebookPages = section.pages;
|
||||||
|
|
||||||
|
notebookPages.forEach(page => {
|
||||||
|
notebookAsText += `### ${page.name}\n\n`;
|
||||||
|
|
||||||
|
const notebookPageEntries = notebookEntries[section.id]?.[page.id];
|
||||||
|
notebookPageEntries.forEach(entry => {
|
||||||
|
if (changes.exportMetaData) {
|
||||||
|
const createdTimestamp = entry.createdOn;
|
||||||
|
const createdBy = entry.createdBy ?? UNKNOWN_USER;
|
||||||
|
const modifiedBy = entry.modifiedBy ?? UNKNOWN_USER;
|
||||||
|
const modifiedTimestamp = entry.modified ?? entry.created;
|
||||||
|
notebookAsText += `Created on ${this.formatTimeStamp(createdTimestamp)} by user ${createdBy}\n\n`;
|
||||||
|
notebookAsText += `Updated on ${this.formatTimeStamp(modifiedTimestamp)} by user ${modifiedBy}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.exportTags) {
|
||||||
|
const domainObjectKeyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
const tags = this.getTagsForEntry(entry, domainObjectKeyString, foundAnnotations);
|
||||||
|
const tagNames = tags.map(tag => this.getTagName(tag, availableTags));
|
||||||
|
if (tagNames) {
|
||||||
|
notebookAsText += `Tags: ${tagNames.join(', ')}\n\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notebookAsText += `${entry.text}\n\n`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([notebookAsText], {type: "text/markdown"});
|
||||||
|
const fileName = domainObject.name + '.md';
|
||||||
|
saveAs(blob, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async showForm(objectPath) {
|
||||||
|
const formStructure = {
|
||||||
|
title: "Export Notebook Text",
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
key: "exportMetaData",
|
||||||
|
control: "toggleSwitch",
|
||||||
|
name: "Include Metadata (created/modified, etc.)",
|
||||||
|
required: true,
|
||||||
|
value: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Include Tags",
|
||||||
|
control: "toggleSwitch",
|
||||||
|
required: true,
|
||||||
|
key: 'exportTags',
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const changes = await this.openmct.forms.showForm(formStructure);
|
||||||
|
|
||||||
|
return this.onSave(changes, objectPath);
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import CopyToNotebookAction from './actions/CopyToNotebookAction';
|
import CopyToNotebookAction from './actions/CopyToNotebookAction';
|
||||||
|
import ExportNotebookAsTextAction from './actions/ExportNotebookAsTextAction';
|
||||||
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
|
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
|
||||||
import NotebookViewProvider from './NotebookViewProvider';
|
import NotebookViewProvider from './NotebookViewProvider';
|
||||||
import NotebookType from './NotebookType';
|
import NotebookType from './NotebookType';
|
||||||
@ -80,6 +81,7 @@ function installBaseNotebookFunctionality(openmct) {
|
|||||||
};
|
};
|
||||||
openmct.types.addType('notebookSnapshotImage', notebookSnapshotImageType);
|
openmct.types.addType('notebookSnapshotImage', notebookSnapshotImageType);
|
||||||
openmct.actions.register(new CopyToNotebookAction(openmct));
|
openmct.actions.register(new CopyToNotebookAction(openmct));
|
||||||
|
openmct.actions.register(new ExportNotebookAsTextAction(openmct));
|
||||||
|
|
||||||
const notebookSnapshotIndicator = new Vue ({
|
const notebookSnapshotIndicator = new Vue ({
|
||||||
components: {
|
components: {
|
||||||
|
Loading…
Reference in New Issue
Block a user