Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion web-common/src/features/canvas/inspector/PageEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { m } from "@rilldata/web-common/lib/i18n/gen/messages";
import Input from "@rilldata/web-common/components/forms/Input.svelte";
import InputLabel from "@rilldata/web-common/components/forms/InputLabel.svelte";
import TagInput from "@rilldata/web-common/components/forms/TagInput.svelte";
import Switch from "@rilldata/web-common/components/forms/Switch.svelte";
import { getParsedDocument } from "@rilldata/web-common/features/canvas/inspector/selectors";
import { getCanvasStore } from "@rilldata/web-common/features/canvas/state-managers/state-managers";
Expand All @@ -13,6 +14,11 @@
} from "@rilldata/web-common/features/entity-management/resource-selectors";
import MultiSelectInput from "@rilldata/web-common/features/visual-editing/MultiSelectInput.svelte";
import SidebarWrapper from "@rilldata/web-common/features/visual-editing/SidebarWrapper.svelte";
import {
getResourceTagSuggestions,
normalizeTags,
readRootYamlTags,
} from "@rilldata/web-common/features/visual-editing/tag-utils";
import ThemeInput from "@rilldata/web-common/features/visual-editing/ThemeInput.svelte";
import {
DEFAULT_RANGES,
Expand All @@ -26,7 +32,10 @@
} from "@rilldata/web-common/lib/time/config";
import { allTimeZones } from "@rilldata/web-common/lib/time/timezone";
import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2";
import { createRuntimeServiceGetInstance } from "@rilldata/web-common/runtime-client";
import {
createRuntimeServiceGetInstance,
createRuntimeServiceListResources,
} from "@rilldata/web-common/runtime-client";
import { YAMLMap, YAMLSeq } from "yaml";
import { DEFAULT_DASHBOARD_WIDTH } from "../layout-util";

Expand Down Expand Up @@ -57,6 +66,22 @@
$: rawTimeZones = $parsedDocument.get("time_zones");
$: rawMaxWidth = $parsedDocument.get("max_width");

$: resourcesQuery = createRuntimeServiceListResources(
client,
{},
{
query: {
select: (data) => data.resources ?? [],
},
},
);

$: resourceTags = readRootYamlTags($parsedDocument);
$: tagSuggestions = getResourceTagSuggestions(
$resourcesQuery?.data,
resourceTags,
);

$: timeZones = new Set(
rawTimeZones instanceof YAMLSeq
? rawTimeZones.toJSON().filter(isString)
Expand Down Expand Up @@ -128,6 +153,16 @@
}
}

async function updateResourceTags(tags: string[]) {
const normalizedTags = normalizeTags(tags);

if (normalizedTags.length) {
await updateProperties({ tags: normalizedTags });
} else {
await updateProperties({}, ["tags"]);
}
}

let currentTab: string = "options";
</script>

Expand All @@ -154,6 +189,13 @@
}}
/>
</div>
<div class="page-param">
<TagInput
tags={resourceTags}
suggestions={tagSuggestions}
onChange={updateResourceTags}
/>
</div>
<div class="page-param">
<Input
capitalizeLabel={false}
Expand Down
44 changes: 44 additions & 0 deletions web-common/src/features/visual-editing/tag-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, expect, it } from "vitest";
import { parseDocument } from "yaml";
import {
getResourceTagSuggestions,
readRootYamlTags,
setRootYamlTags,
} from "./tag-utils";

describe("visual editing tag utils", () => {
it("reads top-level YAML tags from scalar nodes", () => {
const document = parseDocument(`
type: metrics_view
tags:
- finance
- "growth"
- ""
- finance
`);

expect(readRootYamlTags(document)).toEqual(["finance", "growth"]);
});

it("writes and removes top-level YAML tags", () => {
const document = parseDocument("type: explore\n");

setRootYamlTags(document, [" analytics ", "growth", "analytics"]);
expect(readRootYamlTags(document)).toEqual(["analytics", "growth"]);

setRootYamlTags(document, []);
expect(document.has("tags")).toBe(false);
});

it("builds suggestions from resource metadata and extra tags", () => {
expect(
getResourceTagSuggestions(
[
{ meta: { tags: ["finance", "growth"] } },
{ meta: { tags: ["growth", "sales"] } },
] as never,
["executive"],
),
).toEqual(["executive", "finance", "growth", "sales"]);
});
});
79 changes: 79 additions & 0 deletions web-common/src/features/visual-editing/tag-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { V1Resource } from "@rilldata/web-common/runtime-client";
import { Scalar, YAMLSeq } from "yaml";

type YamlDocumentWithTags = {
get(key: string): unknown;
set(key: string, value: unknown): void;
delete(key: string): unknown;
createNode(value: unknown): unknown;
};

function readYamlString(node: unknown): string {
if (typeof node === "string") return node.trim();

if (node instanceof Scalar) {
return typeof node.value === "string" ? node.value.trim() : "";
}

if (node && typeof node === "object" && "value" in node) {
const value = (node as { value: unknown }).value;
return typeof value === "string" ? value.trim() : "";
}

return "";
}

export function normalizeTags(tags: string[] | undefined): string[] {
const seen = new Set<string>();
const normalized: string[] = [];

for (const tag of tags ?? []) {
const value = tag.trim();
if (!value || seen.has(value)) continue;
seen.add(value);
normalized.push(value);
}

return normalized;
}

export function readYamlTags(node: unknown): string[] {
if (!(node instanceof YAMLSeq)) return [];

return normalizeTags(node.items.map(readYamlString));
}

export function readRootYamlTags(document: { get(key: string): unknown }) {
return readYamlTags(document.get("tags"));
}

export function setRootYamlTags(
document: YamlDocumentWithTags,
tags: string[],
) {
const normalizedTags = normalizeTags(tags);

if (normalizedTags.length) {
document.set("tags", document.createNode(normalizedTags));
} else {
document.delete("tags");
}
}

export function getResourceTagSuggestions(
resources: V1Resource[] | undefined,
...extraTags: Array<string[] | undefined>
) {
return buildTagSuggestions(
resources?.flatMap((resource) => resource.meta?.tags ?? []),
...extraTags,
);
}

export function buildTagSuggestions(
...sources: Array<string[] | undefined>
): string[] {
return normalizeTags(sources.flatMap((source) => source ?? [])).sort((a, b) =>
a.localeCompare(b),
);
}
35 changes: 35 additions & 0 deletions web-common/src/features/workspaces/VisualExploreEditing.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { replaceState } from "$app/navigation";
import Button from "@rilldata/web-common/components/button/Button.svelte";
import TagInput from "@rilldata/web-common/components/forms/TagInput.svelte";
import Input from "@rilldata/web-common/components/forms/Input.svelte";
import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte";
import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte";
Expand All @@ -21,6 +22,7 @@
} from "@rilldata/web-common/lib/time/types";
import {
createRuntimeServiceGetInstance,
createRuntimeServiceListResources,
type V1Explore,
} from "@rilldata/web-common/runtime-client";
import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2";
Expand All @@ -38,6 +40,11 @@
import MeasureDimensionSelector from "../visual-editing/MeasureDimensionSelector.svelte";
import MultiSelectInput from "../visual-editing/MultiSelectInput.svelte";
import SidebarWrapper from "../visual-editing/SidebarWrapper.svelte";
import {
getResourceTagSuggestions,
readRootYamlTags,
setRootYamlTags,
} from "../visual-editing/tag-utils";
import ThemeInput from "../visual-editing/ThemeInput.svelte";

const itemTypes = ["measures", "dimensions"] as const;
Expand Down Expand Up @@ -72,6 +79,16 @@

$: parsedDocument = parseDocument($editorContent ?? "");

$: resourcesQuery = createRuntimeServiceListResources(
runtimeClient,
{},
{
query: {
select: (data) => data.resources ?? [],
},
},
);

$: metricsViewsQuery = useFilteredResources(
runtimeClient,
ResourceKind.MetricsView,
Expand Down Expand Up @@ -102,6 +119,12 @@
$: rawTimeRanges = parsedDocument.get("time_ranges");
$: rawDefaults = parsedDocument.get("defaults");

$: resourceTags = readRootYamlTags(parsedDocument);
$: tagSuggestions = getResourceTagSuggestions(
$resourcesQuery?.data,
resourceTags,
);

$: timeZones = new Set(
rawTimeZones instanceof YAMLSeq
? rawTimeZones.toJSON().filter(isString)
Expand Down Expand Up @@ -262,6 +285,12 @@
updateEditorContent(parsedDocument.toString(), false, autoSave);
}

function updateResourceTags(tags: string[]) {
setRootYamlTags(parsedDocument, tags);
killState();
updateEditorContent(parsedDocument.toString(), false, autoSave);
}

function killState() {
localStorage.removeItem(`${exploreName}-persistentDashboardStore`);

Expand Down Expand Up @@ -375,6 +404,12 @@
}}
/>

<TagInput
tags={resourceTags}
suggestions={tagSuggestions}
onChange={updateResourceTags}
/>

<Input
hint="View documentation"
link="https://docs.rilldata.com/reference/project-files/metrics-views"
Expand Down
Loading