Add analytical store schema POC (#164)

* add schema APIs to JunoClient

* start implementing buildSchemaNode

* finish getSchemaNodes

* finish implementing addSchema

* cleanup

* make schema optional

* handle undefined/null schema and fields. Also don't retry on gettting schema failures.

* fix request schema and get schema endpoints

* add feature flag

* try to get most recent schema when refreshed or initialized.

* add tests

* cleanup

* cleanup

* cleanup

* fix merge conflict typos

* fix lint errors

* fix tests and update snapshot

Co-authored-by: REDMOND\gaausfel <gaausfel@microsoft.com>
This commit is contained in:
Garrett Ausfeldt
2020-11-12 13:33:37 -08:00
committed by GitHub
parent addcfedd5e
commit 4ce9dcc024
12 changed files with 727 additions and 1 deletions

View File

@@ -2,7 +2,7 @@ import * as ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
import { TreeComponent, TreeNode, TreeNodeMenuItem, TreeNodeComponent } from "../Controls/TreeComponent/TreeComponent";
import * as ViewModels from "../../Contracts/ViewModels";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory";
@@ -32,6 +32,7 @@ import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger";
import TabsBase from "../Tabs/TabsBase";
import { userContext } from "../../UserContext";
import * as DataModels from "../../Contracts/DataModels";
export class ResourceTreeAdapter implements ReactAdapter {
public static readonly MyNotebooksTitle = "My Notebooks";
@@ -289,6 +290,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
});
}
const schemaNode: TreeNode = this.buildSchemaNode(collection);
if (schemaNode) {
children.push(schemaNode);
}
if (ResourceTreeAdapter.showScriptNodes(this.container)) {
children.push(this.buildStoredProcedureNode(collection));
children.push(this.buildUserDefinedFunctionsNode(collection));
@@ -405,6 +411,75 @@ export class ResourceTreeAdapter implements ReactAdapter {
};
}
public buildSchemaNode(collection: ViewModels.Collection): TreeNode {
if (collection.analyticalStorageTtl() == undefined) {
return undefined;
}
if (!collection.schema || !collection.schema.fields) {
return undefined;
}
return {
label: "Schema",
children: this.getSchemaNodes(collection.schema.fields),
onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Schema);
this.container.tabsManager.refreshActiveTab(
(tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid
);
}
};
}
private getSchemaNodes(fields: DataModels.IDataField[]): TreeNode[] {
const schema: any = {};
//unflatten
fields.forEach((field: DataModels.IDataField, fieldIndex: number) => {
const path: string[] = field.path.split(".");
const fieldProperties = [field.dataType.name, `HasNulls: ${field.hasNulls}`];
let current: any = {};
path.forEach((name: string, pathIndex: number) => {
if (pathIndex === 0) {
if (schema[name] === undefined) {
if (pathIndex === path.length - 1) {
schema[name] = fieldProperties;
} else {
schema[name] = {};
}
}
current = schema[name];
} else {
if (current[name] === undefined) {
if (pathIndex === path.length - 1) {
current[name] = fieldProperties;
} else {
current[name] = {};
}
}
current = current[name];
}
});
});
const traverse = (obj: any): TreeNode[] => {
const children: TreeNode[] = [];
if (obj !== null && !Array.isArray(obj) && typeof obj === "object") {
Object.entries(obj).forEach(([key, value]) => {
children.push({ label: key, children: traverse(value) });
});
} else if (Array.isArray(obj)) {
return [{ label: obj[0] }, { label: obj[1] }];
}
return children;
};
return traverse(schema);
}
private buildNotebooksTrees(): TreeNode {
let notebooksTree: TreeNode = {
label: undefined,