Skip to content
This repository was archived by the owner on Feb 26, 2025. It is now read-only.

Commit

Permalink
Merge pull request #592 from BlueBrain/feature/v1.3.2-release
Browse files Browse the repository at this point in the history
Feature/v1.3.2 release
  • Loading branch information
kenjinp authored Mar 6, 2020
2 parents f96f1b3 + a77ee5e commit 4cfe378
Show file tree
Hide file tree
Showing 23 changed files with 391 additions and 263 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ docker build . --tag=nexus-web
- `GTAG`: The Google Analytics Identifier. GA won't be present unless an ID is specified.
- `SENTRY_DSN`: The sentry URL Nexus Web needs to report errors to. Default is undefined.

The following concern Plugins. [See how to manage plugin deployments](./docs/plugins.md)

- `PLUGINS_MANIFEST_PATH`: Remote end point where plugins and manifest can be found. for example, `https://bbp-nexus.epfl.ch/plugins`
- `PLUGINS_CONFIG_PATH`: A full file path where a plugins configuration can be found.

## Deployment

You can find out how to deploy a build [in the wiki](https://github.com/BlueBrain/nexus-web/wiki/Deploying-Your-Nexus-Web-Instance)
Expand Down
48 changes: 48 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Plugins and Deployment

## Plugin Manifest

The plugin manifest should be available at the same remote endpoint as the plugins. This is so Nexus can find the plugins and apply them dynamically.

The plugin manifest is a json object with keys that correspond to the plugin name with a value that corresponds to a descriptive payload of where to find the manifest, as well as some information about it's development. It's similar to a package.json file.

```{
"circuit": {
"modulePath": "circuit.f7755e13c8b410efdf02.js",
"name": "Circuit",
"description": "",
"version": "",
"tags": [],
"author": "",
"license": ""
}
}
```

## Plugin Config

The plugin config should be available as an adjacent file next to your running Nexus Web instance. It's a json file that describes which resource should be represented by each plugin.

### Matching all resources

The following will show `nexus-plugin-test` for *every* resource inside Nexus Web.

```
{
"nexus-plugin-test" : {}
}
```

### Matching a resource with a specific type and shape
The following will show `nexus-plugin-test` for any resource of type `File` but only if they have a `distribution.encodingFormat` property that's `application/swc`

```
{
"nexus-plugin-test" : {
"@type": "File",
"distribution:" {
"encodingFormat": "application/swc"
}
}
}
```
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@bbp/react-nexus": "1.3.1",
"@sentry/browser": "5.9.1",
"@types/cytoscape": "^3.8.5",
"@types/lodash": "^4.14.149",
"antd": "^3.19.5",
"codemirror": "^5.44.0",
"connected-react-router": "^6.3.2",
Expand All @@ -33,6 +34,7 @@
"express-prom-bundle": "^5.0.2",
"history": "^4.7.2",
"jwt-decode": "^2.2.0",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"morgan": "^1.9.1",
"object-hash": "^1.3.1",
Expand Down
1 change: 1 addition & 0 deletions src/server/config/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const pluginsMap = require('../../../plugin.config.json');
23 changes: 20 additions & 3 deletions src/server/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { resolve, join } from 'path';
import { readdirSync } from 'fs';
import { readFileSync } from 'fs';
import * as express from 'express';
import * as cookieParser from 'cookie-parser';
import * as morgan from 'morgan';
Expand All @@ -17,7 +17,22 @@ const app: express.Express = express();
const rawBase: string = process.env.BASE_PATH || '';

// to develop plugins locally, change PLUGINS_PATH to '/public/plugins'
const pluginsPath = process.env.PLUGINS_PATH || '/plugins';
const pluginsManifestPath =
process.env.PLUGINS_MANIFEST_PATH || '/public/plugins';

const pluginsConfigPath =
process.env.PLUGINS_CONFIG_PATH ||
join(__dirname, '/public/plugins/plugins.config.json');

const getpluginsConfig = () => {
let pluginsConfig;
try {
pluginsConfig = JSON.parse(readFileSync(pluginsConfigPath).toString());
} catch (e) {
console.error(e);
}
return pluginsConfig || {};
};

// remove trailing slash
const base: string = rawBase.replace(/\/$/, '');
Expand Down Expand Up @@ -52,7 +67,9 @@ app.get('*', async (req: express.Request, res: express.Response) => {
const preloadedState: RootState = {
auth: {},
config: {
pluginsPath,
pluginsManifestPath,

pluginsMap: getpluginsConfig(),
apiEndpoint: process.env.API_ENDPOINT || '/',
basePath: base,
clientId: process.env.CLIENT_ID || 'nexus-web',
Expand Down
15 changes: 10 additions & 5 deletions src/shared/App.less
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,21 @@
.highShadow();
padding: 1em;
width: 100%;
max-width: 50%;
background-color: @background-color-subtle;
}
.graph-wrapper {
margin-left: 1em;
width: 100%;
}
}
}

.graph-wrapper-container {
width: 100%;
}

.graph-wrapper {
margin-left: 1em;
width: 100%;
height: 1000px;
}

.resource-details {
.ant-alert-warning {
margin: 1em 0;
Expand Down
25 changes: 4 additions & 21 deletions src/shared/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Modal } from 'antd';
import * as React from 'react';
import { Route, Switch, useLocation, useHistory } from 'react-router-dom';
import { Location } from 'history';
import routes from '../shared/routes';
import NotFound from './views/404';
import MainLayout from './layouts/MainLayout';
import ResourceViewContainer from './containers/ResourceViewContainer';

import './App.less';
import { Modal } from 'antd';
import ResourceViewContainer from './containers/ResourceViewContainer';
import StudioResourceView from './views/StudioResourceView';
import { Location } from 'history';

const App: React.FC = () => {
const location = useLocation();
Expand Down Expand Up @@ -45,31 +44,15 @@ const App: React.FC = () => {
render={routeProps => (
<Modal
visible={true}
footer={null}
onCancel={() => history.push(background.pathname, {})}
onOk={() => history.push(location.pathname, {})}
okText="Graph View"
className="modal-view"
width="inherit"
>
<ResourceViewContainer />
</Modal>
)}
/>,
<Route
key="studio-resource-modal"
path={'/studios/studio-resources/:resourceSelfUri'}
render={routeProps => (
<Modal
visible={true}
onCancel={() => history.push(background.pathname, {})}
footer={null}
className="modal-view -unconstrained"
width="inherit"
>
<StudioResourceView />
</Modal>
)}
/>,
]}
</MainLayout>
);
Expand Down
59 changes: 0 additions & 59 deletions src/shared/components/DashboardEditor/DashboardConfigEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,9 @@ const DashboardConfigEditorComponent: React.FunctionComponent<DashboardConfigEdi
form,
dashboard,
linkToSparqlQueryEditor,
availablePlugins,
}) => {
const { description, label, dataQuery, plugins = [] } = dashboard || {};
const { getFieldDecorator, getFieldsValue, validateFields } = form;
const [selectedPlugins, setSelectedPlugins] = React.useState<string[]>(
plugins
);
const { Panel } = Collapse;

const formatPluginSource = () => {
if (availablePlugins && availablePlugins.length) {
return availablePlugins.map(plugin => (
<Option key={plugin}>{plugin}</Option>
));
}
return [];
};

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
Expand All @@ -67,16 +53,11 @@ const DashboardConfigEditorComponent: React.FunctionComponent<DashboardConfigEdi
description,
label,
dataQuery,
plugins: selectedPlugins,
});
}
});
};

const handlePluginsChange = (nextTargetKeys: string[]) => {
setSelectedPlugins(nextTargetKeys);
};

return (
<Form onSubmit={handleSubmit}>
<Form.Item
Expand Down Expand Up @@ -145,46 +126,6 @@ const DashboardConfigEditorComponent: React.FunctionComponent<DashboardConfigEdi
],
})(<SparqlQueryFormInput />)}
</Form.Item>
<Form.Item>
{getFieldDecorator('plugins', {
rules: [
{
required: false,
},
],
})(
<Collapse>
<Panel
header={
<span>
Plugins{' '}
<Tooltip title="Which plugins should Studio load when viewing a resource.">
<Icon type="question-circle-o" />
</Tooltip>{' '}
Experimental{' | '}
<a
target="_blank"
href="https://github.com/BlueBrain/nexus-web/blob/master/docs/studio/Dashboards.md#plugins-experimental"
>
Read Docs
</a>
</span>
}
key="1"
>
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Please select one or more plugins"
defaultValue={selectedPlugins}
onChange={handlePluginsChange}
>
{formatPluginSource()}
</Select>
</Panel>
</Collapse>
)}
</Form.Item>
<Form.Item>
<Button htmlType="submit" type="primary">
Save
Expand Down
8 changes: 3 additions & 5 deletions src/shared/components/ResultsTable/ResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,13 @@ type ResultTableProps = {
}[];
pageSize?: number;
handleClick: (self: string) => void;
dashboardUrl: string;
};

const ResultsTable: React.FunctionComponent<ResultTableProps> = ({
headerProperties,
items,
pageSize = PAGE_SIZE,
handleClick,
dashboardUrl,
}) => {
const [searchValue, setSearchValue] = React.useState();

Expand Down Expand Up @@ -73,9 +71,9 @@ const ResultsTable: React.FunctionComponent<ResultTableProps> = ({
default:
render = (value: string) => {
const item = items.find(item => item[dataIndex] === value);
const base64EncodedUri = btoa(item && item.self.value);
const studioResourceViewLink = `/studios/studio-resources/${base64EncodedUri}?dashboard=${dashboardUrl}`;

const studioResourceViewLink = item
? `/?_self=${item.self.value}`
: '';
if (isISODate(value)) {
return (
<a href={studioResourceViewLink}>
Expand Down
1 change: 0 additions & 1 deletion src/shared/containers/DashboardListContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ const DashboardList: React.FunctionComponent<DashboardListProps> = ({
DEFAULT_SPARQL_VIEW_ID
}
dataQuery={dashboardResources[selectedDashboardIndex].dataQuery}
dashboardUrl={dashboardResources[selectedDashboardIndex]['_self']}
/>
)}
</TabList>
Expand Down
35 changes: 25 additions & 10 deletions src/shared/containers/DashboardResultsContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import * as React from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { Spin, Alert } from 'antd';
import { Spin, Alert, message } from 'antd';
import ResultsTable from '../components/ResultsTable/ResultsTable';
import { camelCaseToLabelString } from '../utils';
import { SelectQueryResponse, SparqlViewQueryResponse } from '@bbp/nexus-sdk';
import { camelCaseToLabelString, parseProjectUrl } from '../utils';
import {
SelectQueryResponse,
SparqlViewQueryResponse,
Resource,
} from '@bbp/nexus-sdk';
import { useNexusContext } from '@bbp/react-nexus';

export type Binding = {
Expand All @@ -30,8 +34,7 @@ const DashboardResultsContainer: React.FunctionComponent<{
orgLabel: string;
projectLabel: string;
viewId: string;
dashboardUrl: string;
}> = ({ orgLabel, projectLabel, dataQuery, viewId, dashboardUrl }) => {
}> = ({ orgLabel, projectLabel, dataQuery, viewId }) => {
const [error, setError] = React.useState<NexusSparqlError | Error>();
const [items, setItems] = React.useState<any[]>();
const [headerProperties, setHeaderProperties] = React.useState<any[]>();
Expand All @@ -40,10 +43,23 @@ const DashboardResultsContainer: React.FunctionComponent<{
const location = useLocation();

const goToStudioResource = (selfUrl: string) => {
const base64EncodedUri = btoa(selfUrl);
const studioResourceViewLink = `/studios/studio-resources/${base64EncodedUri}?dashboard=${dashboardUrl}`;

history.push(studioResourceViewLink, { background: location });
nexus
.httpGet({
path: selfUrl,
headers: { Accept: 'application/json' },
})
.then((resource: Resource) => {
const [orgLabel, projectLabel] = parseProjectUrl(resource._project);
history.push(
`/${orgLabel}/${projectLabel}/resources/${encodeURIComponent(
resource['@id']
)}`,
{ background: location }
);
})
.catch(error => {
message.error(`Resource ${self} could not be found`);
});
};

React.useEffect(() => {
Expand Down Expand Up @@ -119,7 +135,6 @@ const DashboardResultsContainer: React.FunctionComponent<{
headerProperties={headerProperties}
items={items ? (items as Item[]) : []}
handleClick={goToStudioResource}
dashboardUrl={dashboardUrl}
/>
</Spin>
);
Expand Down
Loading

0 comments on commit 4cfe378

Please sign in to comment.