@malloy-publisher/server 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/api-doc.yaml +127 -53
- package/dist/app/assets/EnvironmentPage-B-t_QP32.js +1 -0
- package/dist/app/assets/{HomePage-D3MvQaZn.js → HomePage-Cpu2zMy-.js} +1 -1
- package/dist/app/assets/{LightMode-e1lNBfpL.js → LightMode-DOBl6awB.js} +1 -1
- package/dist/app/assets/MainPage-C7ySrclP.js +1 -0
- package/dist/app/assets/ModelPage-D8r_J-N9.js +2 -0
- package/dist/app/assets/PackagePage-CcVFBhnT.js +1 -0
- package/dist/app/assets/RenderedResult-CBLHr32C.es-DRr9Gmsz.js +141 -0
- package/dist/app/assets/{RouteError-BQnzPJvb.js → RouteError-mgOkv7XK.js} +1 -1
- package/dist/app/assets/ThemeEditorPage-CIPDs2w9.js +1 -0
- package/dist/app/assets/ToggleButtonGroup-CEZThrzh.js +1 -0
- package/dist/app/assets/{index-CCNqF0-G.js → index-Bg-M8sZz.js} +1 -1
- package/dist/app/assets/index-CGdwupPP.js +457 -0
- package/dist/app/assets/{index-CKmZ4HG7.js → index-CeLTZAFW.js} +116 -116
- package/dist/app/assets/{index-CdExa3Pk.js → index-DoF8ihra.js} +1 -1
- package/dist/app/assets/{index-ufFZR_K-.js → index-IC0jPCkR.js} +1 -1
- package/dist/app/assets/index.es-6eARPKZf.js +198 -0
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +436 -56
- package/dist/server.mjs +9537 -9076
- package/package.json +1 -1
- package/dist/app/assets/EnvironmentPage-UMS7Py4Q.js +0 -1
- package/dist/app/assets/MainPage-B0ozqtQO.js +0 -2
- package/dist/app/assets/ModelPage-lQj4Axqu.js +0 -2
- package/dist/app/assets/PackagePage-BT0YKNKU.js +0 -1
- package/dist/app/assets/RenderedResult-BRgIgxWh.es-DiE7kPNX.js +0 -136
- package/dist/app/assets/ThemeEditorPage-D6t7w9Js.js +0 -1
- package/dist/app/assets/ToggleButtonGroup-CJT2JKTC.js +0 -1
- package/dist/app/assets/index-TiWjmdoW.js +0 -450
- package/dist/app/assets/index.es-iUV0gCYk.js +0 -111
- package/dist/cpufeatures-1yrn0vtw.node +0 -0
|
@@ -13195,7 +13195,7 @@ function toQueryTextProblem(problem) {
|
|
|
13195
13195
|
...problem.at ? { at: { range: problem.at.range } } : {}
|
|
13196
13196
|
};
|
|
13197
13197
|
}
|
|
13198
|
-
var GENERIC_INTERNAL_MESSAGE = "Internal server error.", GENERIC_UPSTREAM_MESSAGE = "Upstream connection error.", MAX_LOGGED_DETAIL_CHARS = 2000, FILESYSTEM_ACCESS_DESCRIPTIONS, NotImplementedError, BadRequestError, InvalidArgumentError, CompileRefusedError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, PackageManifestError, MaterializationEligibilityError, PublisherConfigError, ServerConfigurationError, FrozenConfigError, AccessDeniedError, QueryCompileError, NotQueryableError, OffSurfaceError, MaterializationNotFoundError, MaterializationConflictError, WriteConflictError, WriteRolledBackError, InvalidStateTransitionError, ServiceUnavailableError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
|
|
13198
|
+
var GENERIC_INTERNAL_MESSAGE = "Internal server error.", GENERIC_UPSTREAM_MESSAGE = "Upstream connection error.", MAX_LOGGED_DETAIL_CHARS = 2000, FILESYSTEM_ACCESS_DESCRIPTIONS, NotImplementedError, BadRequestError, InvalidArgumentError, CompileRefusedError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, PackageManifestError, MaterializationEligibilityError, PublisherConfigError, ServerConfigurationError, FrozenConfigError, AccessDeniedError, QueryCompileError, NotQueryableError, OffSurfaceError, MaterializationNotFoundError, MaterializationConflictError, WriteConflictError, WriteRolledBackError, WriteVerifyError, InvalidStateTransitionError, ServiceUnavailableError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
|
|
13199
13199
|
var init_errors = __esm(() => {
|
|
13200
13200
|
init_constants();
|
|
13201
13201
|
init_logger();
|
|
@@ -13348,11 +13348,17 @@ var init_errors = __esm(() => {
|
|
|
13348
13348
|
}
|
|
13349
13349
|
};
|
|
13350
13350
|
WriteRolledBackError = class WriteRolledBackError extends Error {
|
|
13351
|
-
constructor(message) {
|
|
13352
|
-
super(message);
|
|
13351
|
+
constructor(message, options) {
|
|
13352
|
+
super(message, options);
|
|
13353
13353
|
this.name = "WriteRolledBackError";
|
|
13354
13354
|
}
|
|
13355
13355
|
};
|
|
13356
|
+
WriteVerifyError = class WriteVerifyError extends Error {
|
|
13357
|
+
constructor(message, options) {
|
|
13358
|
+
super(message, options);
|
|
13359
|
+
this.name = "WriteVerifyError";
|
|
13360
|
+
}
|
|
13361
|
+
};
|
|
13356
13362
|
InvalidStateTransitionError = class InvalidStateTransitionError extends Error {
|
|
13357
13363
|
constructor(message) {
|
|
13358
13364
|
super(message);
|
|
@@ -13407,6 +13413,7 @@ var init_authorize_routes = __esm(() => {
|
|
|
13407
13413
|
|
|
13408
13414
|
// src/service/authorize.ts
|
|
13409
13415
|
import { payloadOf, routeOf } from "@malloydata/malloy";
|
|
13416
|
+
import { createRequire as createRequire2 } from "module";
|
|
13410
13417
|
function noteRoute(text) {
|
|
13411
13418
|
return routeOf({ value: text.trimStart() });
|
|
13412
13419
|
}
|
|
@@ -13455,7 +13462,7 @@ function motlyAuthorizePayloadPattern(route) {
|
|
|
13455
13462
|
return new RegExp(`^[ \\t]*[([{<][ \\t]*(?:${nearMissWordAlternation(route)})[ \\t]*[)\\]}>]`, "iu");
|
|
13456
13463
|
}
|
|
13457
13464
|
function malformedAuthorizeAttemptPattern(route) {
|
|
13458
|
-
return new RegExp(`^##?\\|?[ \\t]*[([{<]
|
|
13465
|
+
return new RegExp(`^##?\\|?[ \\t]*(?:[([{<][ \\t]*)?(?:${nearMissWordAlternation(route)})`, "iu");
|
|
13459
13466
|
}
|
|
13460
13467
|
function authorizeNoteContent(text) {
|
|
13461
13468
|
const route = canonicalAuthorizeRoute(noteRoute(text));
|
|
@@ -13466,12 +13473,144 @@ function authorizeNoteContent(text) {
|
|
|
13466
13473
|
function authorizeAnnotationRoute(text) {
|
|
13467
13474
|
return authorizeNoteContent(text)?.route;
|
|
13468
13475
|
}
|
|
13469
|
-
function
|
|
13470
|
-
|
|
13476
|
+
function loadMalloyParser() {
|
|
13477
|
+
try {
|
|
13478
|
+
const entry = createRequire2(import.meta.url).resolve("@malloydata/malloy");
|
|
13479
|
+
const loaded = createRequire2(entry)("./lang/run-malloy-parser");
|
|
13480
|
+
return typeof loaded.makeMalloyParser === "function" ? loaded.makeMalloyParser : null;
|
|
13481
|
+
} catch {
|
|
13482
|
+
return null;
|
|
13483
|
+
}
|
|
13484
|
+
}
|
|
13485
|
+
function malloyParserFactory() {
|
|
13486
|
+
if (makeMalloyParser === undefined)
|
|
13487
|
+
makeMalloyParser = parserLoader();
|
|
13488
|
+
return makeMalloyParser ?? undefined;
|
|
13489
|
+
}
|
|
13490
|
+
function lastCallerGuardRefusalKind() {
|
|
13491
|
+
return lastRefusalKind;
|
|
13492
|
+
}
|
|
13493
|
+
function proseRanges(text) {
|
|
13494
|
+
if (lastLexed.text !== text)
|
|
13495
|
+
lastLexed = { text, prose: lexProseRanges(text) };
|
|
13496
|
+
return lastLexed.prose;
|
|
13497
|
+
}
|
|
13498
|
+
function lexProseRanges(text) {
|
|
13499
|
+
const factory = malloyParserFactory();
|
|
13500
|
+
lexerMissing = !factory;
|
|
13501
|
+
if (!factory) {
|
|
13502
|
+
if (!warnedNoParser) {
|
|
13503
|
+
warnedNoParser = true;
|
|
13504
|
+
console.warn("Prose-aware caller-annotation guard disabled: @malloydata/malloy ./lang/run-malloy-parser could not be loaded; falling back to whole-text matching.");
|
|
13505
|
+
}
|
|
13506
|
+
return;
|
|
13507
|
+
}
|
|
13508
|
+
try {
|
|
13509
|
+
const counter = () => ({
|
|
13510
|
+
errors: 0,
|
|
13511
|
+
syntaxError() {
|
|
13512
|
+
this.errors++;
|
|
13513
|
+
}
|
|
13514
|
+
});
|
|
13515
|
+
const lexerErrors = counter();
|
|
13516
|
+
const parserErrors = counter();
|
|
13517
|
+
const { tokenStream, parser } = factory(text, {
|
|
13518
|
+
lexerErrorListener: lexerErrors,
|
|
13519
|
+
parserErrorListener: parserErrors
|
|
13520
|
+
});
|
|
13521
|
+
parser.malloyDocument();
|
|
13522
|
+
const vocabulary = tokenStream.tokenSource?.vocabulary;
|
|
13523
|
+
if (lexerErrors.errors > 0 || parserErrors.errors > 0 || typeof vocabulary?.getSymbolicName !== "function")
|
|
13524
|
+
return;
|
|
13525
|
+
const utf16 = [];
|
|
13526
|
+
for (let i = 0;i < text.length; ) {
|
|
13527
|
+
utf16.push(i);
|
|
13528
|
+
i += text.codePointAt(i) > 65535 ? 2 : 1;
|
|
13529
|
+
}
|
|
13530
|
+
utf16.push(text.length);
|
|
13531
|
+
const rangeOf = (token) => {
|
|
13532
|
+
const from = utf16[token.startIndex];
|
|
13533
|
+
const to = utf16[token.stopIndex + 1];
|
|
13534
|
+
return from === undefined || to === undefined || to < from ? undefined : [from, to];
|
|
13535
|
+
};
|
|
13536
|
+
const prose = [];
|
|
13537
|
+
let block;
|
|
13538
|
+
for (const token of tokenStream.getTokens()) {
|
|
13539
|
+
const name = vocabulary.getSymbolicName(token.type);
|
|
13540
|
+
const range = rangeOf(token);
|
|
13541
|
+
if (name === "BLOCK_ANNOTATION_TEXT" || name === "BLOCK_ANNOTATION_END") {
|
|
13542
|
+
if (!block || !range || token.startIndex !== block.next)
|
|
13543
|
+
return;
|
|
13544
|
+
if (name === "BLOCK_ANNOTATION_END") {
|
|
13545
|
+
block = undefined;
|
|
13546
|
+
} else {
|
|
13547
|
+
if (block.prose)
|
|
13548
|
+
prose.push(range);
|
|
13549
|
+
block.next = token.stopIndex + 1;
|
|
13550
|
+
}
|
|
13551
|
+
continue;
|
|
13552
|
+
}
|
|
13553
|
+
if (block)
|
|
13554
|
+
return;
|
|
13555
|
+
if (name === "BLOCK_ANNOTATION_BEGIN" || name === "DOC_BLOCK_ANNOTATION_BEGIN") {
|
|
13556
|
+
if (!range)
|
|
13557
|
+
return;
|
|
13558
|
+
block = {
|
|
13559
|
+
prose: PROSE_ROUTES.has(noteRoute(text.slice(...range)) ?? ""),
|
|
13560
|
+
next: token.stopIndex + 1
|
|
13561
|
+
};
|
|
13562
|
+
} else if (name === "ANNOTATION" || name === "DOC_ANNOTATION") {
|
|
13563
|
+
if (!range)
|
|
13564
|
+
return;
|
|
13565
|
+
if (PROSE_ROUTES.has(noteRoute(text.slice(...range)) ?? ""))
|
|
13566
|
+
prose.push([range[0] + 1, range[1]]);
|
|
13567
|
+
}
|
|
13568
|
+
}
|
|
13569
|
+
return block ? undefined : prose;
|
|
13570
|
+
} catch {
|
|
13571
|
+
return;
|
|
13572
|
+
}
|
|
13573
|
+
}
|
|
13574
|
+
function hasCallerAuthorizeAnnotation(callerText, precedingText = "") {
|
|
13575
|
+
if (!AUTHORIZE_ANNOTATION_ANYWHERE.test(callerText))
|
|
13576
|
+
return false;
|
|
13577
|
+
const text = precedingText + callerText;
|
|
13578
|
+
const bodies = proseRanges(text);
|
|
13579
|
+
lastRefusalKind = bodies !== undefined ? "lexed" : lexerMissing ? "no_lexer" : "whole_text";
|
|
13580
|
+
if (bodies === undefined)
|
|
13581
|
+
return true;
|
|
13582
|
+
const sticky = new RegExp(AUTHORIZE_TAG_LIKE, "iuy");
|
|
13583
|
+
const callerStart = precedingText.length;
|
|
13584
|
+
const from = text.lastIndexOf(`
|
|
13585
|
+
`, callerStart - 1) + 1;
|
|
13586
|
+
let body = 0;
|
|
13587
|
+
for (let at = text.indexOf("#", from);at !== -1; at = text.indexOf("#", at + 1)) {
|
|
13588
|
+
sticky.lastIndex = at;
|
|
13589
|
+
const hit = sticky.exec(text);
|
|
13590
|
+
if (!hit)
|
|
13591
|
+
continue;
|
|
13592
|
+
const end = at + hit[0].length;
|
|
13593
|
+
if (end <= callerStart)
|
|
13594
|
+
continue;
|
|
13595
|
+
while (body < bodies.length && bodies[body][1] <= at)
|
|
13596
|
+
body++;
|
|
13597
|
+
const [start, stop] = bodies[body] ?? [Infinity, -Infinity];
|
|
13598
|
+
if (!(start <= at && end <= stop))
|
|
13599
|
+
return true;
|
|
13600
|
+
}
|
|
13601
|
+
return false;
|
|
13602
|
+
}
|
|
13603
|
+
function assertNoCallerAuthorizeAnnotation(callerText, precedingText = "") {
|
|
13604
|
+
if (hasCallerAuthorizeAnnotation(callerText, precedingText))
|
|
13605
|
+
refuseCallerAuthorizeAnnotation();
|
|
13471
13606
|
}
|
|
13472
|
-
function
|
|
13607
|
+
function assertNoAuthorizeTagLike(callerText) {
|
|
13473
13608
|
if (!AUTHORIZE_ANNOTATION_ANYWHERE.test(callerText))
|
|
13474
13609
|
return;
|
|
13610
|
+
lastRefusalKind = "whole_text";
|
|
13611
|
+
refuseCallerAuthorizeAnnotation();
|
|
13612
|
+
}
|
|
13613
|
+
function refuseCallerAuthorizeAnnotation() {
|
|
13475
13614
|
throw new BadRequestError("An `authorize` annotation is not permitted in caller-submitted Malloy " + "text. Access gates are declared by the model author on the source; a " + "request cannot introduce, replace, or relax one. To validate a gate " + "you are authoring, save it to the package's model file and reload the " + "package — model load validates every `#(authorize)` annotation it " + "declares.");
|
|
13476
13615
|
}
|
|
13477
13616
|
function containsAuthorizeAnnotationTag(texts) {
|
|
@@ -13669,13 +13808,19 @@ ${rewrites}
|
|
|
13669
13808
|
` + `Test that the rewrite gates the same rows and check the row count ` + `matches what the string form served.`
|
|
13670
13809
|
});
|
|
13671
13810
|
}
|
|
13672
|
-
var AUTHORIZE_TAG_LIKE, AUTHORIZE_ANNOTATION_ANYWHERE, REGEXP_SPECIAL, GIVEN_REF_PATTERN, SINGLE_QUOTE_ESCAPE, DOUBLE_QUOTE_ESCAPE, STRING_LITERAL_PATTERN, everyMemberOf = () => (...members) => members, ROW_LEVEL_GATE_REJECTION_CAUSES, WHOLE_BODY_QUOTED_STRING, WHOLE_BODY_SINGLE_QUOTED_STRING;
|
|
13811
|
+
var AUTHORIZE_TAG_LIKE, AUTHORIZE_ANNOTATION_ANYWHERE, REGEXP_SPECIAL, PROSE_ROUTES, makeMalloyParser, parserLoader, warnedNoParser = false, lastLexed, lastRefusalKind = "lexed", lexerMissing = false, GIVEN_REF_PATTERN, SINGLE_QUOTE_ESCAPE, DOUBLE_QUOTE_ESCAPE, STRING_LITERAL_PATTERN, everyMemberOf = () => (...members) => members, ROW_LEVEL_GATE_REJECTION_CAUSES, WHOLE_BODY_QUOTED_STRING, WHOLE_BODY_SINGLE_QUOTED_STRING;
|
|
13673
13812
|
var init_authorize = __esm(() => {
|
|
13674
13813
|
init_errors();
|
|
13675
13814
|
init_authorize_routes();
|
|
13676
|
-
AUTHORIZE_TAG_LIKE = String.raw`##?\|?[ \t]*[([{<]
|
|
13815
|
+
AUTHORIZE_TAG_LIKE = String.raw`##?\|?[ \t]*(?:[([{<][ \t]*)?(?:(?:(?:row|source)[-_]?)?authorize|access[-_]?filter)(?=[)\]}>]|[ \t]|$)`;
|
|
13677
13816
|
AUTHORIZE_ANNOTATION_ANYWHERE = new RegExp(AUTHORIZE_TAG_LIKE, "iu");
|
|
13678
13817
|
REGEXP_SPECIAL = /[.*+?^${}()|[\]\\]/g;
|
|
13818
|
+
PROSE_ROUTES = new Set(["markdown", "text"]);
|
|
13819
|
+
parserLoader = loadMalloyParser;
|
|
13820
|
+
lastLexed = {
|
|
13821
|
+
text: "",
|
|
13822
|
+
prose: []
|
|
13823
|
+
};
|
|
13679
13824
|
GIVEN_REF_PATTERN = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
13680
13825
|
SINGLE_QUOTE_ESCAPE = /\\(['\\])/g;
|
|
13681
13826
|
DOUBLE_QUOTE_ESCAPE = /\\(["\\])/g;
|
|
@@ -13688,9 +13833,12 @@ var init_authorize = __esm(() => {
|
|
|
13688
13833
|
// src/authorize_metrics.ts
|
|
13689
13834
|
function recordAuthorizeGuardRejection(field) {
|
|
13690
13835
|
guardRejectionCounter ??= publisherMeter().createCounter("publisher_authorize_guard_rejected_total", {
|
|
13691
|
-
description: "Requests rejected with 400 for declaring an `#(authorize)` annotation in caller-submitted Malloy text.
|
|
13836
|
+
description: "Requests rejected with 400 for declaring an `#(authorize)` annotation in caller-submitted Malloy text. Labels: field ('query'|'source_name'|'query_name'|'compile_source'), match ('lexed' when Malloy's lexer placed a tag outside prose, 'whole_text' when the text could not be lexed cleanly or is a name, 'no_lexer' when Malloy's parser failed to load so every prose-bearing text refuses)."
|
|
13837
|
+
});
|
|
13838
|
+
guardRejectionCounter.add(1, {
|
|
13839
|
+
field,
|
|
13840
|
+
match: lastCallerGuardRefusalKind()
|
|
13692
13841
|
});
|
|
13693
|
-
guardRejectionCounter.add(1, { field });
|
|
13694
13842
|
}
|
|
13695
13843
|
function recordAuthorizeBypass(entryPoint) {
|
|
13696
13844
|
bypassCounter ??= publisherMeter().createCounter("publisher_authorize_bypass_total", {
|
|
@@ -14817,6 +14965,44 @@ var init_gate_classification = __esm(() => {
|
|
|
14817
14965
|
init_gate_registry_walk();
|
|
14818
14966
|
});
|
|
14819
14967
|
|
|
14968
|
+
// src/service/model_info.ts
|
|
14969
|
+
import { modelDefToModelInfo } from "@malloydata/malloy";
|
|
14970
|
+
function modelInfoOf(modelDef) {
|
|
14971
|
+
try {
|
|
14972
|
+
return modelDefToModelInfo(modelDef);
|
|
14973
|
+
} catch (err) {
|
|
14974
|
+
if (!isUnboundGiven(err) || !modelDef.givens)
|
|
14975
|
+
throw err;
|
|
14976
|
+
try {
|
|
14977
|
+
return modelDefToModelInfo({
|
|
14978
|
+
...modelDef,
|
|
14979
|
+
givens: withPlaceholderDefaults(modelDef.givens)
|
|
14980
|
+
});
|
|
14981
|
+
} catch {
|
|
14982
|
+
throw err;
|
|
14983
|
+
}
|
|
14984
|
+
}
|
|
14985
|
+
}
|
|
14986
|
+
function isUnboundGiven(err) {
|
|
14987
|
+
return err?.code === "compiler-given-no-value";
|
|
14988
|
+
}
|
|
14989
|
+
function withPlaceholderDefaults(givens) {
|
|
14990
|
+
const out = {};
|
|
14991
|
+
for (const [id, given] of Object.entries(givens)) {
|
|
14992
|
+
if (given.default !== undefined) {
|
|
14993
|
+
out[id] = given;
|
|
14994
|
+
continue;
|
|
14995
|
+
}
|
|
14996
|
+
const isFilter = given.type.type === "filter expression";
|
|
14997
|
+
out[id] = {
|
|
14998
|
+
...given,
|
|
14999
|
+
default: isFilter ? { node: "filterLiteral", filterSrc: "" } : { node: "null" }
|
|
15000
|
+
};
|
|
15001
|
+
}
|
|
15002
|
+
return out;
|
|
15003
|
+
}
|
|
15004
|
+
var init_model_info = () => {};
|
|
15005
|
+
|
|
14820
15006
|
// src/service/query_text.ts
|
|
14821
15007
|
import { ParseUtil } from "@malloydata/malloy-tag";
|
|
14822
15008
|
function extractRunTargetSourceName(query) {
|
|
@@ -15748,6 +15934,17 @@ function isNotebookModelPath(modelPath) {
|
|
|
15748
15934
|
const segments = modelPath.split("/");
|
|
15749
15935
|
return segments.length === 2 && segments[0] === NOTEBOOKS_DIR;
|
|
15750
15936
|
}
|
|
15937
|
+
function isDocumentModelPath(modelPath) {
|
|
15938
|
+
if (!modelPath.endsWith(MODEL_FILE_SUFFIX))
|
|
15939
|
+
return false;
|
|
15940
|
+
const segments = modelPath.split("/");
|
|
15941
|
+
return segments.length === 2 && (segments[0] === NOTEBOOKS_DIR || segments[0] === DASHBOARDS_DIR);
|
|
15942
|
+
}
|
|
15943
|
+
function documentKind(modelPath, tagKind) {
|
|
15944
|
+
if (tagKind === "notebook" || tagKind === "dashboard")
|
|
15945
|
+
return tagKind;
|
|
15946
|
+
return isNotebookModelPath(modelPath) ? "notebook" : "dashboard";
|
|
15947
|
+
}
|
|
15751
15948
|
function routeOfNote(text) {
|
|
15752
15949
|
return routeOf3({ value: text.trimStart() });
|
|
15753
15950
|
}
|
|
@@ -15775,34 +15972,71 @@ function dashboardDescriptionNotes(notes) {
|
|
|
15775
15972
|
return notes.filter((n) => line !== undefined && (n.at?.range.start.line ?? 0) > line).map((n) => n.text);
|
|
15776
15973
|
}
|
|
15777
15974
|
function hasArtifactLineOutsideBlocks(source, artifactLine) {
|
|
15975
|
+
return findArtifactLineOutsideBlocks(source, artifactLine) !== undefined;
|
|
15976
|
+
}
|
|
15977
|
+
function blockCloserFinder(lines) {
|
|
15978
|
+
const byKey = new Map;
|
|
15979
|
+
lines.forEach((line, at) => {
|
|
15980
|
+
const indent = /^[ \t]*/.exec(line)?.[0].length ?? 0;
|
|
15981
|
+
for (const closer of ["|##", "|#"])
|
|
15982
|
+
if (closesBlock(line, 0, indent, closer)) {
|
|
15983
|
+
const key = `${indent}${closer}`;
|
|
15984
|
+
const ats = byKey.get(key);
|
|
15985
|
+
if (ats)
|
|
15986
|
+
ats.push(at);
|
|
15987
|
+
else
|
|
15988
|
+
byKey.set(key, [at]);
|
|
15989
|
+
break;
|
|
15990
|
+
}
|
|
15991
|
+
});
|
|
15992
|
+
const cursor = new Map;
|
|
15993
|
+
return (column, closer, after) => {
|
|
15994
|
+
const key = `${column}${closer}`;
|
|
15995
|
+
const ats = byKey.get(key) ?? [];
|
|
15996
|
+
let k = cursor.get(key) ?? 0;
|
|
15997
|
+
while (k < ats.length && ats[k] <= after)
|
|
15998
|
+
k++;
|
|
15999
|
+
cursor.set(key, k);
|
|
16000
|
+
return ats[k] ?? -1;
|
|
16001
|
+
};
|
|
16002
|
+
}
|
|
16003
|
+
function findArtifactLineOutsideBlocks(source, artifactLine) {
|
|
15778
16004
|
const lines = source.split(/\r\n|\r|\n/);
|
|
16005
|
+
const closerAfter = blockCloserFinder(lines);
|
|
15779
16006
|
for (let i = 0;i < lines.length; i++) {
|
|
15780
16007
|
const trimmed = lines[i].trimStart();
|
|
15781
16008
|
const opener = /^(#{1,2})\|/.exec(trimmed);
|
|
15782
16009
|
if (opener) {
|
|
15783
|
-
const wanted = `|${opener[1]}`;
|
|
15784
|
-
if (trimmed.includes(wanted, opener[0].length))
|
|
15785
|
-
continue;
|
|
15786
16010
|
const column = lines[i].length - trimmed.length;
|
|
15787
|
-
|
|
15788
|
-
for (let at = i + 1;at < lines.length; at++)
|
|
15789
|
-
if (closesBlock(lines[at], 0, column, wanted)) {
|
|
15790
|
-
end = at;
|
|
15791
|
-
break;
|
|
15792
|
-
}
|
|
16011
|
+
const end = closerAfter(column, `|${opener[1]}`, i);
|
|
15793
16012
|
if (end !== -1) {
|
|
16013
|
+
const block = [trimmed, ...lines.slice(i + 1, end)].join(`
|
|
16014
|
+
`);
|
|
16015
|
+
if (artifactLine.test(block))
|
|
16016
|
+
return block;
|
|
15794
16017
|
i = end;
|
|
15795
16018
|
continue;
|
|
15796
16019
|
}
|
|
15797
16020
|
}
|
|
15798
16021
|
if (artifactLine.test(trimmed))
|
|
15799
|
-
return
|
|
16022
|
+
return trimmed;
|
|
15800
16023
|
}
|
|
15801
|
-
return
|
|
16024
|
+
return;
|
|
16025
|
+
}
|
|
16026
|
+
function artifactKindOfNotes(notes) {
|
|
16027
|
+
const tag = motlyTag(notes.map((note) => note.text).filter(isArtifactNoteText));
|
|
16028
|
+
return tagText(tag?.tag("artifact"), "kind");
|
|
15802
16029
|
}
|
|
15803
16030
|
function claimsToBeANotebook(source) {
|
|
15804
16031
|
return hasArtifactLineOutsideBlocks(source, ARTIFACT_NOTE);
|
|
15805
16032
|
}
|
|
16033
|
+
function artifactTagText(source) {
|
|
16034
|
+
return findArtifactLineOutsideBlocks(source, ARTIFACT_NOTE);
|
|
16035
|
+
}
|
|
16036
|
+
function artifactKindInText(source) {
|
|
16037
|
+
const line = artifactTagText(source);
|
|
16038
|
+
return line === undefined ? undefined : tagText(motlyTag([line])?.tag("artifact"), "kind");
|
|
16039
|
+
}
|
|
15806
16040
|
function parseNotebookText(text) {
|
|
15807
16041
|
try {
|
|
15808
16042
|
const { problems, parse } = translateToParse(text);
|
|
@@ -16239,9 +16473,12 @@ function readMarkdownBlocks(parse, text) {
|
|
|
16239
16473
|
return blocks;
|
|
16240
16474
|
}
|
|
16241
16475
|
function notebookReaderProblem(modelPath, text, modelDef, url) {
|
|
16242
|
-
if (!
|
|
16476
|
+
if (!isDocumentModelPath(modelPath))
|
|
16477
|
+
return;
|
|
16478
|
+
const notes = ownModelNoteObjects(modelDef);
|
|
16479
|
+
if (artifactNoteLine(notes) === undefined)
|
|
16243
16480
|
return;
|
|
16244
|
-
if (
|
|
16481
|
+
if (documentKind(modelPath, artifactKindOfNotes(notes)) !== "notebook")
|
|
16245
16482
|
return;
|
|
16246
16483
|
const parse = parseNotebookText(text);
|
|
16247
16484
|
const error = isNotebookReaderError(parse) ? parse : readNotebookCells(parse, modelDef, text).error;
|
|
@@ -16261,7 +16498,7 @@ function notebookReaderProblem(modelPath, text, modelDef, url) {
|
|
|
16261
16498
|
}
|
|
16262
16499
|
};
|
|
16263
16500
|
}
|
|
16264
|
-
var NOTEBOOKS_DIR = "notebooks", ARTIFACT_NOTE, MARKDOWN_ROUTE = "markdown", attachedNowhereFix = (block) => block ? "write it as a floating `##|(markdown)` block closed by `|##` for prose that stands on its own, or move it directly above the statement it describes." : "write it as a floating `##(markdown)` line for prose that stands on its own, or move it directly above the statement it describes.", isRuleNode = (node) => node !== undefined && node.ruleIndex !== undefined && (node.childCount ?? 0) > 0, callAccessor = (node, name) => {
|
|
16501
|
+
var NOTEBOOKS_DIR = "notebooks", DASHBOARDS_DIR = "dashboards", ARTIFACT_NOTE, ANY_ARTIFACT_NOTE, MARKDOWN_ROUTE = "markdown", attachedNowhereFix = (block) => block ? "write it as a floating `##|(markdown)` block closed by `|##` for prose that stands on its own, or move it directly above the statement it describes." : "write it as a floating `##(markdown)` line for prose that stands on its own, or move it directly above the statement it describes.", isRuleNode = (node) => node !== undefined && node.ruleIndex !== undefined && (node.childCount ?? 0) > 0, callAccessor = (node, name) => {
|
|
16265
16502
|
const accessor = node[name];
|
|
16266
16503
|
return typeof accessor === "function" ? accessor.call(node) : undefined;
|
|
16267
16504
|
}, STATEMENT_ACCESSORS, NOTEBOOK_PARSE_URL = "file:///publisher-notebook-reader/notebook.malloy", NOTE_TOKENS, TEXT_BLOCK_NAME;
|
|
@@ -16270,7 +16507,62 @@ var init_notebook = __esm(() => {
|
|
|
16270
16507
|
init_annotations();
|
|
16271
16508
|
init_query_text();
|
|
16272
16509
|
init_motly();
|
|
16273
|
-
ARTIFACT_NOTE =
|
|
16510
|
+
ARTIFACT_NOTE = {
|
|
16511
|
+
test(note) {
|
|
16512
|
+
const prefix = /^##\|?(?:[ \t\r\n]|$)/.exec(note);
|
|
16513
|
+
if (!prefix)
|
|
16514
|
+
return false;
|
|
16515
|
+
const bare = /[0-9A-Za-z_\u00C0-\u024F\u1E00-\u1EFF]+/y;
|
|
16516
|
+
let depth = 0;
|
|
16517
|
+
let before = "";
|
|
16518
|
+
for (let i = prefix[0].length;i < note.length; i++) {
|
|
16519
|
+
const c = note[i];
|
|
16520
|
+
if (/\s/.test(c))
|
|
16521
|
+
continue;
|
|
16522
|
+
if (note.startsWith("-...", i)) {
|
|
16523
|
+
i += 3;
|
|
16524
|
+
before = "";
|
|
16525
|
+
continue;
|
|
16526
|
+
}
|
|
16527
|
+
bare.lastIndex = i;
|
|
16528
|
+
const word = bare.exec(note)?.[0];
|
|
16529
|
+
if (c === '"' || c === "'") {
|
|
16530
|
+
const fence = note.startsWith(c.repeat(3), i) ? c.repeat(3) : c;
|
|
16531
|
+
i += fence.length;
|
|
16532
|
+
while (i < note.length && !note.startsWith(fence, i))
|
|
16533
|
+
i += note[i] === "\\" ? 2 : 1;
|
|
16534
|
+
i += fence.length - 1;
|
|
16535
|
+
} else if (c === "`") {
|
|
16536
|
+
let end = i + 1;
|
|
16537
|
+
while (end < note.length && note[end] !== "`" && note[end] !== `
|
|
16538
|
+
`)
|
|
16539
|
+
end += note[end] === "\\" ? 2 : 1;
|
|
16540
|
+
const name = note.slice(i + 1, end);
|
|
16541
|
+
if (depth === 0 && name === "artifact" && !/[=.-]/.test(before))
|
|
16542
|
+
return true;
|
|
16543
|
+
i = end;
|
|
16544
|
+
} else if (c === "#") {
|
|
16545
|
+
const eol = note.indexOf(`
|
|
16546
|
+
`, i);
|
|
16547
|
+
if (eol < 0)
|
|
16548
|
+
return false;
|
|
16549
|
+
i = eol;
|
|
16550
|
+
continue;
|
|
16551
|
+
} else if (c === "{" || c === "[")
|
|
16552
|
+
depth++;
|
|
16553
|
+
else if (c === "}" || c === "]")
|
|
16554
|
+
depth--;
|
|
16555
|
+
else if (word) {
|
|
16556
|
+
if (depth === 0 && word === "artifact" && !/[=.-]/.test(before))
|
|
16557
|
+
return true;
|
|
16558
|
+
i += word.length - 1;
|
|
16559
|
+
}
|
|
16560
|
+
before = c;
|
|
16561
|
+
}
|
|
16562
|
+
return false;
|
|
16563
|
+
}
|
|
16564
|
+
};
|
|
16565
|
+
ANY_ARTIFACT_NOTE = /^#{1,2}\|?(?=[ \t\r\n])(?:[^"'\\]|\\.|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')*?(?<![\w\u00C0-\u024F\u1E00-\u1EFF.$-])artifact(?![\w\u00C0-\u024F\u1E00-\u1EFF])/;
|
|
16274
16566
|
STATEMENT_ACCESSORS = [
|
|
16275
16567
|
["runStatement", "run"],
|
|
16276
16568
|
["docAnnotations", "notes"],
|
|
@@ -23088,9 +23380,12 @@ var init_given = __esm(() => {
|
|
|
23088
23380
|
});
|
|
23089
23381
|
|
|
23090
23382
|
// src/service/dashboard.ts
|
|
23091
|
-
import { isSourceDef as isSourceDef5 } from "@malloydata/malloy";
|
|
23383
|
+
import { isJoined as isJoined3, isSourceDef as isSourceDef5 } from "@malloydata/malloy";
|
|
23384
|
+
function queryTiles(tiles) {
|
|
23385
|
+
return (tiles ?? []).filter((tile) => tile.kind === "query");
|
|
23386
|
+
}
|
|
23092
23387
|
function filterPublisherOwnedRenderLogs(logs, modelPath) {
|
|
23093
|
-
const couldBeATile =
|
|
23388
|
+
const couldBeATile = isDocumentModelPath(modelPath);
|
|
23094
23389
|
return logs.filter((log) => {
|
|
23095
23390
|
const match = UNKNOWN_RENDER_TAG.exec(log.message ?? "");
|
|
23096
23391
|
if (!match)
|
|
@@ -23115,6 +23410,24 @@ function dashboardSlug(modelPath) {
|
|
|
23115
23410
|
function matchesDocumentedDashboardName(slug) {
|
|
23116
23411
|
return /^[a-zA-Z0-9_-]+$/.test(slug);
|
|
23117
23412
|
}
|
|
23413
|
+
function readTileEntries(artifact) {
|
|
23414
|
+
return artifact.array("tiles")?.flatMap((entry) => {
|
|
23415
|
+
const text = tagText(entry);
|
|
23416
|
+
if (text === undefined)
|
|
23417
|
+
return [];
|
|
23418
|
+
if (tagText(entry, "kind") !== "text")
|
|
23419
|
+
return [{ kind: "query", expression: text }];
|
|
23420
|
+
const colspan = positiveInteger(tagNumeric(entry, "colspan"));
|
|
23421
|
+
return [
|
|
23422
|
+
{
|
|
23423
|
+
kind: "text",
|
|
23424
|
+
name: text,
|
|
23425
|
+
...colspan !== undefined && { colspan },
|
|
23426
|
+
...entry.has("break") && { break: true }
|
|
23427
|
+
}
|
|
23428
|
+
];
|
|
23429
|
+
});
|
|
23430
|
+
}
|
|
23118
23431
|
function readArtifactTag(tag, declaredType) {
|
|
23119
23432
|
const artifact = tag.tag("artifact");
|
|
23120
23433
|
if (!artifact)
|
|
@@ -23123,12 +23436,25 @@ function readArtifactTag(tag, declaredType) {
|
|
|
23123
23436
|
const autorun = readAutorun(artifact);
|
|
23124
23437
|
return {
|
|
23125
23438
|
title: tagText(artifact, "title"),
|
|
23126
|
-
|
|
23439
|
+
kind: tagText(artifact, "kind"),
|
|
23440
|
+
tiles: readTileEntries(artifact),
|
|
23127
23441
|
dashboardColumns: tag.tag("dashboard")?.has("columns") ? positiveInteger(tagNumeric(tag.tag("dashboard"), "columns")) : artifact.array("tiles") ? positiveInteger(tagNumeric(artifact, "dashboard_columns")) : undefined,
|
|
23128
23442
|
givens,
|
|
23129
23443
|
autorun
|
|
23130
23444
|
};
|
|
23131
23445
|
}
|
|
23446
|
+
function readTextBlocks(notes) {
|
|
23447
|
+
return notes.flatMap((note) => {
|
|
23448
|
+
const newline = note.indexOf(`
|
|
23449
|
+
`);
|
|
23450
|
+
const opener = parseMarkdownOpener(newline === -1 ? note : note.slice(0, newline));
|
|
23451
|
+
if (opener?.level !== 2 || !opener.name)
|
|
23452
|
+
return [];
|
|
23453
|
+
const body = newline === -1 ? "" : note.slice(newline + 1);
|
|
23454
|
+
return [{ name: opener.name, body: body.replace(/\r\n?/g, `
|
|
23455
|
+
`) }];
|
|
23456
|
+
});
|
|
23457
|
+
}
|
|
23132
23458
|
function positiveInteger(value) {
|
|
23133
23459
|
return value !== undefined && Number.isInteger(value) && value >= 1 ? value : undefined;
|
|
23134
23460
|
}
|
|
@@ -23176,6 +23502,11 @@ function readDashboardModelFacts(modelPath, modelDef, surfacedGivenNames, gatesB
|
|
|
23176
23502
|
for (const field of obj.fields) {
|
|
23177
23503
|
const fieldName = field.as || field.name;
|
|
23178
23504
|
fieldNames.add(fieldName);
|
|
23505
|
+
if (isJoined3(field)) {
|
|
23506
|
+
for (const joined of field.fields) {
|
|
23507
|
+
fieldNames.add(`${fieldName}.${joined.as || joined.name}`);
|
|
23508
|
+
}
|
|
23509
|
+
}
|
|
23179
23510
|
if (field.type === "turtle") {
|
|
23180
23511
|
const refs = new Set(sourceGivens);
|
|
23181
23512
|
collectGivenRefs(field.pipeline, refs);
|
|
@@ -23246,7 +23577,11 @@ function tagTextArray(tag, ...path) {
|
|
|
23246
23577
|
return items?.map((item) => tagText(item)).filter(isString);
|
|
23247
23578
|
}
|
|
23248
23579
|
function resolveTileGivens(tile, facts) {
|
|
23249
|
-
|
|
23580
|
+
const reads = referencedTileGivens(tile, facts);
|
|
23581
|
+
if (reads === undefined)
|
|
23582
|
+
return;
|
|
23583
|
+
const gateReads = facts.compiledTileGivens?.get(normalizeTileExpression(tile))?.gateReads ?? [];
|
|
23584
|
+
return Array.from(new Set([...reads, ...gateReads])).filter((name) => facts.givens.has(name));
|
|
23250
23585
|
}
|
|
23251
23586
|
function resolveTileLayout(tile, facts) {
|
|
23252
23587
|
const annotations = tileAnnotations(tile, facts);
|
|
@@ -23258,7 +23593,15 @@ function tileAnnotations(tile, facts) {
|
|
|
23258
23593
|
}
|
|
23259
23594
|
function referencedTileGivens(tile, facts) {
|
|
23260
23595
|
const normalized = normalizeTileExpression(tile);
|
|
23261
|
-
|
|
23596
|
+
const statically = facts.viewGivens.get(normalized) ?? facts.queries.find((query) => query.name === normalized)?.givens;
|
|
23597
|
+
const compiled = facts.compiledTileGivens?.get(normalized)?.reads;
|
|
23598
|
+
if (!compiled)
|
|
23599
|
+
return statically;
|
|
23600
|
+
const rank = (name) => {
|
|
23601
|
+
const at = statically?.indexOf(name) ?? -1;
|
|
23602
|
+
return at === -1 ? Number.MAX_SAFE_INTEGER : at;
|
|
23603
|
+
};
|
|
23604
|
+
return [...compiled].sort((a, b) => rank(a) - rank(b));
|
|
23262
23605
|
}
|
|
23263
23606
|
function buildGivenSpecs(names, facts) {
|
|
23264
23607
|
const declarations = facts.givens;
|
|
@@ -23300,26 +23643,41 @@ function buildDashboardManifest(facts) {
|
|
|
23300
23643
|
const modelTag = motlyTag(facts.modelAnnotations);
|
|
23301
23644
|
const declaredType = (name2) => facts.givens.get(name2)?.type;
|
|
23302
23645
|
const composite = modelTag ? readArtifactTag(modelTag, declaredType) : undefined;
|
|
23303
|
-
|
|
23304
|
-
|
|
23646
|
+
const kind = documentKind(facts.modelPath, composite?.kind);
|
|
23647
|
+
if (composite?.tiles && (composite.tiles.length > 0 || kind === "notebook")) {
|
|
23648
|
+
const blocks = readTextBlocks(facts.modelAnnotations);
|
|
23649
|
+
const tiles = composite.tiles.map((entry) => {
|
|
23650
|
+
if (entry.kind === "text") {
|
|
23651
|
+
return {
|
|
23652
|
+
kind: "text",
|
|
23653
|
+
name: entry.name,
|
|
23654
|
+
markdown: blocks.find((block) => block.name === entry.name)?.body ?? "",
|
|
23655
|
+
...entry.colspan !== undefined && { colspan: entry.colspan },
|
|
23656
|
+
...entry.break && { break: true }
|
|
23657
|
+
};
|
|
23658
|
+
}
|
|
23659
|
+
const query = entry.expression;
|
|
23305
23660
|
const givenNames = resolveTileGivens(query, facts);
|
|
23306
23661
|
const layout = resolveTileLayout(query, facts);
|
|
23307
23662
|
return {
|
|
23663
|
+
kind: "query",
|
|
23308
23664
|
query,
|
|
23309
23665
|
...givenNames ? { givenNames } : {},
|
|
23310
23666
|
...layout
|
|
23311
23667
|
};
|
|
23312
23668
|
});
|
|
23669
|
+
const queries = queryTiles(tiles);
|
|
23313
23670
|
const doc = docCommentTitleAndDescription(facts.descriptionNotes ?? facts.modelAnnotations, composite.title);
|
|
23314
23671
|
return {
|
|
23315
23672
|
...base,
|
|
23673
|
+
kind,
|
|
23316
23674
|
title: doc.title ?? name,
|
|
23317
23675
|
description: doc.description,
|
|
23318
23676
|
tiles,
|
|
23319
|
-
dashboardColumns: composite.dashboardColumns,
|
|
23677
|
+
dashboardColumns: kind === "notebook" ? 1 : composite.dashboardColumns,
|
|
23320
23678
|
startingGivens: composite.givens,
|
|
23321
23679
|
autorun: composite.autorun,
|
|
23322
|
-
givens: buildGivenSpecs(
|
|
23680
|
+
givens: buildGivenSpecs(queries.some((tile) => tile.givenNames === undefined) ? Array.from(facts.givens.keys()) : queries.flatMap((tile) => (referencedTileGivens(tile.query, facts) ?? []).filter((name2) => facts.givens.has(name2))), facts)
|
|
23323
23681
|
};
|
|
23324
23682
|
}
|
|
23325
23683
|
for (const query of facts.queries) {
|
|
@@ -23330,6 +23688,7 @@ function buildDashboardManifest(facts) {
|
|
|
23330
23688
|
const doc = docCommentTitleAndDescription(query.annotations, artifact.title);
|
|
23331
23689
|
return {
|
|
23332
23690
|
...base,
|
|
23691
|
+
kind: "dashboard",
|
|
23333
23692
|
title: doc.title ?? name,
|
|
23334
23693
|
description: doc.description,
|
|
23335
23694
|
query: query.name,
|
|
@@ -23371,19 +23730,33 @@ function lintDashboard(facts, manifest) {
|
|
|
23371
23730
|
continue;
|
|
23372
23731
|
add(`\`${property}\` in the artifact tag does nothing in Publisher` + (ARTIFACT_PROPERTY_REPLACEMENTS[property] ? `. ${ARTIFACT_PROPERTY_REPLACEMENTS[property]}` : property === "tiles" ? `: a dashboard whose tag sits on a query is that one query's ` + `result. Move the tag to model level (\`## artifact\`) to ` + `combine separate queries into a grid.` : `.`));
|
|
23373
23732
|
}
|
|
23733
|
+
const textBlocks = readTextBlocks(facts.modelAnnotations);
|
|
23374
23734
|
for (const entry of artifactTag?.array("tiles") ?? []) {
|
|
23375
23735
|
const kind = tagText(entry, "kind");
|
|
23376
|
-
|
|
23377
|
-
|
|
23736
|
+
const label = tagText(entry) ?? "a tile";
|
|
23737
|
+
const read = kind === "text" ? ["kind", "colspan", "break"] : [];
|
|
23738
|
+
const carried = Object.keys(entry.dict ?? {}).filter((property) => !read.includes(property) && !(property === "kind" && kind === "query"));
|
|
23739
|
+
if (carried.length > 0) {
|
|
23740
|
+
const named = carried.map((property) => `\`${property}\``).join(", ");
|
|
23741
|
+
add(kind === "text" ? `\`${label}\` in \`tiles=[…]\` carries ${named}, which Publisher does not read: a text tile's entry reads only \`colspan\` and \`break\`.` : `\`${label}\` in \`tiles=[…]\` carries ${named}, ` + `which Publisher does not read: a tile entry is the run expression ` + `alone. Per-tile presentation goes on the view the tile names ` + `(\`# colspan\`, \`# break\`, \`# label\`, \`# subtitle\`, ` + `\`# borderless\`).`);
|
|
23742
|
+
}
|
|
23743
|
+
if (kind !== "text")
|
|
23378
23744
|
continue;
|
|
23745
|
+
if (!textBlocks.some((block) => block.name === label)) {
|
|
23746
|
+
add(`text tile \`${label}\` has no \`##|(markdown) ${label}\` block, so it shows nothing. ` + `Fix: write that block, with the text on the lines below its opener and \`|##\` after.`);
|
|
23747
|
+
}
|
|
23748
|
+
if (entry.has("colspan") && positiveInteger(tagNumeric(entry, "colspan")) === undefined) {
|
|
23749
|
+
add(`colspan on text tile \`${label}\` must be a positive integer. The tile spans one column.`);
|
|
23379
23750
|
}
|
|
23380
|
-
const carried = Object.keys(entry.dict ?? {}).filter((property) => !(property === "kind" && kind === "query"));
|
|
23381
|
-
if (carried.length === 0)
|
|
23382
|
-
continue;
|
|
23383
|
-
const named = carried.map((property) => `\`${property}\``).join(", ");
|
|
23384
|
-
add(`\`${tagText(entry) ?? "a tile"}\` in \`tiles=[…]\` carries ${named}, ` + `which Publisher does not read: a tile entry is the run expression ` + `alone. Per-tile presentation goes on the view the tile names ` + `(\`# colspan\`, \`# break\`, \`# label\`, \`# subtitle\`, ` + `\`# borderless\`).`);
|
|
23385
23751
|
}
|
|
23386
|
-
|
|
23752
|
+
const seenBlocks = new Set;
|
|
23753
|
+
for (const { name } of textBlocks) {
|
|
23754
|
+
if (seenBlocks.has(name)) {
|
|
23755
|
+
add(`the \`##|(markdown) ${name}\` block is written twice, and only the first is shown. Fix: rename or delete the second.`);
|
|
23756
|
+
}
|
|
23757
|
+
seenBlocks.add(name);
|
|
23758
|
+
}
|
|
23759
|
+
for (const { query: tile } of manifest.kind === "notebook" ? [] : queryTiles(manifest.tiles)) {
|
|
23387
23760
|
const tag = motlyTag(tileAnnotations(tile, facts) ?? []);
|
|
23388
23761
|
if (!tag?.has("colspan"))
|
|
23389
23762
|
continue;
|
|
@@ -23402,21 +23775,28 @@ function lintDashboard(facts, manifest) {
|
|
|
23402
23775
|
}
|
|
23403
23776
|
const columnsTag = ownTag?.tag("dashboard");
|
|
23404
23777
|
if (columnsTag?.has("columns")) {
|
|
23405
|
-
|
|
23778
|
+
const width = positiveInteger(tagNumeric(columnsTag, "columns"));
|
|
23779
|
+
if (width === undefined) {
|
|
23406
23780
|
const raw = tagText(columnsTag, "columns");
|
|
23407
23781
|
add(`# dashboard { columns=… } must be a positive integer, got ` + `${raw === undefined ? "a value that could not be read" : JSON.stringify(raw)}. ` + `The grid falls back to the default.`);
|
|
23782
|
+
} else if (width > MAX_BUILDER_COLUMNS) {
|
|
23783
|
+
add(`# dashboard { columns=${width} } is wider than the builder offers (${MAX_BUILDER_COLUMNS}); the reader still renders it.`);
|
|
23408
23784
|
}
|
|
23409
23785
|
}
|
|
23410
|
-
for (const { query: tile } of manifest.tiles
|
|
23786
|
+
for (const { query: tile } of queryTiles(manifest.tiles)) {
|
|
23411
23787
|
const parts = plainTileParts(tile);
|
|
23412
23788
|
if (!parts)
|
|
23413
23789
|
continue;
|
|
23414
23790
|
const resolved = referencedTileGivens(tile, facts) !== undefined || "name" in parts && facts.queries.some((query) => query.name === parts.name);
|
|
23791
|
+
if (!resolved && "name" in parts && textBlocks.some((block) => block.name === parts.name)) {
|
|
23792
|
+
add(`tile "${tile}" names the ##|(markdown) ${tile} block but has no kind=text, so it is read as a query. Fix: write ${tile} { kind=text }`, "error");
|
|
23793
|
+
continue;
|
|
23794
|
+
}
|
|
23415
23795
|
if (!resolved) {
|
|
23416
23796
|
const detail = "source" in parts ? facts.sourceFields.has(parts.source) ? `source "${parts.source}" has no view "${parts.view}"` : `no source "${parts.source}" in this file` : `no query named "${parts.name}" in this file`;
|
|
23417
23797
|
add(`tile "${tile}" does not resolve: ${detail}.`, "error");
|
|
23418
23798
|
}
|
|
23419
|
-
const unbindable = (referencedTileGivens(tile, facts) ?? []).filter((name) => !facts.givens.has(name));
|
|
23799
|
+
const unbindable = Array.from(new Set(referencedTileGivens(tile, facts) ?? [])).filter((name) => !facts.givens.has(name));
|
|
23420
23800
|
for (const name of unbindable) {
|
|
23421
23801
|
add(`tile "${tile}" filters by given "${name}", which this file does ` + `not import, so no control is shown for it and it stays at its ` + `default. Add it to an import in ${facts.modelPath}.`);
|
|
23422
23802
|
}
|
|
@@ -23579,7 +23959,7 @@ function lintSelfDrills(facts) {
|
|
|
23579
23959
|
}
|
|
23580
23960
|
return Array.from(findings.values());
|
|
23581
23961
|
}
|
|
23582
|
-
var
|
|
23962
|
+
var MAX_BUILDER_COLUMNS = 24, COMPONENT_FILE_SUFFIXES, MODEL_FILE_SUFFIX2 = ".malloy", PUBLISHER_OWNED_TAGS, RENDERER_CHILD_TAGS, UNKNOWN_RENDER_TAG, COMPOSITE_ARTIFACT_PROPERTIES, QUERY_ARTIFACT_PROPERTIES, ARTIFACT_PROPERTY_REPLACEMENTS, NAME_THE_TILES = `Name the views to show: tiles=["source -> view", …].`;
|
|
23583
23963
|
var init_dashboard = __esm(() => {
|
|
23584
23964
|
init_annotations();
|
|
23585
23965
|
init_notebook();
|
|
@@ -23800,9 +24180,8 @@ var init_filter = __esm(() => {
|
|
|
23800
24180
|
|
|
23801
24181
|
// src/service/source_extraction.ts
|
|
23802
24182
|
import {
|
|
23803
|
-
isJoined as
|
|
23804
|
-
isSourceDef as isSourceDef6
|
|
23805
|
-
modelDefToModelInfo
|
|
24183
|
+
isJoined as isJoined4,
|
|
24184
|
+
isSourceDef as isSourceDef6
|
|
23806
24185
|
} from "@malloydata/malloy";
|
|
23807
24186
|
function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
|
|
23808
24187
|
const ids = [field.referenceID, field.sourceID].filter((id) => !!id);
|
|
@@ -23833,7 +24212,7 @@ function considerNoteOwner(declaredBy, note, candidate) {
|
|
|
23833
24212
|
function collectSourceInfos(modelDef) {
|
|
23834
24213
|
const names = Object.keys(modelDef.contents).filter((name) => isSourceDef6(modelDef.contents[name]));
|
|
23835
24214
|
const exports = [...new Set([...modelDef.exports, ...names])];
|
|
23836
|
-
return
|
|
24215
|
+
return modelInfoOf({ ...modelDef, exports }).entries.filter((entry) => entry.kind === "source");
|
|
23837
24216
|
}
|
|
23838
24217
|
function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
23839
24218
|
const filterMap = new Map;
|
|
@@ -23971,7 +24350,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
23971
24350
|
continue;
|
|
23972
24351
|
}
|
|
23973
24352
|
const fieldName = field.as || field.name;
|
|
23974
|
-
if (
|
|
24353
|
+
if (isJoined4(field) && isSourceDef6(field)) {
|
|
23975
24354
|
const joinedStruct = field;
|
|
23976
24355
|
if (joinFieldNamesUnresolvableDeclaration(joinedStruct, modelDef)) {
|
|
23977
24356
|
continue;
|
|
@@ -24029,6 +24408,7 @@ var init_source_extraction = __esm(() => {
|
|
|
24029
24408
|
init_annotations();
|
|
24030
24409
|
init_authorize();
|
|
24031
24410
|
init_authorize_routes();
|
|
24411
|
+
init_model_info();
|
|
24032
24412
|
init_filter();
|
|
24033
24413
|
init_gate_registry_walk();
|
|
24034
24414
|
});
|
|
@@ -24065,12 +24445,12 @@ init_error_wire();
|
|
|
24065
24445
|
init_authorize();
|
|
24066
24446
|
init_gate_classification();
|
|
24067
24447
|
init_gate_dimension();
|
|
24448
|
+
init_model_info();
|
|
24068
24449
|
var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
|
|
24069
24450
|
import {
|
|
24070
24451
|
contextOverlay,
|
|
24071
24452
|
isSourceDef as isSourceDef7,
|
|
24072
24453
|
MalloyConfig,
|
|
24073
|
-
modelDefToModelInfo as modelDefToModelInfo2,
|
|
24074
24454
|
Runtime
|
|
24075
24455
|
} from "@malloydata/malloy";
|
|
24076
24456
|
import {
|
|
@@ -24655,7 +25035,7 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
|
|
|
24655
25035
|
modelPath,
|
|
24656
25036
|
modelType: "model",
|
|
24657
25037
|
modelDef,
|
|
24658
|
-
modelInfo:
|
|
25038
|
+
modelInfo: modelInfoOf(modelDef),
|
|
24659
25039
|
sourceInfos,
|
|
24660
25040
|
sources,
|
|
24661
25041
|
queries,
|
|
@@ -24699,7 +25079,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
|
|
|
24699
25079
|
continue;
|
|
24700
25080
|
}
|
|
24701
25081
|
const currentModelDef = (await localMM.getModel())._modelDef;
|
|
24702
|
-
const currentInfo =
|
|
25082
|
+
const currentInfo = modelInfoOf(currentModelDef);
|
|
24703
25083
|
const newSources = collectSourceInfos(currentModelDef).filter((s) => !(s.name in oldSources));
|
|
24704
25084
|
for (const s of newSources)
|
|
24705
25085
|
oldSources[s.name] = s;
|
|
@@ -24781,7 +25161,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
|
|
|
24781
25161
|
modelPath,
|
|
24782
25162
|
modelType: "notebook",
|
|
24783
25163
|
modelDef: finalModelDef,
|
|
24784
|
-
modelInfo: finalModelDef ?
|
|
25164
|
+
modelInfo: finalModelDef ? modelInfoOf(finalModelDef) : undefined,
|
|
24785
25165
|
sourceInfos: finalSourceInfos,
|
|
24786
25166
|
sources: finalSources,
|
|
24787
25167
|
queries: finalQueries,
|