@malloy-publisher/sdk 0.0.196-dev → 0.0.197-dev
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/README.md +77 -77
- package/TREE_SHAKING.md +11 -11
- package/dist/{ServerProvider-CvZTC0sJ.es.js → ServerProvider-DDScRRDc.es.js} +1393 -1393
- package/dist/ServerProvider-IhQ4aYBm.cjs.js +1 -0
- package/dist/client/api.d.ts +674 -674
- package/dist/client/configuration.d.ts +1 -1
- package/dist/client/index.cjs.js +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.es.js +53 -53
- package/dist/components/Environment/Environment.d.ts +6 -0
- package/dist/components/Environment/index.d.ts +2 -0
- package/dist/components/Home/AddEnvironmentDialog.d.ts +1 -0
- package/dist/components/Home/DeleteEnvironmentDialog.d.ts +5 -0
- package/dist/components/Home/EditEnvironmentDialog.d.ts +7 -0
- package/dist/components/Home/Home.d.ts +2 -2
- package/dist/components/ServerProvider.d.ts +4 -4
- package/dist/components/index.d.ts +1 -1
- package/dist/{core-mP4paWtU.cjs.js → core-7-3Jcsb0.cjs.js} +1 -1
- package/dist/{core-BrfQApxh.es.js → core-w79IMXAG.es.js} +1 -1
- package/dist/hooks/useDimensionFiltersFromSpec.d.ts +3 -3
- package/dist/hooks/useDimensionFiltersQuery.d.ts +3 -3
- package/dist/hooks/useDimensionalFilterRangeData.d.ts +4 -4
- package/dist/{index-ClRkROAM.es.js → index-CN0_kZSF.es.js} +13067 -13033
- package/dist/{index-CW09s4Xx.cjs.js → index-Xo_ADux9.cjs.js} +34 -34
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +21 -21
- package/dist/utils/formatting.d.ts +1 -1
- package/dist/utils/parsing.d.ts +2 -2
- package/package.json +1 -1
- package/src/components/Connections/DeleteConnectionDialog.tsx +1 -0
- package/src/components/Connections/EditConnectionDialog.tsx +2 -0
- package/src/components/{Project → Environment}/About.tsx +8 -4
- package/src/components/{Project → Environment}/AddPackageDialog.tsx +5 -3
- package/src/components/{Project → Environment}/ConnectionExplorer.tsx +14 -14
- package/src/components/{Project → Environment}/DeletePackageDialog.tsx +2 -2
- package/src/components/{Project → Environment}/EditPackageDialog.tsx +18 -13
- package/src/components/{Project/Project.tsx → Environment/Environment.tsx} +6 -6
- package/src/components/{Project → Environment}/Packages.tsx +6 -5
- package/src/components/Environment/index.ts +2 -0
- package/src/components/Home/{AddProjectDialog.tsx → AddEnvironmentDialog.tsx} +21 -18
- package/src/components/Home/{DeleteProjectDialog.tsx → DeleteEnvironmentDialog.tsx} +14 -13
- package/src/components/Home/{EditProjectDialog.tsx → EditEnvironmentDialog.tsx} +32 -28
- package/src/components/Home/Home.tsx +39 -38
- package/src/components/Model/ModelCell.tsx +2 -2
- package/src/components/Model/SourcesExplorer.tsx +2 -2
- package/src/components/Model/useModelData.ts +3 -3
- package/src/components/Notebook/Notebook.tsx +7 -7
- package/src/components/Package/Config.tsx +4 -4
- package/src/components/Package/Connections.tsx +15 -15
- package/src/components/Package/Databases.tsx +4 -4
- package/src/components/Package/Models.tsx +4 -4
- package/src/components/Package/Notebooks.tsx +4 -4
- package/src/components/QueryResult/QueryResult.tsx +6 -6
- package/src/components/ServerProvider.tsx +5 -5
- package/src/components/Workbook/ModelPicker.tsx +4 -4
- package/src/components/Workbook/Workbook.tsx +4 -4
- package/src/components/index.ts +1 -1
- package/src/hooks/useDimensionFiltersFromSpec.ts +5 -5
- package/src/hooks/useDimensionFiltersQuery.ts +6 -6
- package/src/hooks/useDimensionalFilterRangeData.ts +7 -7
- package/src/hooks/useRawQueryData.ts +3 -3
- package/src/index.ts +1 -1
- package/src/utils/formatting.spec.ts +22 -21
- package/src/utils/formatting.ts +8 -7
- package/src/utils/parsing.spec.ts +23 -19
- package/src/utils/parsing.ts +8 -6
- package/dist/ServerProvider-BMsmCksc.cjs.js +0 -1
- package/dist/components/Home/AddProjectDialog.d.ts +0 -1
- package/dist/components/Home/DeleteProjectDialog.d.ts +0 -5
- package/dist/components/Home/EditProjectDialog.d.ts +0 -7
- package/dist/components/Project/Project.d.ts +0 -6
- package/dist/components/Project/index.d.ts +0 -2
- package/src/components/Project/index.ts +0 -2
- /package/dist/components/{Project → Environment}/About.d.ts +0 -0
- /package/dist/components/{Project → Environment}/AddPackageDialog.d.ts +0 -0
- /package/dist/components/{Project → Environment}/ConnectionExplorer.d.ts +0 -0
- /package/dist/components/{Project → Environment}/DeletePackageDialog.d.ts +0 -0
- /package/dist/components/{Project → Environment}/EditPackageDialog.d.ts +0 -0
- /package/dist/components/{Project → Environment}/Packages.d.ts +0 -0
|
@@ -16,12 +16,12 @@ interface QueryResultProps {
|
|
|
16
16
|
|
|
17
17
|
export function createEmbeddedQueryResult(props: QueryResultProps): string {
|
|
18
18
|
const {
|
|
19
|
-
|
|
19
|
+
environmentName: optionalProjectName,
|
|
20
20
|
packageName: optionalPackageName,
|
|
21
21
|
} = parseResourceUri(props.resourceUri);
|
|
22
22
|
if (!optionalProjectName || !optionalPackageName) {
|
|
23
23
|
throw new Error(
|
|
24
|
-
"
|
|
24
|
+
"Environment and Package name must be provided for query embedding.",
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
return JSON.stringify({
|
|
@@ -66,11 +66,11 @@ export default function QueryResult({
|
|
|
66
66
|
resourceUri,
|
|
67
67
|
height = 400,
|
|
68
68
|
}: QueryResultProps) {
|
|
69
|
-
const { modelPath,
|
|
69
|
+
const { modelPath, environmentName, packageName, versionId } =
|
|
70
70
|
parseResourceUri(resourceUri);
|
|
71
71
|
const { apiClients } = useServer();
|
|
72
72
|
|
|
73
|
-
if (!
|
|
73
|
+
if (!environmentName || !packageName) {
|
|
74
74
|
throw new Error(
|
|
75
75
|
"No project or package name provided. A resource URI must be provided.",
|
|
76
76
|
);
|
|
@@ -80,7 +80,7 @@ export default function QueryResult({
|
|
|
80
80
|
queryKey: [resourceUri, query, sourceName, queryName],
|
|
81
81
|
queryFn: () =>
|
|
82
82
|
apiClients.models.executeQueryModel(
|
|
83
|
-
|
|
83
|
+
environmentName,
|
|
84
84
|
packageName,
|
|
85
85
|
modelPath,
|
|
86
86
|
{
|
|
@@ -104,7 +104,7 @@ export default function QueryResult({
|
|
|
104
104
|
)}
|
|
105
105
|
{isError && (
|
|
106
106
|
<ApiErrorDisplay
|
|
107
|
-
context={`${
|
|
107
|
+
context={`${environmentName} > ${packageName} > ${modelPath}`}
|
|
108
108
|
error={error}
|
|
109
109
|
/>
|
|
110
110
|
)}
|
|
@@ -4,17 +4,17 @@ import React, {
|
|
|
4
4
|
createContext,
|
|
5
5
|
ReactNode,
|
|
6
6
|
useContext,
|
|
7
|
-
useMemo,
|
|
8
7
|
useEffect,
|
|
8
|
+
useMemo,
|
|
9
9
|
useState,
|
|
10
10
|
} from "react";
|
|
11
11
|
import {
|
|
12
12
|
ConnectionsApi,
|
|
13
13
|
DatabasesApi,
|
|
14
|
+
EnvironmentsApi,
|
|
14
15
|
ModelsApi,
|
|
15
16
|
NotebooksApi,
|
|
16
17
|
PackagesApi,
|
|
17
|
-
ProjectsApi,
|
|
18
18
|
PublisherApi,
|
|
19
19
|
WatchModeApi,
|
|
20
20
|
} from "../client";
|
|
@@ -52,8 +52,8 @@ export interface ServerProviderProps {
|
|
|
52
52
|
* Will send "Bearer 123" in the Authorization header.
|
|
53
53
|
*/
|
|
54
54
|
getAccessToken?: () => Promise<string>;
|
|
55
|
-
/** Whether the publisher should allow
|
|
56
|
-
* When false, users can only view and explore existing
|
|
55
|
+
/** Whether the publisher should allow environment and package management operations.
|
|
56
|
+
* When false, users can only view and explore existing environments and packages.
|
|
57
57
|
* @default true
|
|
58
58
|
*/
|
|
59
59
|
mutable?: boolean;
|
|
@@ -83,7 +83,7 @@ const getApiClients = (
|
|
|
83
83
|
return {
|
|
84
84
|
models: new ModelsApi(config, basePath, axiosInstance),
|
|
85
85
|
publisher: new PublisherApi(config, basePath, axiosInstance),
|
|
86
|
-
|
|
86
|
+
environments: new EnvironmentsApi(config, basePath, axiosInstance),
|
|
87
87
|
packages: new PackagesApi(config, basePath, axiosInstance),
|
|
88
88
|
notebooks: new NotebooksApi(config, basePath, axiosInstance),
|
|
89
89
|
connections: new ConnectionsApi(config, basePath, axiosInstance),
|
|
@@ -32,16 +32,16 @@ export function ModelPicker({
|
|
|
32
32
|
resourceUri,
|
|
33
33
|
}: ModelPickerProps) {
|
|
34
34
|
const {
|
|
35
|
-
|
|
35
|
+
environmentName: environmentName,
|
|
36
36
|
packageName: packageName,
|
|
37
37
|
versionId: versionId,
|
|
38
38
|
} = parseResourceUri(resourceUri);
|
|
39
39
|
const { apiClients } = useServer();
|
|
40
40
|
|
|
41
41
|
const { data, isLoading, isSuccess, isError, error } = useQueryWithApiError({
|
|
42
|
-
queryKey: ["models",
|
|
42
|
+
queryKey: ["models", environmentName, packageName, versionId],
|
|
43
43
|
queryFn: () =>
|
|
44
|
-
apiClients.models.listModels(
|
|
44
|
+
apiClients.models.listModels(environmentName, packageName, versionId),
|
|
45
45
|
});
|
|
46
46
|
const [selectedModels, setSelectedModels] = React.useState<string[]>(
|
|
47
47
|
initialSelectedModels || [],
|
|
@@ -76,7 +76,7 @@ export function ModelPicker({
|
|
|
76
76
|
return (
|
|
77
77
|
<ApiErrorDisplay
|
|
78
78
|
error={error}
|
|
79
|
-
context={`${
|
|
79
|
+
context={`${environmentName} > ${packageName} > Model Picker`}
|
|
80
80
|
/>
|
|
81
81
|
);
|
|
82
82
|
}
|
|
@@ -45,7 +45,7 @@ export default function Workbook({ workbookPath, resourceUri }: WorkbookProps) {
|
|
|
45
45
|
const { server, getAccessToken } = useServer();
|
|
46
46
|
const { apiClients } = useServer();
|
|
47
47
|
const { workbookStorage } = useWorkbookStorage();
|
|
48
|
-
const {
|
|
48
|
+
const { environmentName, packageName } = parseResourceUri(resourceUri);
|
|
49
49
|
const [success, setSuccess] = React.useState<string | undefined>(undefined);
|
|
50
50
|
const [lastError, setLastError] = React.useState<string | undefined>(
|
|
51
51
|
undefined,
|
|
@@ -113,7 +113,7 @@ export default function Workbook({ workbookPath, resourceUri }: WorkbookProps) {
|
|
|
113
113
|
}
|
|
114
114
|
setDeleteDialogOpen(false);
|
|
115
115
|
// TODO(jjs) - on delete event
|
|
116
|
-
navigate(`/${
|
|
116
|
+
navigate(`/${environmentName}/${packageName}`);
|
|
117
117
|
};
|
|
118
118
|
|
|
119
119
|
const handleDeleteCancel = () => {
|
|
@@ -155,7 +155,7 @@ export default function Workbook({ workbookPath, resourceUri }: WorkbookProps) {
|
|
|
155
155
|
console.log("Fetching model from Publisher", model);
|
|
156
156
|
promises.push(
|
|
157
157
|
apiClients.models
|
|
158
|
-
.getModel(
|
|
158
|
+
.getModel(environmentName, packageName, model, undefined)
|
|
159
159
|
.then((data) => ({
|
|
160
160
|
modelPath: model,
|
|
161
161
|
sourceInfos: data.data.sourceInfos.map((source) =>
|
|
@@ -182,7 +182,7 @@ export default function Workbook({ workbookPath, resourceUri }: WorkbookProps) {
|
|
|
182
182
|
fetchModels();
|
|
183
183
|
// This function cannot depend on sourceAndPaths because it would cause an infinite loop.
|
|
184
184
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
185
|
-
}, [getAccessToken, workbookData, packageName,
|
|
185
|
+
}, [getAccessToken, workbookData, packageName, environmentName, server]);
|
|
186
186
|
|
|
187
187
|
React.useEffect(() => {
|
|
188
188
|
if (!workbookPath) {
|
package/src/components/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export { AnalyzePackageButton } from "./AnalyzePackageButton";
|
|
2
2
|
export { useRouterClickHandler } from "./click_helper";
|
|
3
|
+
export * from "./Environment";
|
|
3
4
|
export * from "./filter";
|
|
4
5
|
export * from "./Home";
|
|
5
6
|
export * from "./Loading";
|
|
6
7
|
export * from "./Model";
|
|
7
8
|
export * from "./Notebook";
|
|
8
9
|
export * from "./Package";
|
|
9
|
-
export * from "./Project";
|
|
10
10
|
export * from "./QueryResult";
|
|
11
11
|
export * from "./RenderedResult";
|
|
12
12
|
export { ServerProvider, useServer } from "./ServerProvider";
|
|
@@ -10,8 +10,8 @@ import { useDimensionFiltersQuery } from "./useDimensionFiltersQuery";
|
|
|
10
10
|
* Configuration for dimensional filters
|
|
11
11
|
*/
|
|
12
12
|
export interface DimensionFiltersConfig {
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/** Environment name */
|
|
14
|
+
environment: string;
|
|
15
15
|
/** Package name */
|
|
16
16
|
package: string;
|
|
17
17
|
/** Version ID (optional) */
|
|
@@ -37,7 +37,7 @@ export interface UseDimensionFiltersFromSpecOptions {
|
|
|
37
37
|
* Combined hook that manages dimensional filters, data fetching, and query execution
|
|
38
38
|
* from a single configuration object.
|
|
39
39
|
*
|
|
40
|
-
* @param config - Configuration containing
|
|
40
|
+
* @param config - Configuration containing environment, package, dimension specs, and index limit
|
|
41
41
|
* @param queryLimit - Maximum number of results to return from query (default: 100)
|
|
42
42
|
* @returns All state and methods needed for dimensional filtering UI
|
|
43
43
|
*/
|
|
@@ -69,7 +69,7 @@ export function useDimensionFiltersFromSpec(
|
|
|
69
69
|
// The hook handles multiple source/model combos internally
|
|
70
70
|
const { data, noRowsMatchedFilter, isLoading, isError, error, refetch } =
|
|
71
71
|
useDimensionalFilterRangeData({
|
|
72
|
-
|
|
72
|
+
environment: config.environment,
|
|
73
73
|
package: config.package,
|
|
74
74
|
indexLimit: config.indexLimit,
|
|
75
75
|
dimensionSpecs: config.dimensionSpecs,
|
|
@@ -83,7 +83,7 @@ export function useDimensionFiltersFromSpec(
|
|
|
83
83
|
// Generate the embedded query result (uses primary source/model)
|
|
84
84
|
const { embeddedQueryResult, queryString, executeQuery, canExecute } =
|
|
85
85
|
useDimensionFiltersQuery({
|
|
86
|
-
|
|
86
|
+
environment: config.environment,
|
|
87
87
|
package: config.package,
|
|
88
88
|
model: primaryModel,
|
|
89
89
|
source: primarySource,
|
|
@@ -7,8 +7,8 @@ import { FilterSelection, FilterValuePrimitive } from "./useDimensionFilters";
|
|
|
7
7
|
* Parameters for the useDimensionFiltersQuery hook
|
|
8
8
|
*/
|
|
9
9
|
export interface UseDimensionFiltersQueryParams {
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/** Environment name */
|
|
11
|
+
environment: string;
|
|
12
12
|
/** Package name */
|
|
13
13
|
package: string;
|
|
14
14
|
/** Model path */
|
|
@@ -218,7 +218,7 @@ function buildFilteredQuery(
|
|
|
218
218
|
* });
|
|
219
219
|
*
|
|
220
220
|
* const { embeddedQueryResult, executeQuery, canExecute } = useDimensionFiltersQuery({
|
|
221
|
-
*
|
|
221
|
+
* environment: "my-environment",
|
|
222
222
|
* package: "my-package",
|
|
223
223
|
* model: "model.malloy",
|
|
224
224
|
* source: "my_source",
|
|
@@ -242,7 +242,7 @@ export function useDimensionFiltersQuery(
|
|
|
242
242
|
params: UseDimensionFiltersQueryParams,
|
|
243
243
|
): DimensionFiltersQueryResult {
|
|
244
244
|
const {
|
|
245
|
-
|
|
245
|
+
environment,
|
|
246
246
|
package: packageName,
|
|
247
247
|
model,
|
|
248
248
|
source,
|
|
@@ -266,11 +266,11 @@ export function useDimensionFiltersQuery(
|
|
|
266
266
|
const resourceUri = useMemo(
|
|
267
267
|
() =>
|
|
268
268
|
encodeResourceUri({
|
|
269
|
-
|
|
269
|
+
environmentName: environment,
|
|
270
270
|
packageName,
|
|
271
271
|
modelPath: model,
|
|
272
272
|
}),
|
|
273
|
-
[
|
|
273
|
+
[environment, packageName, model],
|
|
274
274
|
);
|
|
275
275
|
|
|
276
276
|
// Check if query can be executed
|
|
@@ -80,8 +80,8 @@ export type DimensionValues = Map<string, DimensionValue[]>;
|
|
|
80
80
|
* Parameters for the useDimensionalFilterRangeData hook
|
|
81
81
|
*/
|
|
82
82
|
export interface UseDimensionalFilterRangeDataParams {
|
|
83
|
-
/**
|
|
84
|
-
|
|
83
|
+
/** Environment name */
|
|
84
|
+
environment: string;
|
|
85
85
|
/** Package name */
|
|
86
86
|
package: string;
|
|
87
87
|
/** List of dimension specifications (each includes source and model) */
|
|
@@ -611,13 +611,13 @@ function parseIndexQueryResult(
|
|
|
611
611
|
* The hook groups dimension specs by source/model combination and runs separate
|
|
612
612
|
* index queries for each group, then merges the results.
|
|
613
613
|
*
|
|
614
|
-
* @param params - Parameters including
|
|
614
|
+
* @param params - Parameters including environment, package, and dimension specs (each with source/model)
|
|
615
615
|
* @returns Query result with dimension values map, loading state, and error information
|
|
616
616
|
*
|
|
617
617
|
* @example
|
|
618
618
|
* ```tsx
|
|
619
619
|
* const { data, isLoading, error } = useDimensionalFilterRangeData({
|
|
620
|
-
*
|
|
620
|
+
* environment: "my-environment",
|
|
621
621
|
* package: "my-package",
|
|
622
622
|
* dimensionSpecs: [
|
|
623
623
|
* { dimensionName: "category", filterType: "Star", source: "my_source", model: "model.malloy" },
|
|
@@ -632,7 +632,7 @@ export function useDimensionalFilterRangeData(
|
|
|
632
632
|
params: UseDimensionalFilterRangeDataParams,
|
|
633
633
|
): DimensionalFilterRangeDataResult {
|
|
634
634
|
const {
|
|
635
|
-
|
|
635
|
+
environment,
|
|
636
636
|
package: packageName,
|
|
637
637
|
dimensionSpecs,
|
|
638
638
|
versionId,
|
|
@@ -695,7 +695,7 @@ export function useDimensionalFilterRangeData(
|
|
|
695
695
|
const queryResult = useQueryWithApiError({
|
|
696
696
|
queryKey: [
|
|
697
697
|
"dimensionalFilter",
|
|
698
|
-
|
|
698
|
+
environment,
|
|
699
699
|
packageName,
|
|
700
700
|
dimensionSpecs,
|
|
701
701
|
versionId,
|
|
@@ -716,7 +716,7 @@ export function useDimensionalFilterRangeData(
|
|
|
716
716
|
const results = await Promise.all(
|
|
717
717
|
queryConfigs.map(async (config) => {
|
|
718
718
|
const response = await apiClients.models.executeQueryModel(
|
|
719
|
-
|
|
719
|
+
environment,
|
|
720
720
|
packageName,
|
|
721
721
|
config.model,
|
|
722
722
|
{
|
|
@@ -19,14 +19,14 @@ export function useRawQueryData({
|
|
|
19
19
|
enabled = true,
|
|
20
20
|
resourceUri,
|
|
21
21
|
}: UseRawQueryDataProps) {
|
|
22
|
-
const {
|
|
22
|
+
const { environmentName, packageName, versionId } =
|
|
23
23
|
parseResourceUri(resourceUri);
|
|
24
24
|
const { apiClients } = useServer();
|
|
25
25
|
|
|
26
26
|
const { data, isSuccess, isError, error, isLoading } = useQueryWithApiError({
|
|
27
27
|
queryKey: [
|
|
28
28
|
"rawQueryData",
|
|
29
|
-
|
|
29
|
+
environmentName,
|
|
30
30
|
packageName,
|
|
31
31
|
modelPath,
|
|
32
32
|
versionId,
|
|
@@ -36,7 +36,7 @@ export function useRawQueryData({
|
|
|
36
36
|
],
|
|
37
37
|
queryFn: () =>
|
|
38
38
|
apiClients.models.executeQueryModel(
|
|
39
|
-
|
|
39
|
+
environmentName,
|
|
40
40
|
packageName,
|
|
41
41
|
modelPath,
|
|
42
42
|
{
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./components";
|
|
2
|
-
export { default as ConnectionExplorer } from "./components/
|
|
2
|
+
export { default as ConnectionExplorer } from "./components/Environment/ConnectionExplorer";
|
|
3
3
|
export { useServer } from "./components/ServerProvider";
|
|
4
4
|
export * from "./hooks";
|
|
5
5
|
export { useRawQueryData } from "./hooks/useRawQueryData";
|
|
@@ -3,10 +3,11 @@ import { encodeResourceUri, parseResourceUri } from "./formatting";
|
|
|
3
3
|
|
|
4
4
|
describe("parseResourceUri", () => {
|
|
5
5
|
it("should parse a package URI", () => {
|
|
6
|
-
const resourceUri =
|
|
6
|
+
const resourceUri =
|
|
7
|
+
"publisher://environments/malloy-samples/packages/names";
|
|
7
8
|
const parsedResource = parseResourceUri(resourceUri);
|
|
8
9
|
expect(parsedResource).toEqual({
|
|
9
|
-
|
|
10
|
+
environmentName: "malloy-samples",
|
|
10
11
|
packageName: "names",
|
|
11
12
|
connectionName: undefined,
|
|
12
13
|
modelPath: undefined,
|
|
@@ -15,10 +16,10 @@ describe("parseResourceUri", () => {
|
|
|
15
16
|
});
|
|
16
17
|
it("should parse a connection URI", () => {
|
|
17
18
|
const resourceUri =
|
|
18
|
-
"publisher://
|
|
19
|
+
"publisher://environments/malloy-samples/connections/bigquery";
|
|
19
20
|
const parsedResource = parseResourceUri(resourceUri);
|
|
20
21
|
expect(parsedResource).toEqual({
|
|
21
|
-
|
|
22
|
+
environmentName: "malloy-samples",
|
|
22
23
|
packageName: undefined,
|
|
23
24
|
connectionName: "bigquery",
|
|
24
25
|
modelPath: undefined,
|
|
@@ -28,10 +29,10 @@ describe("parseResourceUri", () => {
|
|
|
28
29
|
|
|
29
30
|
it("should parse a modelPath URI", () => {
|
|
30
31
|
const resourceUri =
|
|
31
|
-
"publisher://
|
|
32
|
+
"publisher://environments/malloy-samples/packages/names/models/names1.malloynb";
|
|
32
33
|
const parsedResource = parseResourceUri(resourceUri);
|
|
33
34
|
expect(parsedResource).toEqual({
|
|
34
|
-
|
|
35
|
+
environmentName: "malloy-samples",
|
|
35
36
|
packageName: "names",
|
|
36
37
|
connectionName: undefined,
|
|
37
38
|
modelPath: "names1.malloynb",
|
|
@@ -41,13 +42,13 @@ describe("parseResourceUri", () => {
|
|
|
41
42
|
|
|
42
43
|
it("should throw an error if the resource URI has the wrong protocol", () => {
|
|
43
44
|
const resourceUri =
|
|
44
|
-
"http://
|
|
45
|
+
"http://environments/malloy-samples/packages/names/models/names1.malloynb";
|
|
45
46
|
expect(() => parseResourceUri(resourceUri)).toThrow(
|
|
46
47
|
`Failed to parse resource URI: ${resourceUri}`,
|
|
47
48
|
);
|
|
48
49
|
});
|
|
49
50
|
|
|
50
|
-
it("should throw an error if the resource URI is missing the
|
|
51
|
+
it("should throw an error if the resource URI is missing the environment name", () => {
|
|
51
52
|
const resourceUri = "publisher://";
|
|
52
53
|
expect(() => parseResourceUri(resourceUri)).toThrow(
|
|
53
54
|
/Failed to parse resource URI/,
|
|
@@ -56,10 +57,10 @@ describe("parseResourceUri", () => {
|
|
|
56
57
|
|
|
57
58
|
it("should parse the optional versionId parameter if present", () => {
|
|
58
59
|
const resourceUri =
|
|
59
|
-
"publisher://
|
|
60
|
+
"publisher://environments/malloy-samples/packages/names?versionId=1.0.0";
|
|
60
61
|
const parsedResource = parseResourceUri(resourceUri);
|
|
61
62
|
expect(parsedResource).toEqual({
|
|
62
|
-
|
|
63
|
+
environmentName: "malloy-samples",
|
|
63
64
|
packageName: "names",
|
|
64
65
|
connectionName: undefined,
|
|
65
66
|
modelPath: undefined,
|
|
@@ -71,65 +72,65 @@ describe("parseResourceUri", () => {
|
|
|
71
72
|
describe("encodeResourceUri", () => {
|
|
72
73
|
it("should encode a package URI with no versionId", () => {
|
|
73
74
|
const resourceUri = encodeResourceUri({
|
|
74
|
-
|
|
75
|
+
environmentName: "malloy-samples",
|
|
75
76
|
packageName: "names",
|
|
76
77
|
connectionName: undefined,
|
|
77
78
|
modelPath: undefined,
|
|
78
79
|
versionId: undefined,
|
|
79
80
|
});
|
|
80
81
|
expect(resourceUri).toEqual(
|
|
81
|
-
"publisher://
|
|
82
|
+
"publisher://environments/malloy-samples/packages/names",
|
|
82
83
|
);
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
it("should encode a package URI with a versionId", () => {
|
|
86
87
|
const resourceUri = encodeResourceUri({
|
|
87
|
-
|
|
88
|
+
environmentName: "malloy-samples",
|
|
88
89
|
packageName: "names",
|
|
89
90
|
versionId: "1.0.0",
|
|
90
91
|
connectionName: undefined,
|
|
91
92
|
modelPath: undefined,
|
|
92
93
|
});
|
|
93
94
|
expect(resourceUri).toEqual(
|
|
94
|
-
"publisher://
|
|
95
|
+
"publisher://environments/malloy-samples/packages/names?versionId=1.0.0",
|
|
95
96
|
);
|
|
96
97
|
});
|
|
97
98
|
|
|
98
99
|
it("should encode a connection URI", () => {
|
|
99
100
|
const resourceUri = encodeResourceUri({
|
|
100
|
-
|
|
101
|
+
environmentName: "malloy-samples",
|
|
101
102
|
packageName: undefined,
|
|
102
103
|
connectionName: "bigquery",
|
|
103
104
|
modelPath: undefined,
|
|
104
105
|
versionId: undefined,
|
|
105
106
|
});
|
|
106
107
|
expect(resourceUri).toEqual(
|
|
107
|
-
"publisher://
|
|
108
|
+
"publisher://environments/malloy-samples/connections/bigquery",
|
|
108
109
|
);
|
|
109
110
|
});
|
|
110
111
|
|
|
111
112
|
it("should encode a modelPath URI", () => {
|
|
112
113
|
const resourceUri = encodeResourceUri({
|
|
113
|
-
|
|
114
|
+
environmentName: "malloy-samples",
|
|
114
115
|
packageName: "names",
|
|
115
116
|
connectionName: undefined,
|
|
116
117
|
modelPath: "names1.malloynb",
|
|
117
118
|
versionId: undefined,
|
|
118
119
|
});
|
|
119
120
|
expect(resourceUri).toEqual(
|
|
120
|
-
"publisher://
|
|
121
|
+
"publisher://environments/malloy-samples/packages/names/models/names1.malloynb",
|
|
121
122
|
);
|
|
122
123
|
});
|
|
123
124
|
|
|
124
|
-
it("should throw an error if the resource URI is missing the
|
|
125
|
+
it("should throw an error if the resource URI is missing the environment name", () => {
|
|
125
126
|
expect(() =>
|
|
126
127
|
encodeResourceUri({
|
|
127
|
-
|
|
128
|
+
environmentName: "",
|
|
128
129
|
packageName: "names",
|
|
129
130
|
connectionName: undefined,
|
|
130
131
|
modelPath: undefined,
|
|
131
132
|
versionId: undefined,
|
|
132
133
|
}),
|
|
133
|
-
).toThrow(/Failed to encode resource URI, missing
|
|
134
|
+
).toThrow(/Failed to encode resource URI, missing environment name/);
|
|
134
135
|
});
|
|
135
136
|
});
|
package/src/utils/formatting.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type ParsedResource = {
|
|
2
|
-
|
|
2
|
+
environmentName: string;
|
|
3
3
|
packageName?: string | undefined;
|
|
4
4
|
connectionName?: string | undefined;
|
|
5
5
|
versionId?: string | undefined;
|
|
@@ -15,8 +15,9 @@ export const parseResourceUri = (resourceUri: string) => {
|
|
|
15
15
|
const pathParts = (parsedUri.hostname + parsedUri.pathname).split("/");
|
|
16
16
|
for (let i = 0; i < pathParts.length; i += 2) {
|
|
17
17
|
const part = pathParts[i];
|
|
18
|
-
if (part === "
|
|
19
|
-
parsedResource.
|
|
18
|
+
if (part === "environments") {
|
|
19
|
+
parsedResource.environmentName =
|
|
20
|
+
decodeURI(pathParts[i + 1]) || undefined;
|
|
20
21
|
} else if (part === "packages") {
|
|
21
22
|
parsedResource.packageName = decodeURI(pathParts[i + 1]) || undefined;
|
|
22
23
|
} else if (part === "connections") {
|
|
@@ -30,19 +31,19 @@ export const parseResourceUri = (resourceUri: string) => {
|
|
|
30
31
|
|
|
31
32
|
parsedResource.versionId =
|
|
32
33
|
parsedUri.searchParams.get("versionId") || undefined;
|
|
33
|
-
if (!parsedResource.
|
|
34
|
+
if (!parsedResource.environmentName) {
|
|
34
35
|
throw new Error(`Failed to parse resource URI: ${resourceUri}`);
|
|
35
36
|
}
|
|
36
37
|
return parsedResource;
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
export const encodeResourceUri = (resource: ParsedResource) => {
|
|
40
|
-
if (!resource.
|
|
41
|
+
if (!resource.environmentName) {
|
|
41
42
|
throw new Error(
|
|
42
|
-
`Failed to encode resource URI, missing
|
|
43
|
+
`Failed to encode resource URI, missing environment name: ${resource}`,
|
|
43
44
|
);
|
|
44
45
|
}
|
|
45
|
-
let uri = `publisher://
|
|
46
|
+
let uri = `publisher://environments/${resource.environmentName}`;
|
|
46
47
|
if (resource.packageName) {
|
|
47
48
|
uri += `/packages/${resource.packageName}`;
|
|
48
49
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
generateEnvironmentReadme,
|
|
4
|
+
getEnvironmentDescription,
|
|
5
|
+
} from "./parsing";
|
|
3
6
|
|
|
4
|
-
describe("
|
|
7
|
+
describe("getEnvironmentDescription", () => {
|
|
5
8
|
it("should return the first paragraph of the README", () => {
|
|
6
|
-
const readme =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
const readme =
|
|
10
|
+
"# Environment Description\nThis is an environment description";
|
|
11
|
+
expect(getEnvironmentDescription(readme)).toBe(
|
|
12
|
+
"Environment Description\nThis is an environment description",
|
|
9
13
|
);
|
|
10
14
|
});
|
|
11
15
|
|
|
@@ -14,42 +18,42 @@ describe("getProjectDescription", () => {
|
|
|
14
18
|
const readme = `${longDescription}`;
|
|
15
19
|
// 5 characters per word + space, so 20 words = 120 characters
|
|
16
20
|
const truncatedDescription = Array(20).fill("abcde").join(" ") + "...";
|
|
17
|
-
expect(
|
|
21
|
+
expect(getEnvironmentDescription(readme)).toBe(truncatedDescription);
|
|
18
22
|
});
|
|
19
23
|
|
|
20
24
|
it("should return a placeholder description if the README is empty", () => {
|
|
21
25
|
const readme = "";
|
|
22
|
-
expect(
|
|
26
|
+
expect(getEnvironmentDescription(readme)).toBe(
|
|
23
27
|
"Explore semantic models, run queries, and build dashboards",
|
|
24
28
|
);
|
|
25
29
|
});
|
|
26
30
|
});
|
|
27
31
|
|
|
28
|
-
describe("
|
|
32
|
+
describe("generateEnvironmentReadme", () => {
|
|
29
33
|
it("should preserve the existing readme if it exists", () => {
|
|
30
|
-
const
|
|
31
|
-
name: "Test
|
|
34
|
+
const environment = {
|
|
35
|
+
name: "Test Environment",
|
|
32
36
|
readme: "# Test Readme",
|
|
33
37
|
};
|
|
34
|
-
expect(
|
|
38
|
+
expect(generateEnvironmentReadme(environment)).toBe("# Test Readme");
|
|
35
39
|
});
|
|
36
40
|
|
|
37
|
-
it("should generate
|
|
38
|
-
const
|
|
39
|
-
name: "Test
|
|
41
|
+
it("should generate an environment readme with the description if it does not exist", () => {
|
|
42
|
+
const environment = {
|
|
43
|
+
name: "Test Environment",
|
|
40
44
|
readme: "",
|
|
41
45
|
};
|
|
42
|
-
expect(
|
|
43
|
-
"# Test
|
|
46
|
+
expect(generateEnvironmentReadme(environment, "Test Description")).toBe(
|
|
47
|
+
"# Test Environment\n\nTest Description",
|
|
44
48
|
);
|
|
45
49
|
});
|
|
46
50
|
|
|
47
51
|
it("should insert the description in the existing readme if both exist", () => {
|
|
48
|
-
const
|
|
49
|
-
name: "Test
|
|
52
|
+
const environment = {
|
|
53
|
+
name: "Test Environment",
|
|
50
54
|
readme: "# Test Readme\n\nOld Description\n\nMore stuff",
|
|
51
55
|
};
|
|
52
|
-
expect(
|
|
56
|
+
expect(generateEnvironmentReadme(environment, "New Description")).toBe(
|
|
53
57
|
"# Test Readme\n\nNew Description\n\nMore stuff",
|
|
54
58
|
);
|
|
55
59
|
});
|
package/src/utils/parsing.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Helper function to extract a brief description from README content
|
|
2
|
-
export const
|
|
2
|
+
export const getEnvironmentDescription = (
|
|
3
|
+
readme: string | undefined,
|
|
4
|
+
): string => {
|
|
3
5
|
if (!readme) {
|
|
4
6
|
return "Explore semantic models, run queries, and build dashboards";
|
|
5
7
|
}
|
|
@@ -28,19 +30,19 @@ export const getProjectDescription = (readme: string | undefined): string => {
|
|
|
28
30
|
return truncated.join(" ") + "...";
|
|
29
31
|
};
|
|
30
32
|
|
|
31
|
-
export const
|
|
32
|
-
|
|
33
|
+
export const generateEnvironmentReadme = (
|
|
34
|
+
environment: {
|
|
33
35
|
name: string;
|
|
34
36
|
readme?: string;
|
|
35
37
|
},
|
|
36
38
|
description?: string,
|
|
37
39
|
): string => {
|
|
38
|
-
const readmeLines = (
|
|
40
|
+
const readmeLines = (environment.readme || undefined)?.split("\n") ?? [];
|
|
39
41
|
if (readmeLines.length === 0) {
|
|
40
|
-
readmeLines.push(`# ${
|
|
42
|
+
readmeLines.push(`# ${environment.name}`);
|
|
41
43
|
readmeLines.push("");
|
|
42
44
|
}
|
|
43
|
-
if ((
|
|
45
|
+
if ((environment.readme?.length ?? 0) > 0 && description) {
|
|
44
46
|
readmeLines.splice(2, 1, description);
|
|
45
47
|
} else if (description) {
|
|
46
48
|
readmeLines.push(description);
|