Skip to content

Commit

Permalink
Add a provider for h5core example app
Browse files Browse the repository at this point in the history
  • Loading branch information
loichuder committed Jun 3, 2021
1 parent 478f680 commit 69f480b
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 176 deletions.
4 changes: 4 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ REACT_APP_JLAB_FALLBACK_FILEPATH="water_224.h5"
# Use dev version of the API
REACT_APP_JLAB_DEV_ENABLED=

# h5core backend parameters
REACT_APP_H5CORE_URL=
REACT_APP_H5CORE_FALLBACK_FILEPATH="water_224.h5"

# Rendering performance profiling (local development only)
REACT_APP_PROFILING_ENABLED=false

Expand Down
4 changes: 4 additions & 0 deletions src/demo-app/DemoApp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import App from '../h5web/App';
import H5CoreApp from './H5CoreApp';
import HsdsApp from './HsdsApp';
import JupyterApp from './JupyterApp';

Expand All @@ -26,6 +27,9 @@ function DemoApp() {
<Route path="/hsds">
<HsdsApp />
</Route>
<Route path="/h5core">
<H5CoreApp />
</Route>
<Route exact path="/">
<JupyterApp />
</Route>
Expand Down
19 changes: 19 additions & 0 deletions src/demo-app/H5CoreApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useLocation } from 'react-router-dom';
import { App } from '../packages/app';
import H5CoreProvider from '../h5web/providers/h5core/H5CoreProvider';

const URL = process.env.REACT_APP_H5CORE_URL || '';
const FILEPATH = process.env.REACT_APP_H5CORE_FALLBACK_FILEPATH || '';

function H5CoreApp() {
const query = new URLSearchParams(useLocation().search);
const filepath = query.get('file');

return (
<H5CoreProvider url={URL} filepath={filepath || FILEPATH}>
<App />
</H5CoreProvider>
);
}

export default H5CoreApp;
18 changes: 18 additions & 0 deletions src/h5web/providers/h5core/H5CoreProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ReactNode, useMemo } from 'react';
import { H5CoreApi } from './h5core-api';
import Provider from '../Provider';

interface Props {
url: string;
filepath: string;
children: ReactNode;
}

function H5CoreProvider(props: Props) {
const { url, filepath, children } = props;
const api = useMemo(() => new H5CoreApi(url, filepath), [filepath, url]);

return <Provider api={api}>{children}</Provider>;
}

export default H5CoreProvider;
150 changes: 150 additions & 0 deletions src/h5web/providers/h5core/h5core-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {
ValueRequestParams,
Attribute,
Entity,
Dataset,
EntityKind,
Group,
} from '../models';
import { isDatasetResponse, isGroupResponse } from './utils';
import { assertDataset } from '../../guards';
import { ProviderApi } from '../api';
import type {
H5CoreDataResponse,
H5CoreAttrResponse,
H5CoreMetaResponse,
H5CoreDatasetMetaReponse,
H5CoreGroupMetaResponse,
} from './models';
import { convertDtype, flattenValue } from '../utils';

export class H5CoreApi extends ProviderApi {
/* API compatible with h5core@bccdb1f77f568d6f7d3519a07c9b3bef7e9ecc20 */
public constructor(url: string, filepath: string) {
super(filepath, { baseURL: url });
}

public async getEntity(path: string): Promise<Entity> {
return this.processEntity(path, 1);
}

public async getValue(params: ValueRequestParams): Promise<unknown> {
const { path, selection } = params;
const [value, entity] = await Promise.all([
this.fetchData(params),
this.getEntity(path),
]);

assertDataset(entity);

return flattenValue(value, entity, selection);
}

private async fetchAttributes(path: string): Promise<H5CoreAttrResponse> {
const { data } = await this.client.get<H5CoreAttrResponse>(
`/attr/${this.filepath}?path=${path}`
);
return data;
}

private async fetchData(
params: ValueRequestParams
): Promise<H5CoreDataResponse> {
const { path, selection = '' } = params;
const { data } = await this.cancellableFetchValue<H5CoreDataResponse>(
`/data/${this.filepath}?path=${path}${
selection && `&selection=${selection}`
}`,
params
);
return data;
}

private async fetchMetadata(path: string): Promise<H5CoreMetaResponse> {
const { data } = await this.client.get<H5CoreMetaResponse>(
`/meta/${this.filepath}?path=${path}`
);
return data;
}

private async processEntity(
path: string,
depth: number
): Promise<Group | Dataset | Entity> {
const response = await this.fetchMetadata(path);

if (isGroupResponse(response)) {
const { name, type, children } = response;

if (depth === 0) {
return {
attributes: await this.processAttributes(path, response),
path,
name,
kind: type,
children: [],
};
}

return {
attributes: await this.processAttributes(path, response),
path,
name,
kind: type,
children: await Promise.all(
children.map((content) =>
this.processEntity(
`${path !== '/' ? path : ''}/${content.name}`,
depth - 1
)
)
),
};
}

if (isDatasetResponse(response)) {
// `dtype` is the type of the data contained in the dataset
const { name, type: kind, dtype, shape } = response;

return {
attributes: await this.processAttributes(path, response),
path,
name,
kind,
shape,
type: convertDtype(dtype),
rawType: dtype,
};
}

// Treat 'other' entities as unresolved
return {
attributes: [],
name: response.name,
path,
kind: EntityKind.Unresolved,
};
}

private async processAttributes(
path: string,
response: H5CoreDatasetMetaReponse | H5CoreGroupMetaResponse
): Promise<Attribute[]> {
const { attributes: attrsMetadata } = response;

if (attrsMetadata.length === 0) {
return [];
}

const attrValues = await this.fetchAttributes(path);
return attrsMetadata.map<Attribute>((attrMetadata) => {
const { name, dtype, shape } = attrMetadata;
return {
name,
shape,
type: convertDtype(dtype),
value: attrValues[name],
};
});
}
}
45 changes: 45 additions & 0 deletions src/h5web/providers/h5core/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { EntityKind } from '../models';

export type H5CoreAttrResponse = Record<string, unknown>;

export type H5CoreDataResponse = unknown;

export interface H5CoreMetaResponse {
name: string;
type:
| EntityKind.Dataset
| EntityKind.Group
| 'externalLink'
| 'softLink'
| 'other';
}

export interface H5CoreSoftLinkMetaResponse extends H5CoreMetaResponse {
target_path: string;
type: 'softLink';
}

export interface H5CoreExternalLinkMetaResponse extends H5CoreMetaResponse {
target_file: string;
target_path: string;
type: 'externalLink';
}

interface H5CoreAttrMetadata {
dtype: string;
name: string;
shape: number[];
}

export interface H5CoreDatasetMetaReponse extends H5CoreMetaResponse {
attributes: H5CoreAttrMetadata[];
dtype: string;
shape: number[];
type: EntityKind.Dataset;
}

export interface H5CoreGroupMetaResponse extends H5CoreMetaResponse {
attributes: H5CoreAttrMetadata[];
children: H5CoreMetaResponse[];
type: EntityKind.Group;
}
17 changes: 17 additions & 0 deletions src/h5web/providers/h5core/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { EntityKind } from '../models';
import type {
H5CoreMetaResponse,
H5CoreDatasetMetaReponse,
H5CoreGroupMetaResponse,
} from './models';

export function isGroupResponse(
response: H5CoreMetaResponse
): response is H5CoreGroupMetaResponse {
return response.type === EntityKind.Group;
}
export function isDatasetResponse(
response: H5CoreMetaResponse
): response is H5CoreDatasetMetaReponse {
return response.type === EntityKind.Dataset;
}
16 changes: 3 additions & 13 deletions src/h5web/providers/jupyter/jupyter-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
assertGroupContent,
isDatasetResponse,
isGroupResponse,
convertDtype,
parseComplex,
} from './utils';
import type {
Expand All @@ -24,7 +23,8 @@ import type {
JupyterMetaResponse,
} from './models';
import { makeStrAttr } from '../mock/metadata-utils';
import { assertDataset, hasArrayShape, hasComplexType } from '../../guards';
import { assertDataset, hasComplexType } from '../../guards';
import { convertDtype, flattenValue } from '../utils';

export class JupyterStableApi extends ProviderApi {
/* API compatible with jupyterlab_hdf v0.5.1 */
Expand All @@ -44,7 +44,7 @@ export class JupyterStableApi extends ProviderApi {
]);

assertDataset(entity);
const flatValue = this.flattenValue(value, entity, selection);
const flatValue = flattenValue(value, entity, selection);

if (hasComplexType(entity)) {
return parseComplex(flatValue as JupyterComplex);
Expand Down Expand Up @@ -163,14 +163,4 @@ export class JupyterStableApi extends ProviderApi {

return contents;
}

protected flattenValue(value: unknown, entity: Dataset, selection?: string) {
if (!hasArrayShape(entity)) {
return value;
}

const slicedDims = selection?.split(',').filter((s) => s.includes(':'));
const dims = slicedDims || entity.shape;
return (value as unknown[]).flat(dims.length - 1);
}
}
5 changes: 3 additions & 2 deletions src/h5web/providers/jupyter/jupyter-dev-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import type {
JupyterMetaGroupResponse,
JupyterMetaResponse,
} from './models';
import { assertGroupContent, convertDtype } from './utils';
import { assertGroupContent } from './utils';
import { assertDataset } from '../../guards';
import { convertDtype, flattenValue } from '../utils';

interface DevJupyterAttrMeta {
name: string;
Expand Down Expand Up @@ -41,7 +42,7 @@ export class JupyterDevApi extends JupyterStableApi {

assertDataset(entity);

return this.flattenValue(value, entity, selection);
return flattenValue(value, entity, selection);
}

protected async fetchMetadata(path: string): Promise<DevJupyterMetaResponse> {
Expand Down
Loading

0 comments on commit 69f480b

Please sign in to comment.