MostRecentActivity changes (#463)

This changes the public API a bit, so that recording activity (the most common use) is less involved.
This commit is contained in:
Jordi Bunster 2021-03-14 20:10:48 -07:00 committed by GitHub
parent 62550f8d6a
commit f86883de6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 153 additions and 97 deletions

View File

@ -0,0 +1,86 @@
import { observable } from "knockout";
import { mostRecentActivity } from "./MostRecentActivity";
describe("MostRecentActivity", () => {
const accountId = "some account";
beforeEach(() => mostRecentActivity.clear(accountId));
it("Has no items at first", () => {
expect(mostRecentActivity.getItems(accountId)).toStrictEqual([]);
});
it("Can record collections being opened", () => {
const collectionId = "some collection";
const databaseId = "some database";
const collection = {
id: observable(collectionId),
databaseId,
};
mostRecentActivity.collectionWasOpened(accountId, collection);
const activity = mostRecentActivity.getItems(accountId);
expect(activity).toEqual([
expect.objectContaining({
collectionId,
databaseId,
}),
]);
});
it("Can record notebooks being opened", () => {
const name = "some notebook";
const path = "some path";
const notebook = { name, path };
mostRecentActivity.notebookWasItemOpened(accountId, notebook);
const activity = mostRecentActivity.getItems(accountId);
expect(activity).toEqual([expect.objectContaining(notebook)]);
});
it("Filters out duplicates", () => {
const name = "some notebook";
const path = "some path";
const notebook = { name, path };
const sameNotebook = { name, path };
mostRecentActivity.notebookWasItemOpened(accountId, notebook);
mostRecentActivity.notebookWasItemOpened(accountId, sameNotebook);
const activity = mostRecentActivity.getItems(accountId);
expect(activity.length).toEqual(1);
expect(activity).toEqual([expect.objectContaining(notebook)]);
});
it("Allows for multiple accounts", () => {
const name = "some notebook";
const path = "some path";
const notebook = { name, path };
const anotherNotebook = { name: "Another " + name, path };
const anotherAccountId = "Another " + accountId;
mostRecentActivity.notebookWasItemOpened(accountId, notebook);
mostRecentActivity.notebookWasItemOpened(anotherAccountId, anotherNotebook);
expect(mostRecentActivity.getItems(accountId)).toEqual([expect.objectContaining(notebook)]);
expect(mostRecentActivity.getItems(anotherAccountId)).toEqual([expect.objectContaining(anotherNotebook)]);
});
it("Can store multiple distinct elements, in FIFO order", () => {
const name = "some notebook";
const path = "some path";
const first = { name, path };
const second = { name: "Another " + name, path };
const third = { name, path: "Another " + path };
mostRecentActivity.notebookWasItemOpened(accountId, first);
mostRecentActivity.notebookWasItemOpened(accountId, second);
mostRecentActivity.notebookWasItemOpened(accountId, third);
const activity = mostRecentActivity.getItems(accountId);
expect(activity).toEqual([third, second, first].map(expect.objectContaining));
});
});

View File

@ -1,4 +1,6 @@
import { CollectionBase } from "../../Contracts/ViewModels";
import { StorageKey, LocalStorageUtility } from "../../Shared/StorageUtility"; import { StorageKey, LocalStorageUtility } from "../../Shared/StorageUtility";
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
export enum Type { export enum Type {
OpenCollection, OpenCollection,
@ -6,21 +8,18 @@ export enum Type {
} }
export interface OpenNotebookItem { export interface OpenNotebookItem {
type: Type.OpenNotebook;
name: string; name: string;
path: string; path: string;
} }
export interface OpenCollectionItem { export interface OpenCollectionItem {
type: Type.OpenCollection;
databaseId: string; databaseId: string;
collectionId: string; collectionId: string;
} }
export interface Item { type Item = OpenNotebookItem | OpenCollectionItem;
type: Type;
title: string;
description: string;
data: OpenNotebookItem | OpenCollectionItem;
}
// Update schemaVersion if you are going to change this interface // Update schemaVersion if you are going to change this interface
interface StoredData { interface StoredData {
@ -32,7 +31,7 @@ interface StoredData {
* Stores most recent activity * Stores most recent activity
*/ */
class MostRecentActivity { class MostRecentActivity {
private static readonly schemaVersion: string = "1"; private static readonly schemaVersion: string = "2";
private static itemsMaxNumber: number = 5; private static itemsMaxNumber: number = 5;
private storedData: StoredData; private storedData: StoredData;
constructor() { constructor() {
@ -92,7 +91,7 @@ class MostRecentActivity {
LocalStorageUtility.setEntryString(StorageKey.MostRecentActivity, JSON.stringify(this.storedData)); LocalStorageUtility.setEntryString(StorageKey.MostRecentActivity, JSON.stringify(this.storedData));
} }
public addItem(accountId: string, newItem: Item): void { private addItem(accountId: string, newItem: Item): void {
// When debugging, accountId is "undefined": most recent activity cannot be saved by account. Uncomment to disable. // When debugging, accountId is "undefined": most recent activity cannot be saved by account. Uncomment to disable.
// if (!accountId) { // if (!accountId) {
// return; // return;
@ -111,6 +110,23 @@ class MostRecentActivity {
return this.storedData.itemsMap[accountId] || []; return this.storedData.itemsMap[accountId] || [];
} }
public collectionWasOpened(accountId: string, { id, databaseId }: Pick<CollectionBase, "id" | "databaseId">) {
const collectionId = id();
this.addItem(accountId, {
type: Type.OpenCollection,
databaseId,
collectionId,
});
}
public notebookWasItemOpened(accountId: string, { name, path }: Pick<NotebookContentItem, "name" | "path">) {
this.addItem(accountId, {
type: Type.OpenNotebook,
name,
path,
});
}
public clear(accountId: string): void { public clear(accountId: string): void {
delete this.storedData.itemsMap[accountId]; delete this.storedData.itemsMap[accountId];
this.saveToLocalStorage(); this.saveToLocalStorage();
@ -128,11 +144,7 @@ class MostRecentActivity {
let index = -1; let index = -1;
for (let i = 0; i < itemsArray.length; i++) { for (let i = 0; i < itemsArray.length; i++) {
const currentItem = itemsArray[i]; const currentItem = itemsArray[i];
if ( if (JSON.stringify(currentItem) === JSON.stringify(item)) {
currentItem.title === item.title &&
currentItem.description === item.description &&
JSON.stringify(currentItem.data) === JSON.stringify(item.data)
) {
index = i; index = i;
break; break;
} }

View File

@ -217,42 +217,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
return heroes; return heroes;
} }
private getItemIcon(item: MostRecentActivity.Item): string {
switch (item.type) {
case MostRecentActivity.Type.OpenCollection:
return CollectionIcon;
case MostRecentActivity.Type.OpenNotebook:
return NotebookIcon;
default:
return null;
}
}
private onItemClicked(item: MostRecentActivity.Item) {
switch (item.type) {
case MostRecentActivity.Type.OpenCollection: {
const openCollectionitem = item.data as MostRecentActivity.OpenCollectionItem;
const collection = this.container.findCollection(
openCollectionitem.databaseId,
openCollectionitem.collectionId
);
if (collection) {
collection.openTab();
}
break;
}
case MostRecentActivity.Type.OpenNotebook: {
const openNotebookItem = item.data as MostRecentActivity.OpenNotebookItem;
const notebookItem = this.container.createNotebookContentItemFile(openNotebookItem.name, openNotebookItem.path);
notebookItem && this.container.openNotebook(notebookItem);
break;
}
default:
console.error("Unknown item type", item);
break;
}
}
private createCommonTaskItems(): SplashScreenItem[] { private createCommonTaskItems(): SplashScreenItem[] {
const items: SplashScreenItem[] = []; const items: SplashScreenItem[] = [];
@ -333,23 +297,45 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
return items; return items;
} }
private static getInfo(item: MostRecentActivity.Item): string { private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
if (item.type === MostRecentActivity.Type.OpenNotebook) { return {
const data = item.data as MostRecentActivity.OpenNotebookItem; iconSrc: NotebookIcon,
return data.path; title: collectionId,
} else { description: "Data",
return undefined; onClick: () => {
} const collection = this.container.findCollection(databaseId, collectionId);
collection && collection.openTab();
},
};
}
private decorateOpenNotebookActivity({ name, path }: MostRecentActivity.OpenNotebookItem) {
return {
info: path,
iconSrc: CollectionIcon,
title: name,
description: "Notebook",
onClick: () => {
const notebookItem = this.container.createNotebookContentItemFile(name, path);
notebookItem && this.container.openNotebook(notebookItem);
},
};
} }
private createRecentItems(): SplashScreenItem[] { private createRecentItems(): SplashScreenItem[] {
return MostRecentActivity.mostRecentActivity.getItems(userContext.databaseAccount?.id).map((item) => ({ return MostRecentActivity.mostRecentActivity.getItems(userContext.databaseAccount?.id).map((activity) => {
iconSrc: this.getItemIcon(item), switch (activity.type) {
title: item.title, default: {
description: item.description, const unknownActivity: never = activity;
info: SplashScreen.getInfo(item), throw new Error(`Unknown activity: ${unknownActivity}`);
onClick: () => this.onItemClicked(item), }
})); case MostRecentActivity.Type.OpenNotebook:
return this.decorateOpenNotebookActivity(activity);
case MostRecentActivity.Type.OpenCollection:
return this.decorateOpenCollectionActivity(activity);
}
});
} }
private createTipsItems(): SplashScreenItem[] { private createTipsItems(): SplashScreenItem[] {

View File

@ -6,7 +6,7 @@ import { TreeComponent, TreeNode, TreeNodeMenuItem, TreeNodeComponent } from "..
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory"; import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory";
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity"; import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import CopyIcon from "../../../images/notebook/Notebook-copy.svg"; import CopyIcon from "../../../images/notebook/Notebook-copy.svg";
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
import CollectionIcon from "../../../images/tree-collection.svg"; import CollectionIcon from "../../../images/tree-collection.svg";
@ -264,15 +264,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
onClick: () => { onClick: () => {
collection.openTab(); collection.openTab();
// push to most recent // push to most recent
MostRecentActivity.mostRecentActivity.addItem(userContext.databaseAccount?.id, { mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
type: MostRecentActivity.Type.OpenCollection,
title: collection.id(),
description: "Data",
data: {
databaseId: collection.databaseId,
collectionId: collection.id(),
},
});
}, },
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ this.isDataNodeSelected(collection.databaseId, collection.id(), [
@ -573,7 +565,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
(item: NotebookContentItem) => { (item: NotebookContentItem) => {
this.container.openNotebook(item).then((hasOpened) => { this.container.openNotebook(item).then((hasOpened) => {
if (hasOpened) { if (hasOpened) {
this.pushItemToMostRecent(item); mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
} }
}); });
}, },
@ -594,7 +586,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
(item: NotebookContentItem) => { (item: NotebookContentItem) => {
this.container.openNotebook(item).then((hasOpened) => { this.container.openNotebook(item).then((hasOpened) => {
if (hasOpened) { if (hasOpened) {
this.pushItemToMostRecent(item); mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
} }
}); });
}, },
@ -624,18 +616,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
return gitHubNotebooksTree; return gitHubNotebooksTree;
} }
private pushItemToMostRecent(item: NotebookContentItem) {
MostRecentActivity.mostRecentActivity.addItem(userContext.databaseAccount?.id, {
type: MostRecentActivity.Type.OpenNotebook,
title: item.name,
description: "Notebook",
data: {
name: item.name,
path: item.path,
},
});
}
private buildChildNodes( private buildChildNodes(
item: NotebookContentItem, item: NotebookContentItem,
onFileClick: (item: NotebookContentItem) => void, onFileClick: (item: NotebookContentItem) => void,

View File

@ -1,5 +1,5 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity"; import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import * as React from "react"; import * as React from "react";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { NotebookContentItem } from "../Notebook/NotebookContentItem"; import { NotebookContentItem } from "../Notebook/NotebookContentItem";
@ -44,15 +44,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
onClick: () => { onClick: () => {
collection.onDocumentDBDocumentsClick(); collection.onDocumentDBDocumentsClick();
// push to most recent // push to most recent
MostRecentActivity.mostRecentActivity.addItem(userContext.databaseAccount?.id, { mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
type: MostRecentActivity.Type.OpenCollection,
title: collection.id(),
description: "Data",
data: {
databaseId: collection.databaseId,
collectionId: collection.id(),
},
});
}, },
isSelected: () => isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), ViewModels.CollectionTabKind.Documents), this.isDataNodeSelected(collection.databaseId, collection.id(), ViewModels.CollectionTabKind.Documents),