Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sdlb_configuration_visualizer",
"version": "0.11.0",
"version": "0.11.1",
"private": true,
"type": "module",
"dependencies": {
Expand Down
56 changes: 32 additions & 24 deletions src/api/fetchAPI_rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Auth } from "aws-amplify";
import { fetchAPI } from "./fetchAPI";
import { ConfigData } from "../util/ConfigExplorer/ConfigData";
import { TstampEntry } from "../types";
import { compareFunc, dateFromNumber, sortIfArray } from "../util/helpers";

export class fetchAPI_rest implements fetchAPI {
url: string;
Expand All @@ -17,7 +18,8 @@ export class fetchAPI_rest implements fetchAPI {
private async fetch(url: string, init: Promise<RequestInit> = this.getRequestInfo()) {
const response = await fetch(url, await init);
if (!response.ok) {
const msg = (await response.json())['message'] + ` (${response.status})`;
const json = await response.json()
const msg = (json['message'] || json['detail']) + ` (${response.status})`;
throw new Error(msg);
}
return await response.json();
Expand Down Expand Up @@ -78,7 +80,7 @@ export class fetchAPI_rest implements fetchAPI {
};

getConfigVersions = async (tenant: string, repo: string, env: string): Promise<string[] | undefined> => {
return this.fetch(`${this.url}/versions?tenant=${tenant}&repo=${repo}&env=${env}`)
return this.fetch(`${this.url}/versions?tenant=${tenant}&repo=${repo}&env=${env}`).then(x => sortIfArray(x).reverse())
}

getDescription = async (
Expand All @@ -90,12 +92,9 @@ export class fetchAPI_rest implements fetchAPI {
version: string | undefined,
) => {
const filename = `${elementType}/${elementName}.md`;
const response = await this.getDescriptionFile(filename, tenant, repo, env, version);
const responseBody = await response.json();
if (!response.ok) {
throw new Error(responseBody["detail"]);
}
return await this.resolveDescriptionImageUrls(responseBody.content, tenant, repo, env, version);
const description = await this.getDescriptionFile(filename, tenant, repo, env, version);
if (description) return this.resolveDescriptionImageUrls((await description!.json()).content, tenant, repo, env, version);
else return Promise.resolve(undefined);
};

/**
Expand Down Expand Up @@ -130,12 +129,11 @@ export class fetchAPI_rest implements fetchAPI {
// Last capture group: ")"
const filename = match[2];
const promise = this.getDescriptionFile(filename, tenant, repo, env, version).then((response) => {
if (!response.ok) {
return response.json().then((error) => {
throw new Error(error["detail"]);
});
if (response!.ok) {
return response!.blob().then((blob) => fileUrlMap.set(filename, URL.createObjectURL(blob)));
} else {
return Promise.resolve(undefined);
}
return response.blob().then((blob) => fileUrlMap.set(filename, URL.createObjectURL(blob)));
});

promises.push(promise);
Expand All @@ -155,19 +153,29 @@ export class fetchAPI_rest implements fetchAPI {
repo: string,
env: string,
version: string | undefined
): Promise<any> => {
return this.fetch(
): Promise<Response | undefined> => {
const response = await fetch(
`${this.url}/descriptions/${filename}?tenant=${tenant}&repo=${repo}&env=${env}&version=${version}`,
this.getRequestInfo("GET", { Accept: "image/*,*/*;q=0.8" })
await this.getRequestInfo("GET", { Accept: "image/*,*/*;q=0.8" })
);
if (response.status == 404) {
console.log(`getDescriptionFile ${filename} not found.`);
return Promise.resolve(undefined);
}
if (!response.ok) {
const json = await response.json()
const msg = (json['message'] || json['detail']) + ` (${response.status})`;
throw new Error(msg);
}
return response;
};

getTstampEntries = async (type: string, subtype: string, elementName: string, tenant: string, repo: string, env: string): Promise<TstampEntry[] | undefined> => {
return this.fetch(`${this.url}/dataobject/${subtype}/${elementName}/tstamps?tenant=${tenant}&repo=${repo}&env=${env}`)
.then((parsedJson) =>
parsedJson.map(
.then((parsedJson: []) =>
parsedJson.toSorted().reverse().map(
(ts: number) =>
({ key: `${elementName}.${subtype}.${ts}`, elementName: elementName, tstamp: new Date(ts) } as TstampEntry)
({ key: `${elementName}.${subtype}.${ts}`, elementName: elementName, ts, tstamp: dateFromNumber(ts) } as TstampEntry)
)
)
.catch((error) => {
Expand All @@ -178,7 +186,7 @@ export class fetchAPI_rest implements fetchAPI {

getSchema = async (schemaTstampEntry: TstampEntry | undefined, tenant: string, repo: string, env: string) => {
if (!schemaTstampEntry?.elementName || !schemaTstampEntry?.tstamp) return Promise.resolve(undefined);
return this.fetch(`${this.url}/dataobject/schema/${schemaTstampEntry!.elementName}?tenant=${tenant}&repo=${repo}&env=${env}&tstamp=${schemaTstampEntry.tstamp.getTime()}`)
return this.fetch(`${this.url}/dataobject/schema/${schemaTstampEntry!.elementName}?tenant=${tenant}&repo=${repo}&env=${env}&tstamp=${schemaTstampEntry.ts}`)
.catch((error) => {
console.log(error);
return undefined;
Expand All @@ -187,7 +195,7 @@ export class fetchAPI_rest implements fetchAPI {

getStats = async (statsTstampEntry: TstampEntry | undefined, tenant: string, repo: string, env: string) => {
if (!statsTstampEntry?.elementName || !statsTstampEntry?.tstamp) return Promise.resolve(undefined);
return this.fetch(`${this.url}/dataobject/stats/${statsTstampEntry!.elementName}?tenant=${tenant}&repo=${repo}&env=${env}&tstamp=${statsTstampEntry.tstamp.getTime()}`)
return this.fetch(`${this.url}/dataobject/stats/${statsTstampEntry!.elementName}?tenant=${tenant}&repo=${repo}&env=${env}&tstamp=${statsTstampEntry.ts}`)
.then((parsedJson) => parsedJson.stats)
.catch((error) => {
console.log(error);
Expand Down Expand Up @@ -215,15 +223,15 @@ export class fetchAPI_rest implements fetchAPI {
};

getTenants = async () => {
return this.fetch(`${this.url}/tenants`);
return this.fetch(`${this.url}/tenants`).then(x => sortIfArray(x));
}

getRepos = async (tenant: string) => {
return this.fetch(`${this.url}/repo?tenant=${tenant}`)
return this.fetch(`${this.url}/repo?tenant=${tenant}`).then(x => sortIfArray(x))
}

getEnvs = async (tenant: string, repo: string) => {
return this.fetch(`${this.url}/envs?tenant=${tenant}&repo=${repo}`)
return this.fetch(`${this.url}/envs?tenant=${tenant}&repo=${repo}`).then(x => sortIfArray(x))
}

getLicenses = async (tenant: string): Promise<any[]> => {
Expand Down
6 changes: 4 additions & 2 deletions src/components/ConfigExplorer/ElementDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export default function ElementDetails(props: {
elementType: elementType as string
}));

const hasSchema = schemaEntries && schemaEntries.length > 0

return (
<>
<Sheet sx={{ flex: 1, minWidth: '500px', height: '100%', display: 'flex', flexDirection: 'column', p: '1rem 0rem 1rem 0.5rem' }}>
Expand All @@ -90,9 +92,9 @@ export default function ElementDetails(props: {
<Tab value="description" disabled={!description}>Description</Tab>
</span>
</Tooltip>
<Tooltip arrow variant="soft" title={(schemaEntriesLoading ? "Loading" : (!schemaEntries ? getMissingSchemaFileCmp(elementType!, elementName!) : null))} enterDelay={500} enterNextDelay={500} placement='bottom-start'>
<Tooltip arrow variant="soft" title={(schemaEntriesLoading ? "Loading" : (!hasSchema ? getMissingSchemaFileCmp(elementType!, elementName!) : null))} enterDelay={500} enterNextDelay={500} placement='bottom-start'>
<span>
{elementType === "dataObjects" && <Tab value="schema" disabled={!schemaEntries}>Schema</Tab>}
{elementType === "dataObjects" && <Tab value="schema" disabled={!hasSchema}>Schema</Tab>}
</span>
</Tooltip>
</TabList>
Expand Down
63 changes: 51 additions & 12 deletions src/layouts/RootLayoutSpinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { useFetchEnvs, useFetchRepos, useFetchTenants } from "../hooks/useFetchD
import { useManifest } from "../hooks/useManifest";
import { useWorkspace } from "../hooks/useWorkspace";
import PageHeader from "./PageHeader";
import MarkdownComponent from "../components/ConfigExplorer/MarkdownComponent";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";


function WorkspaceSpinner({ children }) {
Expand All @@ -23,19 +26,55 @@ function WorkspaceSpinner({ children }) {

export function WorkspaceEmpty() {
const { tenant } = useWorkspace();
const manifest = useManifest();
const url = manifest.data?.backendConfig.split(";")[1];
const clientId = manifest.data?.auth["aws_user_pools_web_client_id"];

const markdown = `
**Tenant '${tenant}' seems still empty.**

Use the following steps based on our [getting-started](https://github.com/smart-data-lake/getting-started) guide to upload an SDLB configuration and runtime informations,
or select another tenant in the upper right corner.

#### 1. Add global.uiBackend configuration
Adapt _repo_ (repository name) and _env_ (environment name) to your needs and use [Secret Providers](https://smartdatalake.ch/docs/reference/hoconSecrets) to hide the _password_ of your UI user.

global {

...

uiBackend {
baseUrl = "${url}"
tenant = ${tenant}
repo = getting-started
env = dev
authMode {
type = AWSUserPwdAuthMode
region = eu-central-1
userPool = sdlb-ui
clientId = ${clientId}
useIdToken = true
user = "###ENV#user###"
password = "###ENV#pwd###"
}
}
}

#### 2. Export SDLB configuration to the UI
Use the _exportConfigSchemaStats.sh_ script from the [getting-started](https://github.com/smart-data-lake/getting-started) folder to export the SDLB configuration to the UI.
This will initialize and populate the _repository_ and _environment_ in the UI, so it is no longer empty.

#### 3. Run SDLB Job
Use the _startJob.sh_ script from the [getting-started](https://github.com/smart-data-lake/getting-started) folder to run an SDLB Job.
The SDLB Job will automatically pickup the global.uiBackend configuration and push its runtime information to the UI.

`


return <>
<PageHeader title="Welcome to SDLB UI" noBack={true} />
<Box sx={{ display: "flex", flexDirection: "column", p: 1, }}>
<span>
Tenant '{tenant}' seems still empty.<br/>
Select another tenant in the upper right corner,<br/>
or use the following steps to upload an SDLB configuration and SDLB run states:
<ol>
<li>configure global.uiBackend TODO</li>
<li>export configuration using TODO</li>
<li>run SDLB</li>
</ol>
</span>
<PageHeader title="Welcome to SDLB UI"/>
<Box sx={{ display: "flex", flexDirection: "column", p: 2, backgroundColor: "white", overflow: "auto" }}>
<ReactMarkdown className='markdown-body' children={markdown} remarkPlugins={[remarkGfm]} />
</Box>
</>;
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export class Row implements MetaDataBaseObject {
export interface TstampEntry {
key: string;
elementName: string;
ts: number;
tstamp: Date;
}

Expand Down
16 changes: 13 additions & 3 deletions src/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ export function onlyUnique(value, index, array) {
* This can be used for sorting arrays.
* usage: arr.sort(compareFunc("x"))
*/
export function compareFunc(attr: any) {
export function compareFunc(attr: any, reverse: boolean = false) {
return (a, b) => {
if (a[attr] === b[attr]) return 0;
else return a[attr] > b[attr] || a[attr] === undefined ? 1 : -1;
else return (a[attr] > b[attr] || a[attr] === undefined ? 1 : -1) * (reverse ? -1 : 1);
}
}

Expand Down Expand Up @@ -220,4 +220,14 @@ export function setAttributeFromPath ( entity, path, value) {
}
}
});
};
};

export function dateFromNumber( ts: number ) {
// convert number to date handling seconds or milliseconds format.
return new Date((ts<9000000000 ? ts*1000 : ts))
}

export function sortIfArray<TInput>(v: TInput) {
if (Array.isArray(v)) return (v as []).sort() as TInput;
else v;
}
Loading