Skip to content
Snippets Groups Projects
Unverified Commit 43332678 authored by Phoomparin Mano's avatar Phoomparin Mano Committed by GitHub
Browse files

feat(sdk): add the create question component demo to the cli (#47348)

parent d566fae9
No related branches found
No related tags found
No related merge requests found
......@@ -2,13 +2,12 @@ import { SDK_PACKAGE_NAME } from "../constants/config";
import type { DashboardInfo } from "../types/dashboard";
interface Options {
instanceUrl: string;
dashboards: DashboardInfo[];
userSwitcherEnabled: boolean;
}
export const getAnalyticsDashboardSnippet = (options: Options) => {
const { instanceUrl, dashboards, userSwitcherEnabled } = options;
const { dashboards, userSwitcherEnabled } = options;
let imports = `import { ThemeSwitcher } from './theme-switcher'`;
......@@ -17,8 +16,8 @@ export const getAnalyticsDashboardSnippet = (options: Options) => {
}
return `
import { useState, useContext } from 'react'
import { InteractiveDashboard } from '${SDK_PACKAGE_NAME}'
import { useState, useContext, useReducer } from 'react'
import { InteractiveDashboard, CreateQuestion } from '${SDK_PACKAGE_NAME}'
import { AnalyticsContext } from "./analytics-provider"
${imports}
......@@ -27,30 +26,33 @@ export const AnalyticsDashboard = () => {
const {email} = useContext(AnalyticsContext)
const [dashboardId, setDashboardId] = useState(DASHBOARDS[0].id)
const editLink = \`${instanceUrl}/dashboard/\${dashboardId}\`
const [isCreateQuestion, toggleCreateQuestion] = useReducer((s) => !s, false)
const isDashboard = !isCreateQuestion
return (
<div className="analytics-container">
<div className="analytics-header">
<div>
<select
className="dashboard-select"
onChange={(e) => setDashboardId(e.target.value)}
>
{DASHBOARDS.map((dashboard) => (
<option key={dashboard.id} value={dashboard.id}>
{dashboard.name}
</option>
))}
</select>
${userSwitcherEnabled ? "<UserSwitcher />" : ""}
</div>
<div className="analytics-header-right">
{/** TODO: Remove. This is just a link to edit the dashboard in Metabase for your convenience. */}
<a href={editLink} target="_blank">
Edit this dashboard
{isDashboard && (
<select
className="dashboard-select"
onChange={(e) => setDashboardId(e.target.value)}
>
{DASHBOARDS.map((dashboard) => (
<option key={dashboard.id} value={dashboard.id}>
{dashboard.name}
</option>
))}
</select>
)}
<a href="#!" onClick={toggleCreateQuestion}>
{isCreateQuestion ? 'Back to dashboard' : 'Create Question'}
</a>
<ThemeSwitcher />
......@@ -58,7 +60,16 @@ export const AnalyticsDashboard = () => {
</div>
{/** Reload the dashboard when user changes with the key prop */}
<InteractiveDashboard dashboardId={dashboardId} withTitle withDownloads key={email} />
{isDashboard && (
<InteractiveDashboard
dashboardId={dashboardId}
withTitle
withDownloads
key={email}
/>
)}
{isCreateQuestion && <CreateQuestion />}
</div>
)
}
......
import ora from "ora";
import { createCollection } from "embedding-sdk/cli/utils/create-collection";
import type { CliStepMethod } from "../types/cli";
import type { DashboardInfo } from "../types/dashboard";
import { createModelFromTable } from "../utils/create-model-from-table";
......@@ -22,11 +24,20 @@ export const createModelsAndXrays: CliStepMethod = async state => {
try {
const models = [];
// Create the "Our models" collection to store the models.
// This helps us to allow access to models when sandboxing.
const modelCollectionId = await createCollection({
name: "Our models",
instanceUrl,
cookie,
});
// Create a model for each table
for (const table of chosenTables) {
const model = await createModelFromTable({
table,
databaseId,
collectionId: modelCollectionId,
cookie,
instanceUrl,
});
......@@ -52,7 +63,7 @@ export const createModelsAndXrays: CliStepMethod = async state => {
spinner.succeed();
return [{ type: "done" }, { ...state, dashboards }];
return [{ type: "done" }, { ...state, dashboards, modelCollectionId }];
} catch (error) {
spinner.fail();
......
import { SANDBOXED_GROUP_NAMES } from "../constants/config";
import { getNoTenantMessage } from "../constants/messages";
import type { CliStepMethod } from "../types/cli";
import { getCollectionPermissions } from "../utils/get-collection-permissions";
import { createCollection } from "../utils/create-collection";
import { getPermissionsForGroups } from "../utils/get-permission-groups";
import { getSandboxedCollectionPermissions } from "../utils/get-sandboxed-collection-permissions";
import { getTenancyIsolationSandboxes } from "../utils/get-tenancy-isolation-sandboxes";
import {
cliError,
......@@ -11,7 +12,12 @@ import {
import { sampleTenantIdsFromTables } from "../utils/sample-tenancy-column-values";
export const setupPermissions: CliStepMethod = async state => {
const { cookie = "", instanceUrl = "", tenancyColumnNames = {} } = state;
const {
cookie = "",
instanceUrl = "",
tenancyColumnNames = {},
modelCollectionId = 0,
} = state;
let res;
const collectionIds: number[] = [];
......@@ -19,21 +25,12 @@ export const setupPermissions: CliStepMethod = async state => {
// Create new customer collections sequentially
try {
for (const groupName of SANDBOXED_GROUP_NAMES) {
res = await fetch(`${instanceUrl}/api/collection`, {
method: "POST",
headers: { "content-type": "application/json", cookie },
body: JSON.stringify({
parent_id: null,
authority_level: null,
color: "#509EE3",
description: null,
name: groupName,
}),
const collectionId = await createCollection({
name: groupName,
instanceUrl,
cookie,
});
await propagateErrorResponse(res);
const { id: collectionId } = (await res.json()) as { id: number };
collectionIds.push(collectionId);
}
} catch (error) {
......@@ -107,7 +104,16 @@ export const setupPermissions: CliStepMethod = async state => {
}
try {
const groups = getCollectionPermissions({ groupIds, collectionIds });
const groups = getSandboxedCollectionPermissions({
groupIds,
collectionIds,
});
// Grant access to the "Our models" collection for all customer groups.
// This is so they can search and select their models in the entity picker.
for (const groupId of groupIds) {
groups[groupId][modelCollectionId] = "write";
}
// Update the permissions for sandboxed collections
res = await fetch(`${instanceUrl}/api/collection/graph`, {
......
......@@ -31,6 +31,9 @@ export type CliState = Partial<{
/** Sampled values of the tenancy columns from the selected tables (e.g. tenancy_id -> [1, 2, 3]) */
tenantIdsMap: Record<string, (string | number)[]>;
/** ID of the "Our models" collection */
modelCollectionId: number;
/** Directory where the Express.js mock server is saved to */
mockServerDir: string;
......
import { propagateErrorResponse } from "embedding-sdk/cli/utils/propagate-error-response";
interface Options {
name: string;
instanceUrl: string;
cookie: string;
}
export async function createCollection(options: Options) {
const { name, instanceUrl, cookie } = options;
const res = await fetch(`${instanceUrl}/api/collection`, {
method: "POST",
headers: { "content-type": "application/json", cookie },
body: JSON.stringify({
parent_id: null,
authority_level: null,
color: "#509EE3",
description: null,
name,
}),
});
await propagateErrorResponse(res);
const { id: collectionId } = (await res.json()) as { id: number };
return collectionId;
}
......@@ -5,13 +5,14 @@ import { propagateErrorResponse } from "./propagate-error-response";
interface Options {
table: Table;
databaseId: number;
collectionId: number | null;
cookie: string;
instanceUrl: string;
}
export async function createModelFromTable(options: Options) {
const { databaseId, table, instanceUrl, cookie = "" } = options;
const { databaseId, collectionId, table, instanceUrl, cookie = "" } = options;
const datasetQuery = {
type: "query",
......@@ -28,7 +29,7 @@ export async function createModelFromTable(options: Options) {
type: "model",
display: "table",
result_metadata: null,
collection_id: null,
collection_id: collectionId,
collection_position: 1,
visualization_settings: {},
dataset_query: datasetQuery,
......
......@@ -5,7 +5,7 @@ interface Options {
const ALL_USERS_GROUP_ID = 1;
export function getCollectionPermissions(options: Options) {
export function getSandboxedCollectionPermissions(options: Options) {
const { groupIds, collectionIds } = options;
const groups: Record<string, Record<string, string>> = {};
......
import { createMockField, createMockTable } from "metabase-types/api/mocks";
import { getCollectionPermissions } from "./get-collection-permissions";
import { getPermissionsForGroups } from "./get-permission-groups";
import { getSandboxedCollectionPermissions } from "./get-sandboxed-collection-permissions";
import { getTenancyIsolationSandboxes } from "./get-tenancy-isolation-sandboxes";
const getMockTable = (id: number) =>
......@@ -36,7 +36,7 @@ describe("permission graph generation for embedding cli", () => {
});
it("should generate valid permissions for collections", () => {
const groups = getCollectionPermissions({
const groups = getSandboxedCollectionPermissions({
groupIds: [3, 4, 5],
collectionIds: [9, 10, 11],
});
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment