@datashow/super-visual-cli 0.0.0-stage → 0.1.0-rc.0
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/bin/supervisual.mjs +9 -0
- package/package.json +45 -4
- package/src/fabric-client.mjs +101 -0
- package/src/main.mjs +663 -0
- package/src/project-target.mjs +80 -0
- package/src/project.mjs +289 -0
- package/src/semantic-preview.mjs +528 -0
- package/templates/blank/README.md +26 -0
- package/templates/blank/contracts/page-set.yaml +78 -0
- package/templates/blank/contracts/report-source.json +30 -0
- package/templates/blank/contracts/semantic-model.yaml +39 -0
- package/templates/blank/contracts/widget-broker.json +33 -0
- package/templates/blank/deployment/CERTIFICATION.md +18 -0
- package/templates/blank/package.json +15 -0
- package/templates/blank/source/report-design.less +20 -0
- package/templates/blank/source/reportApplication.tsx +29 -0
- package/templates/blank/supervisual.yaml +24 -0
- package/templates/hello-world/README.md +29 -0
- package/templates/hello-world/contracts/page-set.yaml +74 -0
- package/templates/hello-world/contracts/report-source.json +45 -0
- package/templates/hello-world/contracts/semantic-model.yaml +32 -0
- package/templates/hello-world/contracts/widget-broker.json +37 -0
- package/templates/hello-world/deployment/CERTIFICATION.md +17 -0
- package/templates/hello-world/package.json +15 -0
- package/templates/hello-world/source/report-design.less +35 -0
- package/templates/hello-world/source/reportApplication.tsx +69 -0
- package/templates/hello-world/supervisual.yaml +24 -0
- package/templates/semantic-preview/index.html +14 -0
- package/templates/semantic-preview/src/main.tsx +172 -0
- package/README.md +0 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
contractVersion: 1
|
|
2
|
+
name: {{PROJECT_TITLE}}
|
|
3
|
+
culture: en-AU
|
|
4
|
+
compatibilityLevel: 1702
|
|
5
|
+
storageMode: import
|
|
6
|
+
sourceParameters:
|
|
7
|
+
server: localhost
|
|
8
|
+
database: SuperVisualDemo
|
|
9
|
+
tables:
|
|
10
|
+
- name: Sales
|
|
11
|
+
role: fact
|
|
12
|
+
grain: One row per example category.
|
|
13
|
+
description: Minimal self-contained sales data used by the Super Visual hello-world project.
|
|
14
|
+
source:
|
|
15
|
+
referencedTables: []
|
|
16
|
+
sql: |-
|
|
17
|
+
SELECT example.[Category], CAST(example.[Amount] AS decimal(19, 4)) AS [Amount]
|
|
18
|
+
FROM (VALUES (N'Bikes', 42000), (N'Clothing', 18000), (N'Accessories', 27000)) AS example([Category], [Amount])
|
|
19
|
+
columns:
|
|
20
|
+
- name: Category
|
|
21
|
+
sourceColumn: Category
|
|
22
|
+
dataType: String
|
|
23
|
+
hidden: false
|
|
24
|
+
summarizeBy: none
|
|
25
|
+
description: Example product category.
|
|
26
|
+
- name: Amount
|
|
27
|
+
sourceColumn: Amount
|
|
28
|
+
dataType: Decimal
|
|
29
|
+
hidden: true
|
|
30
|
+
summarizeBy: none
|
|
31
|
+
description: Example sales amount.
|
|
32
|
+
measures:
|
|
33
|
+
- name: Total Sales
|
|
34
|
+
expression: "SUM('Sales'[Amount])"
|
|
35
|
+
formatString: "$#,##0"
|
|
36
|
+
displayFolder: Measures
|
|
37
|
+
description: Total example sales in the current category context.
|
|
38
|
+
hierarchies: []
|
|
39
|
+
relationships: []
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contractVersion": 1,
|
|
3
|
+
"summary": "One governed categorical dataset for the hello-world chart.",
|
|
4
|
+
"adapterMode": "source-direct-import",
|
|
5
|
+
"baseModelMutation": false,
|
|
6
|
+
"dataView": {
|
|
7
|
+
"mapping": "table",
|
|
8
|
+
"discriminator": "WidgetId",
|
|
9
|
+
"fields": ["WidgetId", "AxisKey", "Label", "Sort", "SeriesKey", "SeriesLabel", "WidgetValue", "BrokerContext"]
|
|
10
|
+
},
|
|
11
|
+
"widgets": [
|
|
12
|
+
{
|
|
13
|
+
"id": "sales",
|
|
14
|
+
"title": "Sales by category",
|
|
15
|
+
"reportWidgetIds": ["sales"],
|
|
16
|
+
"businessQuestionIds": ["sales-mix"],
|
|
17
|
+
"shape": "categorical",
|
|
18
|
+
"dimensions": [{ "key": "Sales[Category]", "label": "Sales[Category]" }],
|
|
19
|
+
"axis": "Sales[Category]",
|
|
20
|
+
"measures": ["[Total Sales]"],
|
|
21
|
+
"rowLimit": 10,
|
|
22
|
+
"rowSelection": "top-by-value",
|
|
23
|
+
"selectable": false,
|
|
24
|
+
"invalidates": []
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"cachePolicy": {
|
|
28
|
+
"retainSourceWidget": true,
|
|
29
|
+
"cancelStaleRequests": true,
|
|
30
|
+
"externalFilterInvalidation": "all-dependent-widgets"
|
|
31
|
+
},
|
|
32
|
+
"risks": []
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Live certification
|
|
2
|
+
|
|
3
|
+
First update the semantic and broker contracts for the selected Power BI
|
|
4
|
+
semantic model. Initialize this project with that model's workspace and
|
|
5
|
+
`--semantic-model-id`, then run the current live workflow:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx supervisual preview test --json
|
|
9
|
+
npx supervisual publish --json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Review the returned workspace, operations, and plan ID. Do not apply until a
|
|
13
|
+
human authorizes that exact plan ID and target workspace. Apply with
|
|
14
|
+
`npx supervisual publish --confirm <authorized-plan-id> --json`.
|
|
15
|
+
|
|
16
|
+
Only an `accepted` result and a successful browser check constitute a passing
|
|
17
|
+
live certification. The selected semantic-model ID is the only model ID users
|
|
18
|
+
provide.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_ID}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"check": "supervisual check .",
|
|
8
|
+
"compile": "supervisual compile .",
|
|
9
|
+
"package": "supervisual package .",
|
|
10
|
+
"verify": "supervisual verify ."
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@datashow/super-visual-cli": "0.1.0-rc.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.hello-world {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
min-height: 100%;
|
|
4
|
+
padding: 32px;
|
|
5
|
+
color: #173c36;
|
|
6
|
+
background: #f4f7f5;
|
|
7
|
+
font-family: Aptos, "Segoe UI", sans-serif;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.hello-world .eyebrow { margin: 0 0 8px; color: #167d6c; font-size: 12px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
|
|
11
|
+
.hello-world h1 { margin: 0; font: 600 clamp(36px, 6vw, 64px)/1 Georgia, serif; }
|
|
12
|
+
.hello-world .lede { max-width: 620px; color: #5e6d68; font-size: 17px; }
|
|
13
|
+
.hello-world section { margin-top: 28px; padding: 24px; border: 1px solid #d3ddd8; border-radius: 8px; background: #fff; }
|
|
14
|
+
.hello-world h2 { margin: 0 0 16px; font-size: 18px; }
|
|
15
|
+
.hello-world .total { font: 600 42px/1 Georgia, serif; }
|
|
16
|
+
.hello-world .bars { display: grid; gap: 14px; margin: 0; padding: 0; list-style: none; }
|
|
17
|
+
.hello-world .bars li { display: grid; grid-template-columns: minmax(90px, 1fr) 3fr minmax(70px, auto); gap: 12px; align-items: center; }
|
|
18
|
+
.hello-world .track { height: 14px; overflow: hidden; border-radius: 7px; background: #eaf0ed; }
|
|
19
|
+
.hello-world .bar { display: block; height: 100%; border-radius: inherit; background: #167d6c; }
|
|
20
|
+
@media (max-width: 640px) { .hello-world { padding: 18px; } .hello-world .bars li { grid-template-columns: 1fr auto; } .hello-world .track { grid-column: 1 / -1; } }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect, type ReactElement } from "react";
|
|
2
|
+
import type { ReportApplicationProps } from "./reportRuntime";
|
|
3
|
+
|
|
4
|
+
export function ReportApplication({ model, onVisibleDatasetsChange }: ReportApplicationProps): ReactElement {
|
|
5
|
+
useEffect(() => onVisibleDatasetsChange?.(["sales"]), [onVisibleDatasetsChange]);
|
|
6
|
+
const dataset = model.datasets.sales;
|
|
7
|
+
const maximum = Math.max(1, ...(dataset?.points.map((point) => point.value) ?? [1]));
|
|
8
|
+
const total = dataset?.points.reduce((sum, point) => sum + point.value, 0) ?? 0;
|
|
9
|
+
|
|
10
|
+
return <main className="hello-world">
|
|
11
|
+
<p className="eyebrow">Super Visual / minimal example</p>
|
|
12
|
+
<h1>Hello, governed data.</h1>
|
|
13
|
+
<p className="lede">This React page is bound only through ReportApplicationProps.</p>
|
|
14
|
+
<section aria-labelledby="total-sales">
|
|
15
|
+
<h2 id="total-sales">Total sales</h2>
|
|
16
|
+
<strong className="total">{new Intl.NumberFormat(undefined, { style: "currency", currency: "AUD", maximumFractionDigits: 0 }).format(total)}</strong>
|
|
17
|
+
</section>
|
|
18
|
+
<section aria-labelledby="sales-by-category">
|
|
19
|
+
<h2 id="sales-by-category">Sales by category</h2>
|
|
20
|
+
{!dataset?.points.length ? <p>Waiting for Power BI data…</p> : <ol className="bars">
|
|
21
|
+
{dataset.points.map((point) => <li key={point.key}>
|
|
22
|
+
<span>{point.label}</span>
|
|
23
|
+
<span className="track"><span className="bar" style={{ width: `${Math.max(2, (point.value / maximum) * 100)}%` }} /></span>
|
|
24
|
+
<strong>{new Intl.NumberFormat().format(point.value)}</strong>
|
|
25
|
+
</li>)}
|
|
26
|
+
</ol>}
|
|
27
|
+
</section>
|
|
28
|
+
</main>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
contractVersion: 1
|
|
2
|
+
application:
|
|
3
|
+
id: {{PROJECT_ID}}
|
|
4
|
+
title: {{PROJECT_TITLE}}
|
|
5
|
+
entry: contracts/report-source.json
|
|
6
|
+
style: contracts/report-source.json
|
|
7
|
+
target:
|
|
8
|
+
platform: powerbi
|
|
9
|
+
visual:
|
|
10
|
+
name: {{VISUAL_NAME}}
|
|
11
|
+
displayName: {{PROJECT_TITLE}}
|
|
12
|
+
guid: {{VISUAL_GUID}}
|
|
13
|
+
version: 1.0.0.1
|
|
14
|
+
apiVersion: 5.11.1
|
|
15
|
+
semantic:
|
|
16
|
+
model: contracts/semantic-model.yaml
|
|
17
|
+
broker: contracts/widget-broker.json
|
|
18
|
+
pageSet: contracts/page-set.yaml
|
|
19
|
+
source:
|
|
20
|
+
bundle: contracts/report-source.json
|
|
21
|
+
output:
|
|
22
|
+
directory: dist
|
|
23
|
+
compliance:
|
|
24
|
+
policies: [core, powerbi]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# {{PROJECT_TITLE}}
|
|
2
|
+
|
|
3
|
+
This is the smallest complete Super Visual connectivity proof. Its React page
|
|
4
|
+
shows a searchable catalog of the entities declared by the selected Power BI
|
|
5
|
+
semantic model. Visible rows prove the governed data path without exposing
|
|
6
|
+
expressions, parameter values, or business rows.
|
|
7
|
+
|
|
8
|
+
This project is ready for the three-command workflow. If `init` was given a
|
|
9
|
+
workspace and semantic model, run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx supervisual dev --open
|
|
13
|
+
npx supervisual publish
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`publish` performs the checks, packaging, verification, and planning, then asks
|
|
17
|
+
for confirmation of the exact plan ID. For a project without a saved binding,
|
|
18
|
+
pass the IDs once:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx supervisual dev . --workspace-id <workspace-guid> \
|
|
22
|
+
--semantic-model-id <semantic-model-guid> --open
|
|
23
|
+
npx supervisual preview test . --workspace-id <workspace-guid> \
|
|
24
|
+
--semantic-model-id <semantic-model-guid> --json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Point the project at another semantic-model ID and the catalog updates
|
|
28
|
+
automatically. Application source receives data only through
|
|
29
|
+
`ReportApplicationProps`.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
contractVersion: 1
|
|
2
|
+
shell:
|
|
3
|
+
id: {{PROJECT_ID}}
|
|
4
|
+
title: {{PROJECT_TITLE}}
|
|
5
|
+
defaultPage: overview
|
|
6
|
+
designSystem:
|
|
7
|
+
id: hello-world
|
|
8
|
+
sha256: {{DESIGN_HASH}}
|
|
9
|
+
acceptedConcept: { id: {{PROJECT_ID}} }
|
|
10
|
+
governanceLabel: Power BI governed
|
|
11
|
+
locale: en-AU
|
|
12
|
+
currencyCode: ''
|
|
13
|
+
filterContext: []
|
|
14
|
+
navigation:
|
|
15
|
+
- { page: overview, label: Model catalog }
|
|
16
|
+
theme:
|
|
17
|
+
background: '#F4F7F5'
|
|
18
|
+
surface: '#FFFFFF'
|
|
19
|
+
quietSurface: '#EAF0ED'
|
|
20
|
+
text: '#173C36'
|
|
21
|
+
mutedText: '#5E6D68'
|
|
22
|
+
metadata: '#71807A'
|
|
23
|
+
rule: '#D3DDD8'
|
|
24
|
+
grid: '#E3EAE6'
|
|
25
|
+
accent: '#167D6C'
|
|
26
|
+
attention: '#9B6B23'
|
|
27
|
+
positive: '#2E7D5B'
|
|
28
|
+
negative: '#B84A52'
|
|
29
|
+
context: ['#167D6C', '#6F6A9A']
|
|
30
|
+
dataColors: ['#167D6C', '#6F6A9A', '#9B6B23']
|
|
31
|
+
displayFont: Georgia, serif
|
|
32
|
+
bodyFont: Aptos, Segoe UI, sans-serif
|
|
33
|
+
contentWidth: 1200
|
|
34
|
+
layoutColumns: 12
|
|
35
|
+
layoutGap: 16
|
|
36
|
+
cardRadius: 8
|
|
37
|
+
cardPadding: [20, 20, 20, 20]
|
|
38
|
+
kpiTopAccent: 3
|
|
39
|
+
density: comfortable
|
|
40
|
+
typography: { findingMin: 40, findingMax: 56, sectionTitle: 22, kpiValue: 32, body: 15, chart: 12, table: 13, metadata: 11 }
|
|
41
|
+
pages:
|
|
42
|
+
- id: overview
|
|
43
|
+
title: Semantic Model Catalog
|
|
44
|
+
eyebrow: Super Visual / connectivity proof
|
|
45
|
+
summary:
|
|
46
|
+
headline: Hello, semantic model.
|
|
47
|
+
body: Search the declared model entities returned through the governed Super Visual broker.
|
|
48
|
+
emphasisBinding: null
|
|
49
|
+
search:
|
|
50
|
+
placeholder: Search type, parent, or name
|
|
51
|
+
dataset: model-catalog
|
|
52
|
+
filterTarget: Super Visual Model Catalog[Entity]
|
|
53
|
+
kpis: []
|
|
54
|
+
widgets:
|
|
55
|
+
- id: model-catalog
|
|
56
|
+
conceptRegionId: model-catalog
|
|
57
|
+
primitive: ranked-table
|
|
58
|
+
title: Semantic model entities
|
|
59
|
+
subtitle: Read from the selected live Power BI semantic model
|
|
60
|
+
dataset: model-catalog
|
|
61
|
+
reportWidgetIds: [model-catalog]
|
|
62
|
+
businessQuestionIds: [semantic-connectivity]
|
|
63
|
+
layout: { columns: 12, minHeight: 420 }
|
|
64
|
+
settings: { orientation: null, curve: null, showLegend: false, showLabels: true, sort: ascending, limit: 100, emphasis: null }
|
|
65
|
+
interactions: []
|
|
66
|
+
designRationale:
|
|
67
|
+
narrative: Make the smallest sample an inspectable proof that React is receiving governed semantic-model rows.
|
|
68
|
+
decisions:
|
|
69
|
+
- Replace arbitrary business data with a generated semantic-model catalog.
|
|
70
|
+
- Keep search client-side so it is immediate and does not create model filters.
|
|
71
|
+
accessibility:
|
|
72
|
+
- Use a labelled search field, semantic table headings, visible connection status, and text counts.
|
|
73
|
+
risks:
|
|
74
|
+
- Catalog rows prove the report-to-semantic-provider path; source refresh remains a separate deployment acceptance check.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contractVersion": 1,
|
|
3
|
+
"designSystem": {
|
|
4
|
+
"id": "hello-world",
|
|
5
|
+
"sha256": "{{DESIGN_HASH}}"
|
|
6
|
+
},
|
|
7
|
+
"acceptedConcept": {
|
|
8
|
+
"id": "{{PROJECT_ID}}"
|
|
9
|
+
},
|
|
10
|
+
"entry": "src/reportApplication.tsx",
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "src/reportApplication.tsx",
|
|
14
|
+
"content": "Template source is synchronized from source/reportApplication.tsx during initialization."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"path": "style/report-design.less",
|
|
18
|
+
"content": "Template style is synchronized from source/report-design.less during initialization."
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"routes": [
|
|
22
|
+
{
|
|
23
|
+
"id": "overview",
|
|
24
|
+
"title": "Semantic Model Catalog",
|
|
25
|
+
"datasets": ["model-catalog"],
|
|
26
|
+
"businessQuestionIds": ["semantic-connectivity"]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"bindings": [
|
|
30
|
+
{
|
|
31
|
+
"dataset": "model-catalog",
|
|
32
|
+
"component": "searchable-table",
|
|
33
|
+
"reportWidgetIds": ["model-catalog"],
|
|
34
|
+
"businessQuestionIds": ["semantic-connectivity"]
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"designRationale": {
|
|
38
|
+
"direction": "A searchable semantic-model catalog that proves the governed provider path.",
|
|
39
|
+
"pageCompositions": ["One semantic-model catalog page"],
|
|
40
|
+
"dataVisualisation": ["Searchable semantic-entity table and entity-type counts"],
|
|
41
|
+
"responsiveBehaviour": ["Summary cards wrap and the semantic table scrolls horizontally on narrow screens."],
|
|
42
|
+
"accessibility": ["Labelled search input, semantic table headings, visible numeric counts, and a live connection status."],
|
|
43
|
+
"risks": ["Catalog rows prove the report-to-semantic-provider path; source refresh remains a separate deployment acceptance check."]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
contractVersion: 1
|
|
2
|
+
name: {{PROJECT_TITLE}}
|
|
3
|
+
culture: en-AU
|
|
4
|
+
compatibilityLevel: 1702
|
|
5
|
+
storageMode: import
|
|
6
|
+
sourceParameters:
|
|
7
|
+
server: localhost
|
|
8
|
+
database: SuperVisualDemo
|
|
9
|
+
tables:
|
|
10
|
+
- name: Hello World
|
|
11
|
+
role: dimension
|
|
12
|
+
grain: One row containing the connectivity greeting.
|
|
13
|
+
description: Minimal source-backed table used to prove model refresh and catalog generation.
|
|
14
|
+
source:
|
|
15
|
+
referencedTables: []
|
|
16
|
+
sql: |-
|
|
17
|
+
SELECT N'Hello, semantic model.' AS [Message]
|
|
18
|
+
columns:
|
|
19
|
+
- name: Message
|
|
20
|
+
sourceColumn: Message
|
|
21
|
+
dataType: String
|
|
22
|
+
hidden: false
|
|
23
|
+
summarizeBy: none
|
|
24
|
+
description: Greeting returned by the source-backed connectivity table.
|
|
25
|
+
measures:
|
|
26
|
+
- name: Row Count
|
|
27
|
+
expression: "COUNTROWS('Hello World')"
|
|
28
|
+
formatString: "0"
|
|
29
|
+
displayFolder: Connectivity
|
|
30
|
+
description: Count of rows returned by the connectivity table.
|
|
31
|
+
hierarchies: []
|
|
32
|
+
relationships: []
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contractVersion": 1,
|
|
3
|
+
"summary": "Generated searchable semantic-model catalog used as an end-to-end connectivity proof.",
|
|
4
|
+
"adapterMode": "source-direct-import",
|
|
5
|
+
"baseModelMutation": false,
|
|
6
|
+
"dataView": {
|
|
7
|
+
"mapping": "table",
|
|
8
|
+
"discriminator": "WidgetId",
|
|
9
|
+
"fields": ["WidgetId", "AxisKey", "Label", "Sort", "SeriesKey", "SeriesLabel", "WidgetValue", "BrokerContext"]
|
|
10
|
+
},
|
|
11
|
+
"widgets": [
|
|
12
|
+
{
|
|
13
|
+
"id": "model-catalog",
|
|
14
|
+
"title": "Semantic model entities",
|
|
15
|
+
"reportWidgetIds": ["model-catalog"],
|
|
16
|
+
"businessQuestionIds": ["semantic-connectivity"],
|
|
17
|
+
"shape": "hierarchy",
|
|
18
|
+
"dimensions": [
|
|
19
|
+
{ "key": "Super Visual Model Catalog[Entity Type]", "label": "Super Visual Model Catalog[Entity Type]" },
|
|
20
|
+
{ "key": "Super Visual Model Catalog[Parent]", "label": "Super Visual Model Catalog[Parent]" },
|
|
21
|
+
{ "key": "Super Visual Model Catalog[Entity]", "label": "Super Visual Model Catalog[Entity]" }
|
|
22
|
+
],
|
|
23
|
+
"axis": "Super Visual Model Catalog[Entity]",
|
|
24
|
+
"measures": ["[Entity Count]"],
|
|
25
|
+
"rowLimit": 250,
|
|
26
|
+
"rowSelection": "first-by-axis",
|
|
27
|
+
"selectable": false,
|
|
28
|
+
"invalidates": []
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"cachePolicy": {
|
|
32
|
+
"retainSourceWidget": true,
|
|
33
|
+
"cancelStaleRequests": true,
|
|
34
|
+
"externalFilterInvalidation": "all-dependent-widgets"
|
|
35
|
+
},
|
|
36
|
+
"risks": ["The catalog lists declared model metadata but does not expose expressions, parameter values, or business rows."]
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Hello World live certification
|
|
2
|
+
|
|
3
|
+
Initialize this project with a workspace and Power BI semantic-model ID using
|
|
4
|
+
`--semantic-model-id`, then run the current live workflow:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx supervisual preview test --json
|
|
8
|
+
npx supervisual publish --json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Review the returned workspace, operations, and plan ID. Do not apply until a
|
|
12
|
+
human authorizes that exact plan ID and target workspace. Apply with
|
|
13
|
+
`npx supervisual publish --confirm <authorized-plan-id> --json`.
|
|
14
|
+
|
|
15
|
+
Only an `accepted` result and a successful browser check constitute a passing
|
|
16
|
+
live certification. The selected semantic-model ID is the only model ID users
|
|
17
|
+
provide.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_ID}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"check": "supervisual check .",
|
|
8
|
+
"compile": "supervisual compile .",
|
|
9
|
+
"package": "supervisual package .",
|
|
10
|
+
"verify": "supervisual verify ."
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@datashow/super-visual-cli": "0.1.0-rc.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.hello-world { box-sizing: border-box; min-height: 100%; padding: 32px; color: #173c36; background: #f4f7f5; font-family: Aptos, "Segoe UI", sans-serif; }
|
|
2
|
+
.hello-world * { box-sizing: border-box; }
|
|
3
|
+
.hero { display: flex; gap: 32px; align-items: flex-start; justify-content: space-between; max-width: 1200px; margin: 0 auto; }
|
|
4
|
+
.eyebrow { margin: 0 0 8px; color: #167d6c; font-size: 12px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
|
|
5
|
+
.hero h1 { margin: 0; font: 600 clamp(36px, 6vw, 64px)/1 Georgia, serif; }
|
|
6
|
+
.lede { max-width: 720px; margin: 16px 0 0; color: #5e6d68; font-size: 17px; line-height: 1.5; }
|
|
7
|
+
.connection { display: inline-flex; flex: 0 0 auto; gap: 8px; align-items: center; margin: 4px 0 0; padding: 9px 13px; border: 1px solid #d3ddd8; border-radius: 999px; background: #fff; color: #5e6d68; font-size: 13px; font-weight: 700; }
|
|
8
|
+
.connection span { width: 9px; height: 9px; border-radius: 50%; background: #9b6b23; }
|
|
9
|
+
.connection.connected span { background: #2e7d5b; box-shadow: 0 0 0 3px #dcebe5; }
|
|
10
|
+
.summary { display: flex; flex-wrap: wrap; gap: 1px; max-width: 1200px; margin: 28px auto 0; overflow: hidden; border: 1px solid #d3ddd8; border-radius: 8px; background: #d3ddd8; }
|
|
11
|
+
.summary div { display: grid; min-width: 128px; flex: 1 1 128px; gap: 3px; padding: 16px 18px; background: #fff; }
|
|
12
|
+
.summary strong { font: 600 28px/1 Georgia, serif; }
|
|
13
|
+
.summary span { color: #5e6d68; font-size: 12px; }
|
|
14
|
+
.catalog { max-width: 1200px; margin: 18px auto 0; overflow: hidden; border: 1px solid #d3ddd8; border-radius: 8px; background: #fff; }
|
|
15
|
+
.catalog-heading { display: flex; gap: 24px; align-items: end; justify-content: space-between; padding: 22px 24px; border-bottom: 1px solid #d3ddd8; }
|
|
16
|
+
.catalog-heading h2 { margin: 0; font-size: 20px; }
|
|
17
|
+
.catalog-heading p { margin: 6px 0 0; color: #5e6d68; font-size: 13px; }
|
|
18
|
+
.search { display: grid; min-width: min(340px, 100%); gap: 6px; color: #5e6d68; font-size: 12px; font-weight: 700; }
|
|
19
|
+
.search input { width: 100%; padding: 10px 12px; border: 1px solid #b9c7c0; border-radius: 6px; color: #173c36; background: #fff; font: inherit; font-size: 14px; font-weight: 400; }
|
|
20
|
+
.search input:focus { outline: 3px solid #b8ddd5; outline-offset: 1px; border-color: #167d6c; }
|
|
21
|
+
.table-scroll { overflow-x: auto; }
|
|
22
|
+
.catalog table { width: 100%; border-collapse: collapse; text-align: left; }
|
|
23
|
+
.catalog th, .catalog td { padding: 13px 24px; border-bottom: 1px solid #e3eae6; }
|
|
24
|
+
.catalog thead th { color: #5e6d68; background: #f8faf9; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
|
|
25
|
+
.catalog tbody th { font-weight: 600; }
|
|
26
|
+
.catalog tbody td { color: #5e6d68; }
|
|
27
|
+
.catalog tbody tr:last-child th, .catalog tbody tr:last-child td { border-bottom: 0; }
|
|
28
|
+
.type { display: inline-block; padding: 4px 8px; border-radius: 999px; color: #126858; background: #e5f2ee; font-size: 11px; font-weight: 700; }
|
|
29
|
+
.empty { margin: 0; padding: 32px 24px; color: #5e6d68; }
|
|
30
|
+
@media (max-width: 720px) {
|
|
31
|
+
.hello-world { padding: 20px; }
|
|
32
|
+
.hero, .catalog-heading { align-items: stretch; flex-direction: column; }
|
|
33
|
+
.search { min-width: 0; }
|
|
34
|
+
.catalog th, .catalog td { padding: 12px 16px; }
|
|
35
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState, type ReactElement } from "react";
|
|
2
|
+
import type { ReportApplicationProps } from "./reportRuntime";
|
|
3
|
+
|
|
4
|
+
interface CatalogRow {
|
|
5
|
+
type: string;
|
|
6
|
+
parent: string;
|
|
7
|
+
entity: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const TYPE_ORDER = ["Model", "Table", "Column", "Measure", "Hierarchy", "Hierarchy level", "Relationship", "Parameter"];
|
|
11
|
+
const TYPE_PLURALS: Record<string, string> = { Hierarchy: "Hierarchies", "Hierarchy level": "Hierarchy levels" };
|
|
12
|
+
|
|
13
|
+
export function ReportApplication({ model, onVisibleDatasetsChange }: ReportApplicationProps): ReactElement {
|
|
14
|
+
const [query, setQuery] = useState("");
|
|
15
|
+
useEffect(() => onVisibleDatasetsChange?.(["model-catalog"]), [onVisibleDatasetsChange]);
|
|
16
|
+
|
|
17
|
+
const dataset = model.datasets["model-catalog"];
|
|
18
|
+
const rows = useMemo<CatalogRow[]>(() => (dataset?.points ?? [])
|
|
19
|
+
.map((point) => ({
|
|
20
|
+
type: point.dimensions[0]?.label ?? "Entity",
|
|
21
|
+
parent: point.dimensions[1]?.label ?? "",
|
|
22
|
+
entity: point.dimensions[2]?.label ?? point.label,
|
|
23
|
+
}))
|
|
24
|
+
.sort((left, right) => (TYPE_ORDER.indexOf(left.type) - TYPE_ORDER.indexOf(right.type))
|
|
25
|
+
|| left.parent.localeCompare(right.parent)
|
|
26
|
+
|| left.entity.localeCompare(right.entity)), [dataset]);
|
|
27
|
+
|
|
28
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
29
|
+
const visibleRows = normalizedQuery
|
|
30
|
+
? rows.filter((row) => [row.type, row.parent, row.entity].some((value) => value.toLowerCase().includes(normalizedQuery)))
|
|
31
|
+
: rows;
|
|
32
|
+
const counts = rows.reduce<Record<string, number>>((result, row) => {
|
|
33
|
+
result[row.type] = (result[row.type] ?? 0) + 1;
|
|
34
|
+
return result;
|
|
35
|
+
}, {});
|
|
36
|
+
const countTypes = [...TYPE_ORDER.filter((type) => counts[type] !== undefined), ...Object.keys(counts).filter((type) => !TYPE_ORDER.includes(type)).sort()];
|
|
37
|
+
const connected = rows.length > 0;
|
|
38
|
+
|
|
39
|
+
return <main className="hello-world">
|
|
40
|
+
<header className="hero">
|
|
41
|
+
<div>
|
|
42
|
+
<p className="eyebrow">Super Visual / connectivity proof</p>
|
|
43
|
+
<h1>Hello, semantic model.</h1>
|
|
44
|
+
<p className="lede">This searchable catalog arrived through the same governed semantic provider used by every Super Visual application.</p>
|
|
45
|
+
</div>
|
|
46
|
+
<p className={connected ? "connection connected" : "connection waiting"} role="status">
|
|
47
|
+
<span aria-hidden="true" />
|
|
48
|
+
{connected ? "Connected · " + rows.length + " entities" : "Waiting for semantic data…"}
|
|
49
|
+
</p>
|
|
50
|
+
</header>
|
|
51
|
+
|
|
52
|
+
<section className="summary" aria-label="Semantic model summary">
|
|
53
|
+
{countTypes.map((type) => <div key={type}><strong>{counts[type]}</strong><span>{counts[type] === 1 ? type : TYPE_PLURALS[type] ?? `${type}s`}</span></div>)}
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<section className="catalog" aria-labelledby="catalog-title">
|
|
57
|
+
<div className="catalog-heading">
|
|
58
|
+
<div><h2 id="catalog-title">Semantic model entities</h2><p>Tables, columns, measures, hierarchies, relationships, and parameters declared by the model.</p></div>
|
|
59
|
+
<label className="search"><span>Search entities</span><input type="search" value={query} onChange={(event) => setQuery(event.currentTarget.value)} placeholder="Search type, parent, or name" /></label>
|
|
60
|
+
</div>
|
|
61
|
+
{!connected ? <p className="empty">The page will populate when the selected semantic model returns its first rows.</p>
|
|
62
|
+
: visibleRows.length === 0 ? <p className="empty">No semantic entities match “{query}”.</p>
|
|
63
|
+
: <div className="table-scroll"><table>
|
|
64
|
+
<thead><tr><th scope="col">Type</th><th scope="col">Parent</th><th scope="col">Entity</th></tr></thead>
|
|
65
|
+
<tbody>{visibleRows.map((row) => <tr key={[row.type, row.parent, row.entity].join("\u0000")}><td><span className="type">{row.type}</span></td><td>{row.parent || "—"}</td><th scope="row">{row.entity}</th></tr>)}</tbody>
|
|
66
|
+
</table></div>}
|
|
67
|
+
</section>
|
|
68
|
+
</main>;
|
|
69
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
contractVersion: 1
|
|
2
|
+
application:
|
|
3
|
+
id: {{PROJECT_ID}}
|
|
4
|
+
title: {{PROJECT_TITLE}}
|
|
5
|
+
entry: contracts/report-source.json
|
|
6
|
+
style: contracts/report-source.json
|
|
7
|
+
target:
|
|
8
|
+
platform: powerbi
|
|
9
|
+
visual:
|
|
10
|
+
name: {{VISUAL_NAME}}
|
|
11
|
+
displayName: {{PROJECT_TITLE}}
|
|
12
|
+
guid: {{VISUAL_GUID}}
|
|
13
|
+
version: 1.0.0.1
|
|
14
|
+
apiVersion: 5.11.1
|
|
15
|
+
semantic:
|
|
16
|
+
model: contracts/semantic-model.yaml
|
|
17
|
+
broker: contracts/widget-broker.json
|
|
18
|
+
pageSet: contracts/page-set.yaml
|
|
19
|
+
source:
|
|
20
|
+
bundle: contracts/report-source.json
|
|
21
|
+
output:
|
|
22
|
+
directory: dist
|
|
23
|
+
compliance:
|
|
24
|
+
policies: [core, powerbi]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="referrer" content="no-referrer" />
|
|
7
|
+
<title>Super Visual semantic preview</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<aside id="supervisual-preview-status" role="status">Connecting to semantic model…</aside>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|