From 23e82d9ac64894d093a6a2ad3a9446912732ccd4 Mon Sep 17 00:00:00 2001 From: Will Scullin Date: Fri, 1 Sep 2023 12:32:54 -0700 Subject: [PATCH] Add named queries --- src/common/message_types.ts | 6 ++++ src/extension/notebook/malloy_controller.ts | 10 ++++--- .../notebook/renderer/schema_entry.tsx | 27 +++++++++++++---- src/extension/tree_views/schema_view.ts | 17 ++++------- .../webviews/components/SchemaRenderer.tsx | 29 +++++++++++++++++-- src/extension/webviews/query_page/App.tsx | 1 + src/server/translate_cache.ts | 24 +++++++++++++-- 7 files changed, 87 insertions(+), 27 deletions(-) diff --git a/src/common/message_types.ts b/src/common/message_types.ts index eda2b134..9d628884 100755 --- a/src/common/message_types.ts +++ b/src/common/message_types.ts @@ -24,6 +24,7 @@ import { MalloyError, MalloyQueryData, + NamedQuery, ResultJSON, SerializedExplore, } from '@malloydata/malloy'; @@ -365,3 +366,8 @@ export type HelpPanelMessage = HelpMessageAppReady | HelpMessageEditConnections; export const queryPanelProgress = new ProgressType(); export const msqlPanelProgress = new ProgressType(); + +export interface FetchModelMessage { + explores: SerializedExplore[]; + queries: NamedQuery[]; +} diff --git a/src/extension/notebook/malloy_controller.ts b/src/extension/notebook/malloy_controller.ts index 9a01e5d0..f4006418 100644 --- a/src/extension/notebook/malloy_controller.ts +++ b/src/extension/notebook/malloy_controller.ts @@ -29,7 +29,7 @@ import {errorMessage} from '../../common/errors'; import {BuildModelRequest, CellMetadata, QueryCost} from '../../common/types'; import {convertFromBytes} from '../../common/convert_to_bytes'; import {BaseLanguageClient} from 'vscode-languageclient'; -import {SerializedExplore} from '@malloydata/malloy'; +import {FetchModelMessage} from '../../common/message_types'; const NO_QUERY = 'Model has no queries.'; @@ -244,12 +244,14 @@ class MalloyController { vscode.NotebookCellOutputItem.text('Loading Schema Information'), ]), ]); - const serializedExplores: SerializedExplore[] = - await this.client.sendRequest('malloy/fetchModel', request); + const model: FetchModelMessage = await this.client.sendRequest( + 'malloy/fetchModel', + request + ); execution.replaceOutput([ new vscode.NotebookCellOutput([ vscode.NotebookCellOutputItem.json( - serializedExplores, + model, 'x-application/malloy-schema' ), ]), diff --git a/src/extension/notebook/renderer/schema_entry.tsx b/src/extension/notebook/renderer/schema_entry.tsx index c4d87953..06b3f100 100644 --- a/src/extension/notebook/renderer/schema_entry.tsx +++ b/src/extension/notebook/renderer/schema_entry.tsx @@ -26,8 +26,9 @@ import React from 'react'; import {StyleSheetManager} from 'styled-components'; import {ActivationFunction} from 'vscode-notebook-renderer'; import {SchemaRenderer} from '../../webviews/components/SchemaRenderer'; -import {Explore, Field, SerializedExplore} from '@malloydata/malloy'; +import {Explore, Field, NamedQuery} from '@malloydata/malloy'; import {fieldType} from '../../common/schema'; +import {FetchModelMessage} from '../../../common/message_types'; export const activate: ActivationFunction = ({postMessage}) => { return { @@ -58,7 +59,7 @@ export const activate: ActivationFunction = ({postMessage}) => { }; interface SchemaRendererWrapperProps { - results: SerializedExplore[]; + results: FetchModelMessage; postMessage?: (message: unknown) => void; } @@ -73,15 +74,16 @@ const SchemaRendererWrapper = ({ postMessage, }: SchemaRendererWrapperProps) => { const [explores, setExplores] = React.useState(); + const [queries, setQueries] = React.useState(); React.useEffect(() => { if (results) { - const explores = results.map(json => Explore.fromJSON(json)); - setExplores(explores); + setExplores(results.explores.map(json => Explore.fromJSON(json))); + setQueries(results.queries); } }, [results]); - if (!explores) { + if (!explores || !queries) { return null; } @@ -114,5 +116,18 @@ const SchemaRendererWrapper = ({ } }; - return ; + const onQueryClick = (query: NamedQuery) => { + const type = 'malloy.runNamedQuery'; + const args = [query.name]; + postMessage?.({type, args}); + }; + + return ( + + ); }; diff --git a/src/extension/tree_views/schema_view.ts b/src/extension/tree_views/schema_view.ts index b3c6accf..8122d967 100644 --- a/src/extension/tree_views/schema_view.ts +++ b/src/extension/tree_views/schema_view.ts @@ -24,13 +24,7 @@ import * as vscode from 'vscode'; import {Utils} from 'vscode-uri'; -import { - Explore, - Field, - QueryField, - AtomicField, - SerializedExplore, -} from '@malloydata/malloy'; +import {Explore, Field, QueryField, AtomicField} from '@malloydata/malloy'; import numberIcon from '../../media/number.svg'; import numberAggregateIcon from '../../media/number-aggregate.svg'; import booleanIcon from '../../media/boolean.svg'; @@ -45,6 +39,7 @@ import {MALLOY_EXTENSION_STATE} from '../state'; import {BaseLanguageClient} from 'vscode-languageclient/node'; import {BuildModelRequest} from '../../common/types'; import {exploreSubtype, fieldType, isFieldAggregate} from '../common/schema'; +import {FetchModelMessage} from '../../common/message_types'; export class SchemaProvider implements vscode.TreeDataProvider @@ -146,14 +141,12 @@ async function getStructs( version: document.version, languageId: document.languageId, }; - const serialized_explores: SerializedExplore[] = await client.sendRequest( + const model: FetchModelMessage = await client.sendRequest( 'malloy/fetchModel', request ); - const explores = serialized_explores.map(explore => - Explore.fromJSON(explore) - ); - return Object.values(explores).sort(exploresByName); + const explores = model.explores.map(explore => Explore.fromJSON(explore)); + return explores.sort(exploresByName); } catch (error) { return undefined; } diff --git a/src/extension/webviews/components/SchemaRenderer.tsx b/src/extension/webviews/components/SchemaRenderer.tsx index e18c82a9..c5acef2c 100644 --- a/src/extension/webviews/components/SchemaRenderer.tsx +++ b/src/extension/webviews/components/SchemaRenderer.tsx @@ -23,7 +23,7 @@ import * as React from 'react'; import styled from 'styled-components'; -import {Explore, Field} from '@malloydata/malloy'; +import {Explore, Field, NamedQuery} from '@malloydata/malloy'; import {exploreSubtype, fieldType, isFieldAggregate} from '../../common/schema'; import NumberIcon from '../../../media/number.svg'; import NumberAggregateIcon from '../../../media/number-aggregate.svg'; @@ -40,8 +40,10 @@ import OneToOneIcon from '../../../media/one_to_one.svg'; */ export interface SchemaRendererProps { explores: Explore[]; + queries: NamedQuery[]; defaultShow?: boolean; onFieldClick?: (field: Field) => void; + onQueryClick?: (query: NamedQuery) => void; } /** @@ -148,7 +150,7 @@ Path: ${path}${path ? '.' : ''}${fieldName} Type: ${type}`; } -const sortByName = (a: Field | Explore, b: Field | Explore) => +const sortByName = (a: {name: string}, b: {name: string}) => a.name.localeCompare(b.name); /** @@ -193,8 +195,10 @@ function bucketFields(fields: Field[]) { */ export const SchemaRenderer: React.FC = ({ explores, + queries, defaultShow = false, onFieldClick, + onQueryClick, }) => { if (!explores || !explores.length) { return No Schema Information; @@ -300,9 +304,30 @@ export const SchemaRenderer: React.FC = ({ ); }; + const QueryItem = ({query}: {query: NamedQuery}) => { + const onClick = () => { + onQueryClick?.(query); + }; + const clickable = onQueryClick ? 'clickable' : ''; + + return ( +
+ {getIconElement('query', false)} + {query.name} +
+ ); + }; + return (
    +
  • +
    + {queries.sort(sortByName).map(query => ( + + ))} +
    +
  • {explores.sort(sortByName).map(explore => ( ))} diff --git a/src/extension/webviews/query_page/App.tsx b/src/extension/webviews/query_page/App.tsx index c8091586..653bcc54 100755 --- a/src/extension/webviews/query_page/App.tsx +++ b/src/extension/webviews/query_page/App.tsx @@ -341,6 +341,7 @@ export const App: React.FC = () => { diff --git a/src/server/translate_cache.ts b/src/server/translate_cache.ts index 3d15a137..0992f7a4 100644 --- a/src/server/translate_cache.ts +++ b/src/server/translate_cache.ts @@ -26,8 +26,9 @@ import { MalloyError, Model, ModelMaterializer, + NamedModelObject, + NamedQuery, Runtime, - SerializedExplore, } from '@malloydata/malloy'; import {TextDocument} from 'vscode-languageserver-textdocument'; @@ -39,6 +40,10 @@ import { MalloySQLSQLStatement, MalloySQLStatementType, } from '@malloydata/malloy-sql'; +import {FetchModelMessage} from '../common/message_types'; + +const isNamedQuery = (object: NamedModelObject): object is NamedQuery => + object.type === 'query'; export class TranslateCache implements TranslateCache { cache = new Map(); @@ -50,13 +55,26 @@ export class TranslateCache implements TranslateCache { ) { connection.onRequest( 'malloy/fetchModel', - async (event: BuildModelRequest): Promise => { + async (event: BuildModelRequest): Promise => { const model = await this.translateWithCache( event.uri, event.version, event.languageId ); - return model?.explores.map(explore => explore.toJSON()) || []; + if (model) { + return { + explores: model.explores.map(explore => explore.toJSON()) || [], + // TODO(whscullin) - Create non-backdoor access method + queries: Object.values(model._modelDef.contents).filter( + isNamedQuery + ), + }; + } else { + return { + explores: [], + queries: [], + }; + } } ); }