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:
parent
62550f8d6a
commit
f86883de6c
|
@ -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));
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue