Prep Schema Analyzer for flighting (#760)
* Prepare for flighting Schema Analyzer * Rename SchemaAnalyzerComponent -> SchemaAnalyzer * Only show Schema option if notebooks enabled
This commit is contained in:
parent
d7c62ac7f1
commit
404b1fc0f1
|
@ -1,10 +1,15 @@
|
||||||
.schema-analyzer-cell-outputs {
|
.schema-analyzer-cell-outputs {
|
||||||
padding: 10px;
|
padding: 10px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mimic FluentUI8's DocumentCard style
|
||||||
.schema-analyzer-cell-output {
|
.schema-analyzer-cell-output {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding: 10px;
|
padding: 14px 20px;
|
||||||
border-radius: 2px;
|
border: 1px solid rgb(237, 235, 233);
|
||||||
box-shadow: rgba(0, 0, 0, 13%) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 11%) 0px 0.3px 0.9px 0px;
|
}
|
||||||
|
|
||||||
|
.schema-analyzer-cell-output:hover {
|
||||||
|
border-color: rgb(200, 198, 196);
|
||||||
|
box-shadow: inset 0 0 0 1px rgb(200, 198, 196)
|
||||||
}
|
}
|
|
@ -94,6 +94,7 @@ export class Flights {
|
||||||
public static readonly MongoIndexEditor = "mongoindexeditor";
|
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||||
public static readonly MongoIndexing = "mongoindexing";
|
public static readonly MongoIndexing = "mongoindexing";
|
||||||
public static readonly AutoscaleTest = "autoscaletest";
|
public static readonly AutoscaleTest = "autoscaletest";
|
||||||
|
public static readonly SchemaAnalyzer = "schemaanalyzer";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
|
|
|
@ -163,7 +163,6 @@ export default class Explorer {
|
||||||
public isMongoIndexingEnabled: ko.Observable<boolean>;
|
public isMongoIndexingEnabled: ko.Observable<boolean>;
|
||||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
public canExceedMaximumValue: ko.Computed<boolean>;
|
||||||
public isAutoscaleDefaultEnabled: ko.Observable<boolean>;
|
public isAutoscaleDefaultEnabled: ko.Observable<boolean>;
|
||||||
|
|
||||||
public isSchemaEnabled: ko.Computed<boolean>;
|
public isSchemaEnabled: ko.Computed<boolean>;
|
||||||
|
|
||||||
// Notebooks
|
// Notebooks
|
||||||
|
@ -1048,6 +1047,9 @@ export default class Explorer {
|
||||||
if (flights.indexOf(Constants.Flights.MongoIndexing) !== -1) {
|
if (flights.indexOf(Constants.Flights.MongoIndexing) !== -1) {
|
||||||
this.isMongoIndexingEnabled(true);
|
this.isMongoIndexingEnabled(true);
|
||||||
}
|
}
|
||||||
|
if (flights.indexOf(Constants.Flights.SchemaAnalyzer) !== -1) {
|
||||||
|
userContext.features.enableSchemaAnalyzer = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedCollection(): ViewModels.Collection {
|
public findSelectedCollection(): ViewModels.Collection {
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { FileType, IContent, IContentProvider, ServerConfig } from "@nteract/core";
|
||||||
|
import { Observable, of } from "rxjs";
|
||||||
|
import { AjaxResponse } from "rxjs/ajax";
|
||||||
|
import { HttpStatusCodes } from "../../../../Common/Constants";
|
||||||
|
import { getErrorMessage } from "../../../../Common/ErrorHandlingUtils";
|
||||||
|
import * as Logger from "../../../../Common/Logger";
|
||||||
|
|
||||||
|
export interface InMemoryContentProviderParams {
|
||||||
|
[path: string]: { readonly: boolean; content: IContent<FileType> };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nteract relies on `errno` property to figure out the kind of failure
|
||||||
|
// That's why we need a custom wrapper around Error to include `errno` property
|
||||||
|
class InMemoryContentProviderError extends Error {
|
||||||
|
constructor(error: string, public errno: number = InMemoryContentProvider.SelfErrorCode) {
|
||||||
|
super(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InMemoryContentProvider implements IContentProvider {
|
||||||
|
public static readonly SelfErrorCode = 666;
|
||||||
|
|
||||||
|
constructor(private params: InMemoryContentProviderParams) {}
|
||||||
|
|
||||||
|
public remove(): Observable<AjaxResponse> {
|
||||||
|
return this.errorResponse("Not implemented", "remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
public get(_config: ServerConfig, uri: string): Observable<AjaxResponse> {
|
||||||
|
const item = this.params[uri];
|
||||||
|
if (item) {
|
||||||
|
return of(this.createSuccessAjaxResponse(HttpStatusCodes.OK, item.content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.errorResponse(`${uri} not found`, "get");
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(): Observable<AjaxResponse> {
|
||||||
|
return this.errorResponse("Not implemented", "update");
|
||||||
|
}
|
||||||
|
|
||||||
|
public create(): Observable<AjaxResponse> {
|
||||||
|
return this.errorResponse("Not implemented", "create");
|
||||||
|
}
|
||||||
|
|
||||||
|
public save<FT extends FileType>(
|
||||||
|
_config: ServerConfig, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
uri: string,
|
||||||
|
model: Partial<IContent<FT>>
|
||||||
|
): Observable<AjaxResponse> {
|
||||||
|
const item = this.params[uri];
|
||||||
|
if (item) {
|
||||||
|
if (!item.readonly) {
|
||||||
|
Object.assign(item.content, model);
|
||||||
|
}
|
||||||
|
return of(this.createSuccessAjaxResponse(HttpStatusCodes.OK, item.content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.errorResponse(`${uri} not found`, "save");
|
||||||
|
}
|
||||||
|
|
||||||
|
public listCheckpoints(): Observable<AjaxResponse> {
|
||||||
|
return this.errorResponse("Not implemented", "listCheckpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public createCheckpoint(): Observable<AjaxResponse> {
|
||||||
|
return this.errorResponse("Not implemented", "createCheckpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteCheckpoint(): Observable<AjaxResponse> {
|
||||||
|
return this.errorResponse("Not implemented", "deleteCheckpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
public restoreFromCheckpoint(): Observable<AjaxResponse> {
|
||||||
|
return this.errorResponse("Not implemented", "restoreFromCheckpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
private errorResponse(message: string, functionName: string): Observable<AjaxResponse> {
|
||||||
|
const error = new InMemoryContentProviderError(message);
|
||||||
|
Logger.logError(error.message, `InMemoryContentProvider/${functionName}`, error.errno);
|
||||||
|
return of(this.createErrorAjaxResponse(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSuccessAjaxResponse(status: number, content: IContent<FileType>): AjaxResponse {
|
||||||
|
return {
|
||||||
|
originalEvent: new Event("no-op"),
|
||||||
|
xhr: new XMLHttpRequest(),
|
||||||
|
request: {},
|
||||||
|
status,
|
||||||
|
response: content ? content : undefined,
|
||||||
|
responseText: content ? JSON.stringify(content) : undefined,
|
||||||
|
responseType: "json",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private createErrorAjaxResponse(error: InMemoryContentProviderError): AjaxResponse {
|
||||||
|
return {
|
||||||
|
originalEvent: new Event("no-op"),
|
||||||
|
xhr: new XMLHttpRequest(),
|
||||||
|
request: {},
|
||||||
|
status: error.errno,
|
||||||
|
response: error,
|
||||||
|
responseText: getErrorMessage(error),
|
||||||
|
responseType: "json",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
// memory://<path>
|
||||||
|
// Custom scheme for in memory content
|
||||||
|
export const ContentUriPattern = /memory:\/\/([^/]*)/;
|
||||||
|
|
||||||
|
export function fromContentUri(contentUri: string): undefined | string {
|
||||||
|
const matches = contentUri.match(ContentUriPattern);
|
||||||
|
if (matches && matches.length > 1) {
|
||||||
|
return matches[1];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toContentUri(path: string): string {
|
||||||
|
return `memory://${path}`;
|
||||||
|
}
|
|
@ -1,11 +1,17 @@
|
||||||
import { ServerConfig, IContentProvider, FileType, IContent, IGetParams } from "@nteract/core";
|
import { FileType, IContent, IContentProvider, IGetParams, ServerConfig } from "@nteract/core";
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
import { AjaxResponse } from "rxjs/ajax";
|
import { AjaxResponse } from "rxjs/ajax";
|
||||||
import { GitHubContentProvider } from "../../../GitHub/GitHubContentProvider";
|
import { GitHubContentProvider } from "../../../GitHub/GitHubContentProvider";
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
|
import { InMemoryContentProvider } from "./ContentProviders/InMemoryContentProvider";
|
||||||
|
import * as InMemoryContentProviderUtils from "./ContentProviders/InMemoryContentProviderUtils";
|
||||||
|
|
||||||
export class NotebookContentProvider implements IContentProvider {
|
export class NotebookContentProvider implements IContentProvider {
|
||||||
constructor(private gitHubContentProvider: GitHubContentProvider, private jupyterContentProvider: IContentProvider) {}
|
constructor(
|
||||||
|
private inMemoryContentProvider: InMemoryContentProvider,
|
||||||
|
private gitHubContentProvider: GitHubContentProvider,
|
||||||
|
private jupyterContentProvider: IContentProvider
|
||||||
|
) {}
|
||||||
|
|
||||||
public remove(serverConfig: ServerConfig, path: string): Observable<AjaxResponse> {
|
public remove(serverConfig: ServerConfig, path: string): Observable<AjaxResponse> {
|
||||||
return this.getContentProvider(path).remove(serverConfig, path);
|
return this.getContentProvider(path).remove(serverConfig, path);
|
||||||
|
@ -60,6 +66,10 @@ export class NotebookContentProvider implements IContentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getContentProvider(path: string): IContentProvider {
|
private getContentProvider(path: string): IContentProvider {
|
||||||
|
if (InMemoryContentProviderUtils.fromContentUri(path)) {
|
||||||
|
return this.inMemoryContentProvider;
|
||||||
|
}
|
||||||
|
|
||||||
if (GitHubUtils.fromContentUri(path)) {
|
if (GitHubUtils.fromContentUri(path)) {
|
||||||
return this.gitHubContentProvider;
|
return this.gitHubContentProvider;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,14 @@ import { getFullName } from "../../Utils/UserUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
|
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
|
||||||
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
|
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
|
||||||
// import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
|
||||||
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
|
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
|
||||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||||
|
import { InMemoryContentProvider } from "./NotebookComponent/ContentProviders/InMemoryContentProvider";
|
||||||
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
||||||
import { SnapshotRequest } from "./NotebookComponent/types";
|
import { SnapshotRequest } from "./NotebookComponent/types";
|
||||||
import { NotebookContainerClient } from "./NotebookContainerClient";
|
import { NotebookContainerClient } from "./NotebookContainerClient";
|
||||||
import { NotebookContentClient } from "./NotebookContentClient";
|
import { NotebookContentClient } from "./NotebookContentClient";
|
||||||
|
import { SchemaAnalyzerNotebook } from "./SchemaAnalyzer/SchemaAnalyzerUtils";
|
||||||
|
|
||||||
type NotebookPaneContent = string | ImmutableNotebook;
|
type NotebookPaneContent = string | ImmutableNotebook;
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ export default class NotebookManager {
|
||||||
public notebookClient: NotebookContainerClient;
|
public notebookClient: NotebookContainerClient;
|
||||||
public notebookContentClient: NotebookContentClient;
|
public notebookContentClient: NotebookContentClient;
|
||||||
|
|
||||||
|
private inMemoryContentProvider: InMemoryContentProvider;
|
||||||
private gitHubContentProvider: GitHubContentProvider;
|
private gitHubContentProvider: GitHubContentProvider;
|
||||||
public gitHubOAuthService: GitHubOAuthService;
|
public gitHubOAuthService: GitHubOAuthService;
|
||||||
public gitHubClient: GitHubClient;
|
public gitHubClient: GitHubClient;
|
||||||
|
@ -63,12 +65,20 @@ export default class NotebookManager {
|
||||||
this.gitHubOAuthService = new GitHubOAuthService(this.junoClient);
|
this.gitHubOAuthService = new GitHubOAuthService(this.junoClient);
|
||||||
this.gitHubClient = new GitHubClient(this.onGitHubClientError);
|
this.gitHubClient = new GitHubClient(this.onGitHubClientError);
|
||||||
|
|
||||||
|
this.inMemoryContentProvider = new InMemoryContentProvider({
|
||||||
|
[SchemaAnalyzerNotebook.path]: {
|
||||||
|
readonly: true,
|
||||||
|
content: SchemaAnalyzerNotebook,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.gitHubContentProvider = new GitHubContentProvider({
|
this.gitHubContentProvider = new GitHubContentProvider({
|
||||||
gitHubClient: this.gitHubClient,
|
gitHubClient: this.gitHubClient,
|
||||||
promptForCommitMsg: this.promptForCommitMsg,
|
promptForCommitMsg: this.promptForCommitMsg,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.notebookContentProvider = new NotebookContentProvider(
|
this.notebookContentProvider = new NotebookContentProvider(
|
||||||
|
this.inMemoryContentProvider,
|
||||||
this.gitHubContentProvider,
|
this.gitHubContentProvider,
|
||||||
contents.JupyterContentProvider
|
contents.JupyterContentProvider
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.schemaAnalyzerComponent {
|
.schemaAnalyzer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
|
@ -1,22 +1,26 @@
|
||||||
import { FontIcon, PrimaryButton, Spinner, SpinnerSize, Stack, Text, TextField } from "@fluentui/react";
|
import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
|
||||||
import { ImmutableOutput } from "@nteract/commutable";
|
import { ImmutableExecuteResult, ImmutableOutput } from "@nteract/commutable";
|
||||||
import { actions, AppState, ContentRef, KernelRef, selectors } from "@nteract/core";
|
import { actions, AppState, ContentRef, KernelRef, selectors } from "@nteract/core";
|
||||||
import Immutable from "immutable";
|
import Immutable from "immutable";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Dispatch } from "redux";
|
import { Dispatch } from "redux";
|
||||||
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import loadTransform from "../NotebookComponent/loadTransform";
|
import loadTransform from "../NotebookComponent/loadTransform";
|
||||||
import SandboxOutputs from "../NotebookRenderer/outputs/SandboxOutputs";
|
import SandboxOutputs from "../NotebookRenderer/outputs/SandboxOutputs";
|
||||||
import "./SchemaAnalyzerComponent.less";
|
import "./SchemaAnalyzer.less";
|
||||||
|
import { DefaultFilter, DefaultSampleSize, SchemaAnalyzerHeader } from "./SchemaAnalyzerHeader";
|
||||||
|
import { SchemaAnalyzerSplashScreen } from "./SchemaAnalyzerSplashScreen";
|
||||||
|
|
||||||
interface SchemaAnalyzerComponentPureProps {
|
interface SchemaAnalyzerPureProps {
|
||||||
contentRef: ContentRef;
|
contentRef: ContentRef;
|
||||||
kernelRef: KernelRef;
|
kernelRef: KernelRef;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SchemaAnalyzerComponentDispatchProps {
|
interface SchemaAnalyzerDispatchProps {
|
||||||
runCell: (contentRef: ContentRef, cellId: string) => void;
|
runCell: (contentRef: ContentRef, cellId: string) => void;
|
||||||
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => void;
|
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => void;
|
||||||
updateCell: (text: string, id: string, contentRef: ContentRef) => void;
|
updateCell: (text: string, id: string, contentRef: ContentRef) => void;
|
||||||
|
@ -24,25 +28,23 @@ interface SchemaAnalyzerComponentDispatchProps {
|
||||||
|
|
||||||
type OutputType = "rich" | "json";
|
type OutputType = "rich" | "json";
|
||||||
|
|
||||||
interface SchemaAnalyzerComponentState {
|
interface SchemaAnalyzerState {
|
||||||
outputType: OutputType;
|
outputType: OutputType;
|
||||||
filter?: string;
|
|
||||||
isFiltering: boolean;
|
isFiltering: boolean;
|
||||||
|
sampleSize: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SchemaAnalyzerComponentProps = SchemaAnalyzerComponentPureProps &
|
type SchemaAnalyzerProps = SchemaAnalyzerPureProps & StateProps & SchemaAnalyzerDispatchProps;
|
||||||
StateProps &
|
|
||||||
SchemaAnalyzerComponentDispatchProps;
|
|
||||||
|
|
||||||
export class SchemaAnalyzerComponent extends React.Component<
|
export class SchemaAnalyzer extends React.Component<SchemaAnalyzerProps, SchemaAnalyzerState> {
|
||||||
SchemaAnalyzerComponentProps,
|
private clickAnalyzeTelemetryStartKey: number;
|
||||||
SchemaAnalyzerComponentState
|
|
||||||
> {
|
constructor(props: SchemaAnalyzerProps) {
|
||||||
constructor(props: SchemaAnalyzerComponentProps) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
outputType: "rich",
|
outputType: "rich",
|
||||||
isFiltering: false,
|
isFiltering: false,
|
||||||
|
sampleSize: DefaultSampleSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,34 +52,59 @@ export class SchemaAnalyzerComponent extends React.Component<
|
||||||
loadTransform(this.props);
|
loadTransform(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFilterTextFieldChange = (
|
private onAnalyzeButtonClick = (filter: string = DefaultFilter, sampleSize: string = this.state.sampleSize) => {
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
||||||
newValue?: string
|
|
||||||
): void => {
|
|
||||||
this.setState({
|
|
||||||
filter: newValue,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAnalyzeButtonClick = () => {
|
|
||||||
const query = {
|
const query = {
|
||||||
command: "listSchema",
|
command: "listSchema",
|
||||||
database: this.props.databaseId,
|
database: this.props.databaseId,
|
||||||
collection: this.props.collectionId,
|
collection: this.props.collectionId,
|
||||||
outputType: this.state.outputType,
|
outputType: this.state.outputType,
|
||||||
filter: this.state.filter,
|
filter,
|
||||||
|
sampleSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.state.filter) {
|
this.setState({
|
||||||
this.setState({
|
isFiltering: true,
|
||||||
isFiltering: true,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.updateCell(JSON.stringify(query), this.props.firstCellId, this.props.contentRef);
|
this.props.updateCell(JSON.stringify(query), this.props.firstCellId, this.props.contentRef);
|
||||||
|
|
||||||
|
this.clickAnalyzeTelemetryStartKey = traceStart(Action.SchemaAnalyzerClickAnalyze, {
|
||||||
|
database: this.props.databaseId,
|
||||||
|
collection: this.props.collectionId,
|
||||||
|
sampleSize,
|
||||||
|
});
|
||||||
|
|
||||||
this.props.runCell(this.props.contentRef, this.props.firstCellId);
|
this.props.runCell(this.props.contentRef, this.props.firstCellId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private traceClickAnalyzeComplete = (kernelStatus: string, outputs: Immutable.List<ImmutableOutput>) => {
|
||||||
|
/**
|
||||||
|
* CosmosMongoKernel always returns 1st output as "text/html"
|
||||||
|
* This output can be an error stack or information about how many documents were sampled
|
||||||
|
*/
|
||||||
|
let firstTextHtmlOutput: string;
|
||||||
|
if (outputs.size > 0 && outputs.get(0).output_type === "execute_result") {
|
||||||
|
const executeResult = outputs.get(0) as ImmutableExecuteResult;
|
||||||
|
firstTextHtmlOutput = executeResult.data["text/html"];
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
database: this.props.databaseId,
|
||||||
|
collection: this.props.collectionId,
|
||||||
|
firstTextHtmlOutput,
|
||||||
|
sampleSize: this.state.sampleSize,
|
||||||
|
numOfOutputs: outputs.size,
|
||||||
|
kernelStatus,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only in cases where CosmosMongoKernel runs into an error we get a single output
|
||||||
|
if (outputs.size === 1) {
|
||||||
|
traceFailure(Action.SchemaAnalyzerClickAnalyze, data, this.clickAnalyzeTelemetryStartKey);
|
||||||
|
} else {
|
||||||
|
traceSuccess(Action.SchemaAnalyzerClickAnalyze, data, this.clickAnalyzeTelemetryStartKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
const { firstCellId: id, contentRef, kernelStatus, outputs } = this.props;
|
const { firstCellId: id, contentRef, kernelStatus, outputs } = this.props;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
@ -86,31 +113,22 @@ export class SchemaAnalyzerComponent extends React.Component<
|
||||||
|
|
||||||
const isKernelBusy = kernelStatus === "busy";
|
const isKernelBusy = kernelStatus === "busy";
|
||||||
const isKernelIdle = kernelStatus === "idle";
|
const isKernelIdle = kernelStatus === "idle";
|
||||||
const showSchemaOutput = isKernelIdle && outputs.size > 0;
|
const showSchemaOutput = isKernelIdle && outputs?.size > 0;
|
||||||
|
|
||||||
|
if (showSchemaOutput && this.clickAnalyzeTelemetryStartKey) {
|
||||||
|
this.traceClickAnalyzeComplete(kernelStatus, outputs);
|
||||||
|
this.clickAnalyzeTelemetryStartKey = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="schemaAnalyzerComponent">
|
<div className="schemaAnalyzer">
|
||||||
<Stack horizontalAlign="center" tokens={{ childrenGap: 20, padding: 20 }}>
|
<Stack tokens={{ childrenGap: 20, padding: 20 }}>
|
||||||
<Stack.Item grow styles={{ root: { display: "contents" } }}>
|
<SchemaAnalyzerHeader
|
||||||
<Stack horizontal tokens={{ childrenGap: 20 }} styles={{ root: { width: "100%" } }}>
|
isKernelIdle={isKernelIdle}
|
||||||
<Stack.Item grow align="end">
|
isKernelBusy={isKernelBusy}
|
||||||
<TextField
|
onSampleSizeUpdated={(sampleSize) => this.setState({ sampleSize })}
|
||||||
value={this.state.filter}
|
onAnalyzeButtonClick={this.onAnalyzeButtonClick}
|
||||||
onChange={this.onFilterTextFieldChange}
|
/>
|
||||||
label="Filter"
|
|
||||||
placeholder="{ field: 'value' }"
|
|
||||||
disabled={!isKernelIdle}
|
|
||||||
/>
|
|
||||||
</Stack.Item>
|
|
||||||
<Stack.Item align="end">
|
|
||||||
<PrimaryButton
|
|
||||||
text={isKernelBusy ? "Analyzing..." : "Analyze"}
|
|
||||||
onClick={this.onAnalyzeButtonClick}
|
|
||||||
disabled={!isKernelIdle}
|
|
||||||
/>
|
|
||||||
</Stack.Item>
|
|
||||||
</Stack>
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
{showSchemaOutput ? (
|
{showSchemaOutput ? (
|
||||||
<SandboxOutputs
|
<SandboxOutputs
|
||||||
|
@ -120,32 +138,13 @@ export class SchemaAnalyzerComponent extends React.Component<
|
||||||
outputClassName="schema-analyzer-cell-output"
|
outputClassName="schema-analyzer-cell-output"
|
||||||
/>
|
/>
|
||||||
) : this.state.isFiltering ? (
|
) : this.state.isFiltering ? (
|
||||||
<Stack.Item>
|
<Spinner styles={{ root: { marginTop: 40 } }} size={SpinnerSize.large} />
|
||||||
{isKernelBusy && <Spinner styles={{ root: { marginTop: 40 } }} size={SpinnerSize.large} />}
|
|
||||||
</Stack.Item>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<SchemaAnalyzerSplashScreen
|
||||||
<Stack.Item>
|
isKernelIdle={isKernelIdle}
|
||||||
<FontIcon iconName="Chart" style={{ fontSize: 100, color: "#43B1E5", marginTop: 40 }} />
|
isKernelBusy={isKernelBusy}
|
||||||
</Stack.Item>
|
onAnalyzeButtonClick={this.onAnalyzeButtonClick}
|
||||||
<Stack.Item>
|
/>
|
||||||
<Text variant="xxLarge">Explore your schema</Text>
|
|
||||||
</Stack.Item>
|
|
||||||
<Stack.Item>
|
|
||||||
<Text variant="large">
|
|
||||||
Quickly visualize your schema to infer the frequency, types and ranges of fields in your data set.
|
|
||||||
</Text>
|
|
||||||
</Stack.Item>
|
|
||||||
<Stack.Item>
|
|
||||||
<PrimaryButton
|
|
||||||
styles={{ root: { fontSize: 18, padding: 30 } }}
|
|
||||||
text={isKernelBusy ? "Analyzing..." : "Analyze Schema"}
|
|
||||||
onClick={this.onAnalyzeButtonClick}
|
|
||||||
disabled={kernelStatus !== "idle"}
|
|
||||||
/>
|
|
||||||
</Stack.Item>
|
|
||||||
<Stack.Item>{isKernelBusy && <Spinner size={SpinnerSize.large} />}</Stack.Item>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
|
@ -229,4 +228,4 @@ const makeMapDispatchToProps = () => {
|
||||||
return mapDispatchToProps;
|
return mapDispatchToProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(makeMapStateToProps, makeMapDispatchToProps)(SchemaAnalyzerComponent);
|
export default connect(makeMapStateToProps, makeMapDispatchToProps)(SchemaAnalyzer);
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { actions, createContentRef, createKernelRef, KernelRef } from "@nteract/core";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||||
|
import {
|
||||||
|
NotebookComponentBootstrapper,
|
||||||
|
NotebookComponentBootstrapperOptions,
|
||||||
|
} from "../NotebookComponent/NotebookComponentBootstrapper";
|
||||||
|
import SchemaAnalyzer from "./SchemaAnalyzer";
|
||||||
|
import { SchemaAnalyzerNotebook } from "./SchemaAnalyzerUtils";
|
||||||
|
|
||||||
|
export class SchemaAnalyzerAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
|
||||||
|
public parameters: unknown;
|
||||||
|
private kernelRef: KernelRef;
|
||||||
|
|
||||||
|
constructor(options: NotebookComponentBootstrapperOptions, private databaseId: string, private collectionId: string) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
if (!this.contentRef) {
|
||||||
|
this.contentRef = createContentRef();
|
||||||
|
this.kernelRef = createKernelRef();
|
||||||
|
|
||||||
|
this.getStore().dispatch(
|
||||||
|
actions.fetchContent({
|
||||||
|
filepath: SchemaAnalyzerNotebook.path,
|
||||||
|
params: {},
|
||||||
|
kernelRef: this.kernelRef,
|
||||||
|
contentRef: this.contentRef,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderComponent(): JSX.Element {
|
||||||
|
const props = {
|
||||||
|
contentRef: this.contentRef,
|
||||||
|
kernelRef: this.kernelRef,
|
||||||
|
databaseId: this.databaseId,
|
||||||
|
collectionId: this.collectionId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider store={this.getStore()}>
|
||||||
|
<SchemaAnalyzer {...props} />;
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
import {
|
||||||
|
DefaultButton,
|
||||||
|
Icon,
|
||||||
|
IRenderFunction,
|
||||||
|
ITextFieldProps,
|
||||||
|
PrimaryButton,
|
||||||
|
Stack,
|
||||||
|
TextField,
|
||||||
|
TooltipHost,
|
||||||
|
} from "@fluentui/react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
type SchemaAnalyzerHeaderProps = {
|
||||||
|
isKernelIdle: boolean;
|
||||||
|
isKernelBusy: boolean;
|
||||||
|
onSampleSizeUpdated: (sampleSize: string) => void;
|
||||||
|
onAnalyzeButtonClick: (filter: string, sampleSize: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultFilter = "";
|
||||||
|
export const DefaultSampleSize = "1000";
|
||||||
|
const FilterPlaceholder = "{ field: 'value' }";
|
||||||
|
const SampleSizePlaceholder = "1000";
|
||||||
|
const MinSampleSize = 1;
|
||||||
|
const MaxSampleSize = 5000;
|
||||||
|
|
||||||
|
export const SchemaAnalyzerHeader = ({
|
||||||
|
isKernelIdle,
|
||||||
|
isKernelBusy,
|
||||||
|
onSampleSizeUpdated,
|
||||||
|
onAnalyzeButtonClick,
|
||||||
|
}: SchemaAnalyzerHeaderProps): JSX.Element => {
|
||||||
|
const [filter, setFilter] = React.useState<string>(DefaultFilter);
|
||||||
|
const [sampleSize, setSampleSize] = React.useState<string>(DefaultSampleSize);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||||
|
<Stack.Item grow>
|
||||||
|
<TextField
|
||||||
|
value={filter}
|
||||||
|
onChange={(event, newValue) => setFilter(newValue)}
|
||||||
|
label="Filter"
|
||||||
|
placeholder={FilterPlaceholder}
|
||||||
|
disabled={!isKernelIdle}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<TextField
|
||||||
|
value={sampleSize}
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
const num = Number(newValue);
|
||||||
|
if (!newValue || (num >= MinSampleSize && num <= MaxSampleSize)) {
|
||||||
|
setSampleSize(newValue);
|
||||||
|
onSampleSizeUpdated(newValue);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
label="Sample size"
|
||||||
|
onRenderLabel={onSampleSizeWrapDefaultLabelRenderer}
|
||||||
|
placeholder={SampleSizePlaceholder}
|
||||||
|
disabled={!isKernelIdle}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item align="end">
|
||||||
|
<PrimaryButton
|
||||||
|
text={isKernelBusy ? "Analyzing..." : "Analyze"}
|
||||||
|
onClick={() => {
|
||||||
|
const sampleSizeToUse = sampleSize || DefaultSampleSize;
|
||||||
|
setSampleSize(sampleSizeToUse);
|
||||||
|
onAnalyzeButtonClick(filter, sampleSizeToUse);
|
||||||
|
}}
|
||||||
|
disabled={!isKernelIdle}
|
||||||
|
styles={{ root: { width: 120 } }}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item align="end">
|
||||||
|
<DefaultButton
|
||||||
|
text="Reset"
|
||||||
|
disabled={!isKernelIdle}
|
||||||
|
onClick={() => {
|
||||||
|
setFilter(DefaultFilter);
|
||||||
|
setSampleSize(DefaultSampleSize);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSampleSizeWrapDefaultLabelRenderer = (
|
||||||
|
props: ITextFieldProps,
|
||||||
|
defaultRender: IRenderFunction<ITextFieldProps>
|
||||||
|
): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 4 }}>
|
||||||
|
<span>{defaultRender(props)}</span>
|
||||||
|
<TooltipHost content={`Number of documents to sample between ${MinSampleSize} and ${MaxSampleSize}`}>
|
||||||
|
<Icon iconName="Info" ariaLabel="Info" />
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { FontIcon, PrimaryButton, Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
type SchemaAnalyzerSplashScreenProps = {
|
||||||
|
isKernelIdle: boolean;
|
||||||
|
isKernelBusy: boolean;
|
||||||
|
onAnalyzeButtonClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SchemaAnalyzerSplashScreen = ({
|
||||||
|
isKernelIdle,
|
||||||
|
isKernelBusy,
|
||||||
|
onAnalyzeButtonClick,
|
||||||
|
}: SchemaAnalyzerSplashScreenProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Stack horizontalAlign="center" tokens={{ childrenGap: 20, padding: 20 }}>
|
||||||
|
<Stack.Item>
|
||||||
|
<FontIcon iconName="Chart" style={{ fontSize: 100, color: "#43B1E5", marginTop: 40 }} />
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Text variant="xxLarge">Explore your schema</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Text variant="large">
|
||||||
|
Quickly visualize your schema to infer the frequency, types and ranges of fields in your data set.
|
||||||
|
</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<PrimaryButton
|
||||||
|
styles={{ root: { fontSize: 18, padding: 30 } }}
|
||||||
|
text={isKernelBusy ? "Analyzing..." : "Analyze Schema"}
|
||||||
|
onClick={() => onAnalyzeButtonClick()}
|
||||||
|
disabled={!isKernelIdle}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>{isKernelBusy && <Spinner size={SpinnerSize.large} />}</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Notebook } from "@nteract/commutable";
|
||||||
|
import { IContent } from "@nteract/types";
|
||||||
|
import * as InMemoryContentProviderUtils from "../NotebookComponent/ContentProviders/InMemoryContentProviderUtils";
|
||||||
|
|
||||||
|
const notebookName = "schema-analyzer-component-notebook.ipynb";
|
||||||
|
const notebookPath = InMemoryContentProviderUtils.toContentUri(notebookName);
|
||||||
|
const notebook: Notebook = {
|
||||||
|
cells: [
|
||||||
|
{
|
||||||
|
cell_type: "code",
|
||||||
|
metadata: {},
|
||||||
|
execution_count: 0,
|
||||||
|
outputs: [],
|
||||||
|
source: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
metadata: {
|
||||||
|
kernelspec: {
|
||||||
|
displayName: "Mongo",
|
||||||
|
language: "mongocli",
|
||||||
|
name: "mongo",
|
||||||
|
},
|
||||||
|
language_info: {
|
||||||
|
file_extension: "ipynb",
|
||||||
|
mimetype: "application/json",
|
||||||
|
name: "mongo",
|
||||||
|
version: "1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nbformat: 4,
|
||||||
|
nbformat_minor: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SchemaAnalyzerNotebook: IContent<"notebook"> = {
|
||||||
|
name: notebookName,
|
||||||
|
path: notebookPath,
|
||||||
|
type: "notebook",
|
||||||
|
writable: true,
|
||||||
|
created: "",
|
||||||
|
last_modified: "",
|
||||||
|
mimetype: "application/x-ipynb+json",
|
||||||
|
content: notebook,
|
||||||
|
format: "json",
|
||||||
|
};
|
|
@ -1,88 +0,0 @@
|
||||||
import { Notebook } from "@nteract/commutable";
|
|
||||||
import { actions, createContentRef, createKernelRef, IContent, KernelRef } from "@nteract/core";
|
|
||||||
import * as React from "react";
|
|
||||||
import { Provider } from "react-redux";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import {
|
|
||||||
NotebookComponentBootstrapper,
|
|
||||||
NotebookComponentBootstrapperOptions,
|
|
||||||
} from "../NotebookComponent/NotebookComponentBootstrapper";
|
|
||||||
import SchemaAnalyzerComponent from "./SchemaAnalyzerComponent";
|
|
||||||
|
|
||||||
export class SchemaAnalyzerComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
|
|
||||||
public parameters: unknown;
|
|
||||||
private kernelRef: KernelRef;
|
|
||||||
|
|
||||||
constructor(options: NotebookComponentBootstrapperOptions, private databaseId: string, private collectionId: string) {
|
|
||||||
super(options);
|
|
||||||
|
|
||||||
if (!this.contentRef) {
|
|
||||||
this.contentRef = createContentRef();
|
|
||||||
this.kernelRef = createKernelRef();
|
|
||||||
|
|
||||||
const notebook: Notebook = {
|
|
||||||
cells: [
|
|
||||||
{
|
|
||||||
cell_type: "code",
|
|
||||||
metadata: {},
|
|
||||||
execution_count: 0,
|
|
||||||
outputs: [],
|
|
||||||
source: "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
metadata: {
|
|
||||||
kernelspec: {
|
|
||||||
displayName: "Mongo",
|
|
||||||
language: "mongocli",
|
|
||||||
name: "mongo",
|
|
||||||
},
|
|
||||||
language_info: {
|
|
||||||
file_extension: "ipynb",
|
|
||||||
mimetype: "application/json",
|
|
||||||
name: "mongo",
|
|
||||||
version: "1.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nbformat: 4,
|
|
||||||
nbformat_minor: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
const model: IContent<"notebook"> = {
|
|
||||||
name: "schema-analyzer-component-notebook.ipynb",
|
|
||||||
path: "schema-analyzer-component-notebook.ipynb",
|
|
||||||
type: "notebook",
|
|
||||||
writable: true,
|
|
||||||
created: "",
|
|
||||||
last_modified: "",
|
|
||||||
mimetype: "application/x-ipynb+json",
|
|
||||||
content: notebook,
|
|
||||||
format: "json",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Request fetching notebook content
|
|
||||||
this.getStore().dispatch(
|
|
||||||
actions.fetchContentFulfilled({
|
|
||||||
filepath: model.path,
|
|
||||||
model,
|
|
||||||
kernelRef: this.kernelRef,
|
|
||||||
contentRef: this.contentRef,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
const props = {
|
|
||||||
contentRef: this.contentRef,
|
|
||||||
kernelRef: this.kernelRef,
|
|
||||||
databaseId: this.databaseId,
|
|
||||||
collectionId: this.collectionId,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Provider store={this.getStore()}>
|
|
||||||
<SchemaAnalyzerComponent {...props} />;
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,16 @@
|
||||||
import { SchemaAnalyzerComponentAdapter } from "../Notebook/SchemaAnalyzerComponent/SchemaAnalyzerComponentAdapter";
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { traceSuccess } from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { SchemaAnalyzerAdapter } from "../Notebook/SchemaAnalyzer/SchemaAnalyzerAdapter";
|
||||||
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
||||||
|
|
||||||
export default class SchemaAnalyzerTab extends NotebookTabBase {
|
export default class SchemaAnalyzerTab extends NotebookTabBase {
|
||||||
public readonly html = '<div data-bind="react:schemaAnalyzerComponentAdapter" style="height: 100%"></div>';
|
public readonly html = '<div data-bind="react:schemaAnalyzerAdapter" style="height: 100%"></div>';
|
||||||
private schemaAnalyzerComponentAdapter: SchemaAnalyzerComponentAdapter;
|
private schemaAnalyzerAdapter: SchemaAnalyzerAdapter;
|
||||||
|
|
||||||
constructor(options: NotebookTabBaseOptions) {
|
constructor(options: NotebookTabBaseOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this.schemaAnalyzerComponentAdapter = new SchemaAnalyzerComponentAdapter(
|
this.schemaAnalyzerAdapter = new SchemaAnalyzerAdapter(
|
||||||
{
|
{
|
||||||
contentRef: undefined,
|
contentRef: undefined,
|
||||||
notebookClient: NotebookTabBase.clientManager,
|
notebookClient: NotebookTabBase.clientManager,
|
||||||
|
@ -17,6 +20,21 @@ export default class SchemaAnalyzerTab extends NotebookTabBase {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onActivate(): void {
|
||||||
|
traceSuccess(
|
||||||
|
Action.Tab,
|
||||||
|
{
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id,
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: "Schema",
|
||||||
|
},
|
||||||
|
this.onLoadStartKey
|
||||||
|
);
|
||||||
|
|
||||||
|
super.onActivate();
|
||||||
|
}
|
||||||
|
|
||||||
protected buildCommandBarOptions(): void {
|
protected buildCommandBarOptions(): void {
|
||||||
this.updateNavbarWithTabsButtons();
|
this.updateNavbarWithTabsButtons();
|
||||||
}
|
}
|
||||||
|
|
|
@ -511,7 +511,7 @@ export default class Collection implements ViewModels.Collection {
|
||||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer);
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer);
|
||||||
const SchemaAnalyzerTab = await (await import("../Tabs/SchemaAnalyzerTab")).default;
|
const SchemaAnalyzerTab = await (await import("../Tabs/SchemaAnalyzerTab")).default;
|
||||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||||
description: "Mongo Schema node",
|
description: "Schema node",
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
collectionName: this.id(),
|
collectionName: this.id(),
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as ko from "knockout";
|
|
||||||
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
||||||
|
import * as ko from "knockout";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
||||||
import DeleteIcon from "../../../images/delete.svg";
|
import DeleteIcon from "../../../images/delete.svg";
|
||||||
|
@ -273,7 +273,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection),
|
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userContext.apiType === "Mongo" && userContext.features.enableSchemaAnalyzer) {
|
if (
|
||||||
|
userContext.apiType === "Mongo" &&
|
||||||
|
this.container.isNotebookEnabled() &&
|
||||||
|
userContext.features.enableSchemaAnalyzer
|
||||||
|
) {
|
||||||
children.push({
|
children.push({
|
||||||
label: "Schema (Preview)",
|
label: "Schema (Preview)",
|
||||||
onClick: collection.onSchemaAnalyzerClick.bind(collection),
|
onClick: collection.onSchemaAnalyzerClick.bind(collection),
|
||||||
|
|
|
@ -8,7 +8,7 @@ export type Features = {
|
||||||
readonly enableReactPane: boolean;
|
readonly enableReactPane: boolean;
|
||||||
readonly enableRightPanelV2: boolean;
|
readonly enableRightPanelV2: boolean;
|
||||||
readonly enableSchema: boolean;
|
readonly enableSchema: boolean;
|
||||||
readonly enableSchemaAnalyzer: boolean;
|
enableSchemaAnalyzer: boolean;
|
||||||
readonly enableSDKoperations: boolean;
|
readonly enableSDKoperations: boolean;
|
||||||
readonly enableSpark: boolean;
|
readonly enableSpark: boolean;
|
||||||
readonly enableTtl: boolean;
|
readonly enableTtl: boolean;
|
||||||
|
|
|
@ -116,6 +116,7 @@ export enum Action {
|
||||||
NotebooksGalleryPublishedCount,
|
NotebooksGalleryPublishedCount,
|
||||||
SelfServe,
|
SelfServe,
|
||||||
ExpandAddCollectionPaneAdvancedSection,
|
ExpandAddCollectionPaneAdvancedSection,
|
||||||
|
SchemaAnalyzerClickAnalyze,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionModifiers = {
|
export const ActionModifiers = {
|
||||||
|
|
Loading…
Reference in New Issue