Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Forman xxx xcube intergr 2 #147

Merged
merged 21 commits into from
May 3, 2021
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
10 changes: 5 additions & 5 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ REACT_APP_KEYCLOAK_CLIENT_ID=cate-webui
# REACT_APP_MAX_NUM_USERS=50

# TODO: document me
# REACT_APP_CATEHUB_ENDPOINT=https://stage.catehub.brockmann-consult.de
REACT_APP_CATEHUB_ENDPOINT=http://localhost:8000
REACT_APP_CATEHUB_WEBAPI_MANAG_PATH=/user/{username}/webapi
REACT_APP_CATEHUB_WEBAPI_CLOSE_PATH=/user/{username}/webapi/shutdown
REACT_APP_CATEHUB_WEBAPI_COUNT_PATH=/webapi/count
REACT_APP_CATEHUB_ENDPOINT=https://stage.catehub.climate.esa.int/api/v2
#REACT_APP_CATEHUB_ENDPOINT=http://localhost:8080/api/v2
REACT_APP_CATEHUB_WEBAPI_MANAG_PATH=/users/{username}/webapis
REACT_APP_CATEHUB_WEBAPI_CLOSE_PATH=/users/{username}/webapis
REACT_APP_CATEHUB_WEBAPI_COUNT_PATH=/webapis
27 changes: 27 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
### Changes 3.0 (in development)

* Cate App 3.0 now requires the Web API service of the `cate 3.0+`
Python package.

* In order to keep alive the connection to the Web API service,
Cate App now sends a keepalive signal every 2.5 seconds. (#150)

* Optimisations in the DATA SOURCES panel (that have been enabled by
using [xcube](https://xcube.readthedocs.io/) in the backend):
- Initalising the "CCI Open Data Portal" data store
is now accellerated by a magnitude.
- Local caching of remote data sources when opening datasets
is now much faster and more reliable.
- Added new experimental store "CCI Zarr Store" that offers
selected CCI datasets that have been converted to Zarr format
and are read from JASMIN object storage.
- Ability to add more data stores has been greatly improved.

* We now obtain Cate Hub's status information from a dedicated GitHub
repository [cate-status](https://github.com/CCI-Tools/cate-status).

* Adapted to changed cate-hub API. (An API response no longer has
`status` and `result` properties, instead a response _is_ the result
and the response status is represented by the HTTP response code.)


### Changes 2.2.3

* Fixed a problem that prevented using Matomo Analytics service.
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM node:stretch-slim as build-deps

LABEL maintainer="[email protected]"
LABEL name="Cate App"
LABEL version="2.2.3"
LABEL version="3.0.0-dev.0"

RUN apt-get -y update && apt-get install -y git apt-utils wget vim

Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 2.2.3-{build}
version: 3.0.0-dev.0-{build}
image: Ubuntu
stack: node 12
install:
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cate-app",
"version": "2.2.3",
"version": "3.0.0-dev.0",
"private": true,
"dependencies": {
"@blueprintjs/core": "^3.30.1",
Expand Down Expand Up @@ -50,6 +50,7 @@
"@types/dom4": "^1.5.20",
"@types/geojson": "^1.0.6",
"@types/jest": "^24.0.0",
"@types/json-schema": "^7.0.7",
"@types/mocha": "^2.2.41",
"@types/node": "^12.0.0",
"@types/oboe": "^2.0.28",
Expand Down
12 changes: 7 additions & 5 deletions src/renderer/actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ describe('Actions', () => {
]);
});

it('updateDataSourceTemporalCoverage', () => {
it('updateDataSourceMetaInfo', () => {
dispatch(actions.updateDataStores(
[
{id: 'local-1'},
Expand All @@ -133,17 +133,19 @@ describe('Actions', () => {
{id: 'fileset-1'},
{id: 'fileset-2'}
] as any));
dispatch(actions.updateDataSourceTemporalCoverage('local-2',
'fileset-1',
['2010-01-01', '2014-12-30']));
dispatch(actions.updateDataSourceMetaInfo('local-2',
'fileset-1',
{'data_id': 'x', 'type_specifier': 'y'},
'ok'));
expect(getState().data.dataStores).to.deep.equal(
[
{id: 'local-1'},
{
id: 'local-2', dataSources: [
{
id: 'fileset-1',
temporalCoverage: ['2010-01-01', '2014-12-30']
metaInfo: {'data_id': 'x', 'type_specifier': 'y'},
metaInfoStatus: 'ok',
},
{id: 'fileset-2'}
]
Expand Down
91 changes: 68 additions & 23 deletions src/renderer/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ import * as selectors from './selectors';
import {
BackendConfigState,
ColorMapCategoryState,
ControlState,
ControlState, DatasetDescriptor,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ControlState, DatasetDescriptor,
ControlState,
DatasetDescriptor,

DataSourceState,
DataStoreState,
GeographicPosition,
GeographicPosition,
HubStatus,
ImageStatisticsState,
LayerState,
MessageState,
Expand All @@ -67,6 +68,7 @@ import {
} from './state';
import {
AUTO_LAYER_ID,
findDataSource,
findResourceByName,
genSimpleId,
getCsvUrl,
Expand Down Expand Up @@ -137,6 +139,7 @@ export const SET_WEBAPI_STATUS = 'SET_WEBAPI_STATUS';
export const SET_WEBAPI_CLIENT = 'SET_WEBAPI_CLIENT';
export const SET_WEBAPI_SERVICE_URL = 'SET_WEBAPI_SERVICE_URL';
export const SET_WEBAPI_SERVICE_INFO = 'SET_WEBAPI_SERVICE_INFO';
export const UPDATE_HUB_STATUS = 'UPDATE_HUB_STATUS';
export const UPDATE_DIALOG_STATE = 'UPDATE_DIALOG_STATE';
export const UPDATE_TASK_STATE = 'UPDATE_TASK_STATE';
export const REMOVE_TASK_STATE = 'REMOVE_TASK_STATE';
Expand Down Expand Up @@ -312,29 +315,47 @@ export function connectWebAPIService(webAPIServiceURL: string): ThunkAction {

const webAPIClient = newWebAPIClient(selectors.apiWebSocketsUrlSelector(getState()));

const formatMessage = (message: string, event: any): string => {
if (event.message) {
return `${message} (${event.message})`;
} else {
return message;
}
};

/**
* Called to inform backend we are still alive.
* Hopefully avoids closing WebSocket connection.
*/
const keepAlive = () => {
if (webAPIClient.isOpen) {
console.debug("calling keep_alive()");
webAPIClient.call('keep_alive', [])
}
};

let keepAliveTimer = null;

webAPIClient.onOpen = () => {
dispatch(setWebAPIClient(webAPIClient));
dispatch(loadBackendConfig());
dispatch(loadColorMaps());
dispatch(loadPreferences());
dispatch(loadDataStores());
dispatch(loadOperations());
};

const formatMessage = (message: string, event: any): string => {
if (event.message) {
return `${message} (${event.message})`;
} else {
return message;
}
keepAliveTimer = setInterval(keepAlive, 2500);
};

webAPIClient.onClose = (event) => {
if (keepAliveTimer !== null) {
clearInterval(keepAliveTimer);
}
const webAPIStatus = getState().communication.webAPIStatus;
if (webAPIStatus === 'shuttingDown' || webAPIStatus === 'loggingOut') {
// When we are logging off, the webAPIClient is expected to close.
return;
}
// When we end up here, the connection closed unintentionally.
console.error('webAPIClient.onClose:', event);
dispatch(setWebAPIStatus('closed'));
showToast({type: 'notification', text: formatMessage('Connection to Cate service closed', event)});
Expand All @@ -353,6 +374,10 @@ export function connectWebAPIService(webAPIServiceURL: string): ThunkAction {
};
}

export function updateHubStatus(hubStatus: HubStatus): Action {
return {type: UPDATE_HUB_STATUS, payload: hubStatus};
}

export function updateInitialState(initialState: Object): Action {
return {type: UPDATE_INITIAL_STATE, payload: initialState};
}
Expand Down Expand Up @@ -885,7 +910,7 @@ export function setSelectedPlacemarkId(selectedPlacemarkId: string | null): Acti

export const UPDATE_DATA_STORES = 'UPDATE_DATA_STORES';
export const UPDATE_DATA_SOURCES = 'UPDATE_DATA_SOURCES';
export const UPDATE_DATA_SOURCE_TEMPORAL_COVERAGE = 'UPDATE_DATA_SOURCE_TEMPORAL_COVERAGE';
export const UPDATE_DATA_SOURCE_META_INFO = 'UPDATE_DATA_SOURCE_META_INFO';

/**
* Asynchronously load the available Cate data stores.
Expand Down Expand Up @@ -987,33 +1012,54 @@ export function setSelectedDataStoreIdImpl(selectedDataStoreId: string | null) {
return updateSessionState({selectedDataStoreId});
}

export function setSelectedDataSourceId(selectedDataSourceId: string | null) {
return updateSessionState({selectedDataSourceId});
export function setSelectedDataSourceId(selectedDataSourceId: string): ThunkAction {
return (dispatch: Dispatch, getState: GetState) => {
dispatch(updateSessionState({selectedDataSourceId}));
const dataStoreId = getState().session.selectedDataStoreId;
if (dataStoreId && selectedDataSourceId) {
dispatch(loadDataSourceMetaInfo(dataStoreId, selectedDataSourceId));
}
}
}

export function setDataSourceFilterExpr(dataSourceFilterExpr: string) {
return updateSessionState({dataSourceFilterExpr});
}

export function loadTemporalCoverage(dataStoreId: string, dataSourceId: string): ThunkAction {
export function loadDataSourceMetaInfo(dataStoreId: string, dataSourceId: string): ThunkAction {
return (dispatch: Dispatch, getState: GetState) => {
const dataStores = getState().data.dataStores;
if (!dataStores) {
return;
}
const dataSource = findDataSource(dataStores, dataStoreId, dataSourceId);
if (!dataSource || dataSource.metaInfoStatus !== 'init') {
return;
}

dispatch(updateDataSourceMetaInfo(dataStoreId, dataSourceId, undefined, 'loading'));

function call(onProgress) {
return selectors.datasetAPISelector(getState()).getDataSourceTemporalCoverage(dataStoreId, dataSourceId, onProgress);
return selectors.datasetAPISelector(getState()).getDataSourceMetaInfo(dataStoreId, dataSourceId, onProgress);
}

function action(temporalCoverage) {
dispatch(updateDataSourceTemporalCoverage(dataStoreId, dataSourceId, temporalCoverage));
function action(metaInfo: DatasetDescriptor) {
dispatch(updateDataSourceMetaInfo(dataStoreId, dataSourceId, metaInfo, 'ok'));
}

callAPI({title: `Load temporal coverage for ${dataSourceId}`, dispatch, call, action});
function planB() {
dispatch(updateDataSourceMetaInfo(dataStoreId, dataSourceId, undefined, 'error'));
}

callAPI({title: `Loading meta data for ${dataSourceId}`, dispatch, call, action, planB});
};
}

export function updateDataSourceTemporalCoverage(dataStoreId: string,
dataSourceId: string,
temporalCoverage: [string, string] | null): Action {
return {type: UPDATE_DATA_SOURCE_TEMPORAL_COVERAGE, payload: {dataStoreId, dataSourceId, temporalCoverage}};
export function updateDataSourceMetaInfo(dataStoreId: string,
dataSourceId: string,
metaInfo: DatasetDescriptor | undefined,
metaInfoStatus: 'loading' | 'ok' | 'error' = 'ok'): Action {
return {type: UPDATE_DATA_SOURCE_META_INFO, payload: {dataStoreId, dataSourceId, metaInfo, metaInfoStatus}};
}

export function openDataset(dataSourceId: string, args: any, updateLocalDataSources: boolean): ThunkAction {
Expand Down Expand Up @@ -2712,4 +2758,3 @@ function readDroppedFile(file: File, dispatch: Dispatch) {
console.warn('Dropped file of unrecognized type: ', file.name);
}
}

32 changes: 22 additions & 10 deletions src/renderer/components/DataSourceDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,24 @@ const DataSourceDetails: React.FC<IDataSourceDetailsProps> = ({dataSource}) => {
if (!dataSource) {
return null;
}

let metaInfoKeys;
if (dataSource.metaInfo) {
metaInfoKeys = Object.keys(dataSource.metaInfo).filter(key => key !== 'variables');
}
let variables;
if (dataSource.metaInfo.variables) {
variables = dataSource.metaInfo.variables;

const metaInfo = dataSource.metaInfo;

if (metaInfo) {
metaInfoKeys = Object.keys(metaInfo).filter(key => key !== 'variables');
if (metaInfo.variables) {
variables = metaInfo.variables;
}
}

const details: DetailPart[] = [
renderAbstract(dataSource),
renderVariablesTable(variables),
renderMetaInfoTable(dataSource.metaInfo, metaInfoKeys),
renderMetaInfoLicences(dataSource.metaInfo),
renderMetaInfoTable(metaInfo, metaInfoKeys),
renderMetaInfoLicences(metaInfo),
];

return (
Expand Down Expand Up @@ -84,18 +88,26 @@ function renderAbstract(dataSource: DataSourceState): DetailPart {
</div>
);
}
if (dataSource.temporalCoverage) {
if (dataSource.metaInfo && dataSource.metaInfo.time_range) {
let [start, end] = dataSource.metaInfo.time_range;
if (!start && !end) {
start = end = 'unknown';
} else if (!start) {
start = 'unknown';
} else if (!end) {
end = 'today';
}
temporalCoverage = (
<div><h5>Temporal coverage</h5>
<table>
<tbody>
<tr>
<td>Start</td>
<td className="user-selectable">{dataSource.temporalCoverage[0]}</td>
<td className="user-selectable">{start}</td>
</tr>
<tr>
<td>End</td>
<td className="user-selectable">{dataSource.temporalCoverage[1]}</td>
<td className="user-selectable">{end}</td>
</tr>
</tbody>
</table>
Expand Down
Loading