import { Spinner, SpinnerSize, Stack } from "@fluentui/react"; import { ImmutableExecuteResult, ImmutableOutput } from "@nteract/commutable"; import { actions, AppState, ContentRef, KernelRef, selectors } from "@nteract/core"; import Immutable from "immutable"; import * as React from "react"; import { connect } from "react-redux"; import { Dispatch } from "redux"; import * as Logger from "../../../Common/Logger"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor"; import loadTransform from "../NotebookComponent/loadTransform"; import SandboxOutputs from "../NotebookRenderer/outputs/SandboxOutputs"; import "./SchemaAnalyzer.less"; import { DefaultFilter, DefaultSampleSize, SchemaAnalyzerHeader } from "./SchemaAnalyzerHeader"; import { SchemaAnalyzerSplashScreen } from "./SchemaAnalyzerSplashScreen"; interface SchemaAnalyzerPureProps { contentRef: ContentRef; kernelRef: KernelRef; databaseId: string; collectionId: string; } interface SchemaAnalyzerDispatchProps { runCell: (contentRef: ContentRef, cellId: string) => void; addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => void; updateCell: (text: string, id: string, contentRef: ContentRef) => void; } type OutputType = "rich" | "json"; interface SchemaAnalyzerState { outputType: OutputType; isFiltering: boolean; sampleSize: string; } type SchemaAnalyzerProps = SchemaAnalyzerPureProps & StateProps & SchemaAnalyzerDispatchProps; export class SchemaAnalyzer extends React.Component { private clickAnalyzeTelemetryStartKey: number; constructor(props: SchemaAnalyzerProps) { super(props); this.state = { outputType: "rich", isFiltering: false, sampleSize: DefaultSampleSize, }; } componentDidMount(): void { loadTransform(this.props); } private onAnalyzeButtonClick = (filter: string = DefaultFilter, sampleSize: string = this.state.sampleSize) => { const query = { command: "listSchema", database: this.props.databaseId, collection: this.props.collectionId, outputType: this.state.outputType, filter, sampleSize, }; this.setState({ isFiltering: true, }); 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); }; private traceClickAnalyzeComplete = (kernelStatus: string, outputs: Immutable.List) => { /** * 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); Logger.logError(`Failed to analyze schema: ${JSON.stringify(data)}`, "SchemaAnalyzer/traceClickAnalyzeComplete"); } else { traceSuccess(Action.SchemaAnalyzerClickAnalyze, data, this.clickAnalyzeTelemetryStartKey); } }; render(): JSX.Element { const { firstCellId: id, contentRef, kernelStatus, outputs } = this.props; if (!id) { return <>; } const isKernelBusy = kernelStatus === "busy"; const isKernelIdle = kernelStatus === "idle"; const showSchemaOutput = isKernelIdle && outputs?.size > 0; if (showSchemaOutput && this.clickAnalyzeTelemetryStartKey) { this.traceClickAnalyzeComplete(kernelStatus, outputs); this.clickAnalyzeTelemetryStartKey = undefined; } return (
this.setState({ sampleSize })} onAnalyzeButtonClick={this.onAnalyzeButtonClick} /> {showSchemaOutput ? ( ) : this.state.isFiltering ? ( ) : ( )}
); } } interface StateProps { firstCellId: string; kernelStatus: string; outputs: Immutable.List; } interface InitialProps { kernelRef: string; contentRef: string; } // Redux const makeMapStateToProps = (state: AppState, initialProps: InitialProps) => { const { kernelRef, contentRef } = initialProps; const mapStateToProps = (state: AppState) => { let kernelStatus; let firstCellId; let outputs; const kernel = selectors.kernel(state, { kernelRef }); if (kernel) { kernelStatus = kernel.status; } const content = selectors.content(state, { contentRef }); if (content?.type === "notebook") { const cellOrder = selectors.notebook.cellOrder(content.model); if (cellOrder.size > 0) { firstCellId = cellOrder.first() as string; const model = selectors.model(state, { contentRef }); if (model && model.type === "notebook") { const cell = selectors.notebook.cellById(model, { id: firstCellId }); if (cell) { outputs = cell.get("outputs", Immutable.List()); } } } } return { firstCellId, kernelStatus, outputs, }; }; return mapStateToProps; }; const makeMapDispatchToProps = () => { const mapDispatchToProps = (dispatch: Dispatch) => { return { addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => { return dispatch( actions.addTransform({ mediaType: transform.MIMETYPE, component: transform, }), ); }, runCell: (contentRef: ContentRef, cellId: string) => { return dispatch( actions.executeCell({ contentRef, id: cellId, }), ); }, updateCell: (text: string, id: string, contentRef: ContentRef) => { dispatch(actions.updateCellSource({ id, contentRef, value: text })); }, }; }; return mapDispatchToProps; }; export default connect(makeMapStateToProps, makeMapDispatchToProps)(SchemaAnalyzer);