Skip to content

Commit

Permalink
Implemented dashboard for providers and sources
Browse files Browse the repository at this point in the history
  • Loading branch information
rmathis committed Apr 27, 2021
1 parent 7ef485c commit 1d9e320
Show file tree
Hide file tree
Showing 17 changed files with 391 additions and 112 deletions.
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"reactstrap": "^8.9.0",
Expand Down
51 changes: 33 additions & 18 deletions ui/src/app/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,52 @@ import {
Redirect,
Route
} from "react-router-dom";

import { Provider as HttpProvider } from 'use-http';

import './App.scss';
import { I18nProvider } from './i18n/context/I18n.provider';
import Footer from './core/components/Footer';
import { get_cookie } from './core/utility/get_cookie';

import Dashboard from './dashboard/container/Dashboard';
import Header from './core/components/Header';
import { UserProvider } from './core/user/UserContext';
import { Metadata } from './metadata/Metadata';

function App() {

const httpOptions = {
interceptors: {
request: async ({options, url, path, route}) => {
options.headers['X-XSRF-TOKEN'] = get_cookie('XSRF-TOKEN');
return options;
}
}
};

return (
<div className="shibui">
<UserProvider>
<I18nProvider>
<Router>
<Header />
<main className="pad-content">
<Switch>
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>
<Route path="/dashboard" component={Dashboard} />
<Route path="/metadata/:type/:id" component={Metadata} />
</Switch>
</main>
<Footer />
</Router>
</I18nProvider>
</UserProvider>
<HttpProvider options={httpOptions}>


<UserProvider>
<I18nProvider>
<Router>
<Header />
<main className="pad-content">
<Switch>
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>
<Route path="/dashboard" component={Dashboard} />
<Route path="/metadata/:type/:id" component={Metadata} />
</Switch>
</main>
<Footer />
</Router>
</I18nProvider>
</UserProvider>
</HttpProvider>
</div>
);
}
Expand Down
12 changes: 12 additions & 0 deletions ui/src/app/core/utility/array_move.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function array_move(arr, old_index, new_index) {
if (new_index >= arr.length) {
let k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr;
}

export default array_move;
17 changes: 17 additions & 0 deletions ui/src/app/core/utility/get_cookie.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function get_cookie (cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}

export default get_cookie;
87 changes: 87 additions & 0 deletions ui/src/app/dashboard/component/Ordered.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import useFetch from 'use-http';
import first from 'lodash/first';
import last from 'lodash/last';
import API_BASE_PATH from '../../App.constant';
import { array_move } from '../../core/utility/array_move';
import { pick } from 'lodash';

const orderPaths = {
provider: `/MetadataResolversPositionOrder`
};

export const getId = (entity) => {
return entity.resourceId ? entity.resourceId : entity.id;
};

export const mergeOrderFn = (entities, order) => {
const ordered = [...entities.sort(
(a, b) => {
const aIndex = order.indexOf(getId(a));
const bIndex = order.indexOf(getId(b));
return aIndex > bIndex ? 1 : bIndex > aIndex ? -1 : 0;
}
)];
return ordered;
};

export function Ordered ({type = 'provider', entities, children}) {

const orderEntities = (orderById, list) => {
setOrdered(mergeOrderFn(list, orderById));
};

const { get, post, response } = useFetch(`${API_BASE_PATH}`, {
cachePolicy: 'no-cache'
});

const [order, setOrder] = React.useState([]);
const [ordered, setOrdered] = React.useState([]);

const [firstId, setFirstId] = React.useState(null);
const [lastId, setLastId] = React.useState(null);

async function changeOrder(resourceIds) {
const update = await post(`${orderPaths[type]}`, {
resourceIds
});
if (response.ok) {
loadOrder();
}
}

const onOrderUp = (id) => {
const index = order.indexOf(id);
const newOrder = array_move(order, index, index - 1);
changeOrder(newOrder);
};

const onOrderDown = (id) => {
const index = order.indexOf(id);
const newOrder = array_move(order, index, index + 1);
changeOrder(newOrder);
};

async function loadOrder () {
const o = await get(`${orderPaths[type]}`);
console.log(o)
if (response.ok) {
const ids = o.resourceIds;
setOrder(ids);
setFirstId(first(ids));
setLastId(last(ids));
}
}

React.useEffect(() => loadOrder(),[]);

React.useEffect(() => orderEntities(order, entities), [order, entities]);

React.useEffect(() => console.log(ordered.map(e => pick(e, ['resourceId']))), [ordered]);

return (
<>
{children(ordered, firstId, lastId, onOrderUp, onOrderDown)}
</>
);
}
33 changes: 33 additions & 0 deletions ui/src/app/dashboard/component/Scroller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

import InfiniteScroll from 'react-infinite-scroll-component';

const PAGE_LIMIT = 20;

export function Scroller ({ entities, children }) {

const [page, setPage] = React.useState(1);

const [limited, setLimited] = React.useState([]);

React.useEffect(() => {
let maxIndex = (page * PAGE_LIMIT) - 1,
minIndex = 0;
const l = entities.filter((resolver, index) => (maxIndex >= index && index >= minIndex));
setLimited(l);
}, [entities, page])

const loadNext = () => {
setPage(page + 1);
}

return (
<InfiniteScroll
dataLength={limited.length} //This is important field to render the next data
next={() => loadNext()}
hasMore={entities.length > limited.length}
>
{ children(limited) }
</InfiniteScroll>
);
}
47 changes: 47 additions & 0 deletions ui/src/app/dashboard/component/Search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import pick from 'lodash/pick';

import { Form, Label, Input, Button, InputGroup, InputGroupAddon, FormGroup} from 'reactstrap';
import { includes, some, values } from 'lodash';

export function Search ({ entities, searchable, children }) {

const [searched, setSearched] = React.useState([]);
const [query, setQuery] = React.useState('');

React.useEffect(() => setSearched(entities), [entities]);

const search = (query) => {
setQuery(query);
};

React.useEffect(() => {
if (!query) {
setSearched(entities);
} else {
setSearched(entities.filter((e) => {
const picked = values(pick(e, searchable));
return some(picked, (v) => includes(v.toLowerCase(), query.toLowerCase()));
}));
}
}, [query, entities, searchable]);

return (
<>
<Form className="w-50">
<FormGroup>
<Label for="search" className="sr-only">Search</Label>
<InputGroup>
<Input type="email" name="email" id="search"
placeholder="Search Files" onChange={ (event) => search(event.target.value) }
value={query} />
<InputGroupAddon addonType="append">
<Button color="text" className="px-3" onClick={ () => search('') }>Clear</Button>
</InputGroupAddon>
</InputGroup>
</FormGroup>
</Form>
{ children(searched) }
</>
);
}
20 changes: 17 additions & 3 deletions ui/src/app/dashboard/container/ProvidersTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import React from 'react';
import { useMetadataEntities } from '../../metadata/hooks/api';
import Translate from '../../i18n/components/translate';
import ProviderList from '../../metadata/domain/provider/component/ProviderList';
import {Search} from '../component/Search';
import { Ordered } from '../component/Ordered';

const searchProps = ['name', '@type', 'createdBy'];

export function ProvidersTab () {

Expand All @@ -28,9 +32,19 @@ export function ProvidersTab () {
</span>
</div>
<div className="p-3">
{ /* search goes here */}
<ProviderList entities={providers}></ProviderList>

<Ordered type="provider" entities={providers}>
{(ordered, first, last, onOrderUp, onOrderDown) =>
<Search entities={ordered} searchable={searchProps}>
{(searched) => <ProviderList
entities={searched}
reorder={providers.length === searched.length}
first={first}
last={last}
onOrderUp={onOrderUp}
onOrderDown={onOrderDown}></ProviderList>}
</Search>
}
</Ordered>
</div>
</div>
</section>
Expand Down
9 changes: 6 additions & 3 deletions ui/src/app/dashboard/container/SourcesTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import API_BASE_PATH from '../../App.constant';

import SourceList from '../../metadata/domain/source/component/SourceList';
import { useMetadataEntities } from '../../metadata/hooks/api';
import { Search } from '../component/Search';

const searchProps = ['serviceProviderName', 'entityId', 'createdBy'];

export function SourcesTab () {

Expand Down Expand Up @@ -37,9 +40,9 @@ export function SourcesTab () {
</span>
</div>
<div className="p-3">
{ /* search goes here */ }
<SourceList entities={ sources } onDelete={ deleteSource }></SourceList>

<Search entities={sources} searchable={searchProps}>
{(searched) => <SourceList entities={ searched } onDelete={ deleteSource }></SourceList>}
</Search>
</div>
</div>
</section>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/metadata/component/MetadataOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function MetadataOptions () {
<Translate value="action.version-history">Version History</Translate>
</Link>
{type === 'provider' &&
<button className="btn btn-link" onClick={"onScrollTo('filters')"}>
<button className="btn btn-link" onClick={() => console.log("onScrollTo('filters')")}>
<FontAwesomeIcon icon={faArrowDown} />&nbsp;
<Translate value="label.filters">Filters</Translate>
</button>
Expand Down
22 changes: 11 additions & 11 deletions ui/src/app/metadata/component/properties/ArrayProperty.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function ArrayProperty ({ property, columns, index, onPreview }) {
{property.differences && <span className="sr-only">Changed:</span> }
<span className="p-2" role="term" style={ {width} } ><Translate value={property.name}>{ property.name }</Translate></span>
{property.value.map((v, vidx) =>
<>
<React.Fragment key={vidx}>
{(!v || !v.length) && <p style={ {width} } className="text-secondary m-0">-</p> }
{(v && v.length > 0) &&
<ul style={ {width} } className="list-unstyled py-2 m-0">
Expand All @@ -89,22 +89,22 @@ export function ArrayProperty ({ property, columns, index, onPreview }) {
)}
</ul>
}
</>
</React.Fragment>
)}
</div>
: property.widget && property.widget.data ?
<>
{dataList.map((item, itemIdx) =>
<div className={`d-flex justify-content-start border-bottom border-light ${ property.differences && item.differences ? 'bg-diff' : '' }`} tabIndex="0">
{item.differences && <span className="sr-only">Changed:</span> }
<span className="p-2" role="term" style={ {width} }><Translate value={item.label}>{ item.label }</Translate></span>
{ property.value.map((v, vIdx) =>
<div className="py-2" style={ {width} }>
{v && v.indexOf(item.key) > -1 && <span><Translate value="value.true">true</Translate></span> }
{(!v || !(v.indexOf(item.key) > -1)) && <span><Translate value="value.false">false</Translate></span> }
<div className={`d-flex justify-content-start border-bottom border-light ${ property.differences && item.differences ? 'bg-diff' : '' }`} tabIndex="0" key={itemIdx}>
{item.differences && <span className="sr-only">Changed:</span> }
<span className="p-2" role="term" style={ {width} }><Translate value={item.label}>{ item.label }</Translate></span>
{ property.value.map((v, vIdx) =>
<div className="py-2" style={ {width} }>
{v && v.indexOf(item.key) > -1 && <span><Translate value="value.true">true</Translate></span> }
{(!v || !(v.indexOf(item.key) > -1)) && <span><Translate value="value.false">false</Translate></span> }
</div>
)}
</div>
)}
</div>
)}
</>
: ''}
Expand Down
Loading

0 comments on commit 1d9e320

Please sign in to comment.