Skip to content

Commit

Permalink
Updated search
Browse files Browse the repository at this point in the history
Former-commit-id: 5bf13c3
  • Loading branch information
rmathis committed Aug 19, 2022
1 parent 1e1b35f commit f467c5a
Show file tree
Hide file tree
Showing 12 changed files with 8,171 additions and 720 deletions.
7 changes: 7 additions & 0 deletions backend/src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ action.select-bundle=Select Bundle

action.get-latest=Get latest

action.configurations=Shibboleth configurations
action.create-new-configuration=Create Shibboleth configuration set

value.enabled=Enabled
value.disabled=Disabled
value.current=Current
Expand Down Expand Up @@ -530,6 +533,10 @@ label.role-name=Role Name
label.role-description=Role Description
label.role=Role

label.configuration-management=Manage Shibboleth configurations
label.configuration-name=Shibboleth configuration sets
label.new-configuration=Create new configuration set

message.delete-role-title=Delete Role?

message.delete-role-body=You are requesting to delete a role. If you complete this process the role will be removed. This cannot be undone. Do you wish to continue?
Expand Down
8,529 changes: 7,872 additions & 657 deletions ui/public/data/properties.json

Large diffs are not rendered by default.

141 changes: 132 additions & 9 deletions ui/src/app/admin/component/ConfigurationForm.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,99 @@
import React from 'react';
import React, { Fragment } from 'react';
import Button from 'react-bootstrap/Button';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner, faSave } from '@fortawesome/free-solid-svg-icons';
import { Highlighter, Menu, MenuItem, Token, Typeahead } from 'react-bootstrap-typeahead';
import Translate from '../../i18n/components/translate';
import { ToggleButton } from '../../form/component/ToggleButton';

import { FormContext, setFormDataAction, setFormErrorAction } from '../../form/FormManager';
import { useProperties, usePropertiesLoading } from '../hoc/PropertiesProvider';
import { groupBy } from 'lodash';
import { useCallback } from 'react';

export function ConfigurationForm({ property = {}, errors = [], loading = false, schema, onSave, onCancel }) {
export function ConfigurationForm({ configuration = {}, errors = [], schema, onSave, onCancel }) {

const { dispatch } = React.useContext(FormContext);
const onChange = ({ formData, errors }) => {
dispatch(setFormDataAction(formData));
dispatch(setFormErrorAction(errors));
const properties = useProperties();
const loading = usePropertiesLoading();

const select = (data) => {
console.log(data);
setSelected(data);
};

const [selected, setSelected] = React.useState([]);

const [config, setConfig] = React.useState({ name: '', properties: [] });

// config.properties.filter(p => p.category === item.category).length === properties.filter(p => p.category === item.category).length

const menu = useCallback((results, menuProps, state) => {
let index = 0;
const mapped = results.map(p => !p.category || p.category === '?' ? { ...p, category: 'Misc' } : p);
const grouped = groupBy(mapped, 'category');
const items = Object.keys(grouped).sort().map((item) => (
<Fragment key={item}>
{index !== 0 && <Menu.Divider />}
<Menu.Header className="p-0">
<MenuItem key={index}
option={{category: item, propertyName: item, isCategory: true}}
position={index}>
{item} - Add all
</MenuItem>
</Menu.Header>
{grouped[item].map((i) => {
const item =
<MenuItem key={index} option={i} position={index} disabled={ config.properties.some((p) => p.propertyName === i.propertyName) }>
<Highlighter search={state.text}>
{`- ${i.propertyName}`}
</Highlighter>
</MenuItem>;
index += 1;
return item;
})}
</Fragment>
));

return <Menu {...menuProps}>{items}</Menu>;
}, [config.properties]);

const token = (option, { onRemove }, index) => (
<Token
key={index}
onRemove={onRemove}
option={option}>
{`${option.propertyName}`}
</Token>
);

const addProperties = (props) => {

const parsed = props.reduce((coll, prop, idx) => {
if (prop.isCategory) {
return [...coll, ...properties.filter(p => p.category === prop.category)];
} else {
return [...coll, prop];
}
}, []);

setConfig({
...config,
properties: [
...config.properties,
...parsed,
]
});
setSelected([]);
};

React.useEffect(() => console.log(selected), [selected]);

return (<>
<div className="container-fluid">
<div className="d-flex justify-content-end align-items-center">
<React.Fragment>
<Button variant="info" className="me-2"
type="button"
onClick={() => onSave(property)}
onClick={() => onSave(configuration)}
disabled={errors.length > 0 || loading}
aria-label="Save changes to the metadata source. You will return to the dashboard">
<FontAwesomeIcon icon={loading ? faSpinner : faSave} pulse={loading} />&nbsp;
Expand All @@ -36,7 +109,57 @@ export function ConfigurationForm({ property = {}, errors = [], loading = false,
<hr />
<div className="row">
<div className="col-12 col-lg-6 order-2">

<div className="d-flex align-items-end">
<div className="flex-grow w-75">
<label htmlFor="property-selector">Add properties</label>
<Typeahead
id='property-selector'
onChange={selected => select(selected)}
options={[...properties]}
selected={selected}
labelKey={option => `${option.propertyName}`}
filterBy={['propertyName', 'category', 'displayType']}
renderMenu={ menu }
multiple={ true }
renderToken={ token }
>
{({ isMenuShown, toggleMenu }) => (
<ToggleButton isOpen={isMenuShown} onClick={e => toggleMenu()}>
<span className="sr-only">Options</span>
</ToggleButton>
)}
</Typeahead>
</div>
<Button type="button"
variant="outline-secondary"
className="ms-2"
onClick={() => addProperties(selected)}>Add</Button>
</div>
</div>
</div>
<div className="my-4"></div>
<div className='row'>
<div className='col-12'>
<table className='w-100 table'>
<thead>
<tr>
<th>Property</th>
<th>Category</th>
<th>Type</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{config.properties.map((p, idx) => (
<tr key={idx}>
<td>{ p.propertyName }</td>
<td></td>
<td></td>
<td></td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
Expand Down
10 changes: 5 additions & 5 deletions ui/src/app/admin/container/EditConfiguration.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import React from 'react';
import { Prompt, useHistory } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import Translate from '../../i18n/components/translate';
import { useProperties } from '../hooks';
import { useConfigurations } from '../hooks';
import { Schema } from '../../form/Schema';
import { FormManager } from '../../form/FormManager';

import { PropertyProvider } from '../hoc/PropertyProvider';
import { ConfigurationsProvider } from '../hoc/ConfigurationsProvider';
import { createNotificationAction, NotificationTypes, useNotificationDispatcher } from '../../notifications/hoc/Notifications';
import { useTranslator } from '../../i18n/hooks';
import { BASE_PATH } from '../../App.constant';
Expand All @@ -22,7 +22,7 @@ export function EditConfiguration() {

const history = useHistory();

const { put, response, loading } = useProperties();
const { put, response, loading } = useConfigurations();

const [blocking, setBlocking] = React.useState(false);

Expand Down Expand Up @@ -66,7 +66,7 @@ export function EditConfiguration() {
</div>
</div>
<div className="section-body p-4 border border-top-0 border-info">
<PropertyProvider id={id}>
<ConfigurationsProvider id={id}>
{(property) =>
<Schema path={`/${BASE_PATH}assets/schema/configuration/configuration.json`}>
{(schema) =>
Expand All @@ -84,7 +84,7 @@ export function EditConfiguration() {
}</>}
</Schema>
}
</PropertyProvider>
</ConfigurationsProvider>
</div>
</section>
</div>
Expand Down
35 changes: 19 additions & 16 deletions ui/src/app/admin/container/NewConfiguration.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import React from 'react';

import { Prompt, useHistory } from 'react-router-dom';
import Translate from '../../i18n/components/translate';
import { useProperties } from '../hooks';
import { useConfiguration } from '../hooks';
import { Schema } from '../../form/Schema';
import { FormManager } from '../../form/FormManager';
import { ConfigurationForm } from '../component/ConfigurationForm';

import { createNotificationAction, NotificationTypes, useNotificationDispatcher } from '../../notifications/hoc/Notifications';
import { useTranslator } from '../../i18n/hooks';
import { BASE_PATH } from '../../App.constant';
import { PropertiesProvider } from '../hoc/PropertiesProvider';

export function NewConfiguration() {
const history = useHistory();
const notifier = useNotificationDispatcher();
const translator = useTranslator();

const { post, response, loading } = useProperties({});
const { post, response, loading } = useConfiguration({});

const [blocking, setBlocking] = React.useState(false);

Expand Down Expand Up @@ -55,24 +56,26 @@ export function NewConfiguration() {
<div className="section-header bg-info p-2 text-white">
<div className="row justify-content-between">
<div className="col-md-12">
<span className="lead"><Translate value="label.new-property">Add a new property</Translate></span>
<span className="lead"><Translate value="label.new-configuration">Create new configuration set</Translate></span>
</div>
</div>
</div>
<div className="section-body p-4 border border-top-0 border-info">
<Schema path={`/${BASE_PATH}assets/schema/configuration/configuration.json`}>
{(schema) =>
<FormManager initial={{}}>
{(data, errors) =>
<ConfigurationForm
property={data}
errors={errors}
schema={schema}
loading={loading}
onSave={(data) => save(data)}
onCancel={() => cancel()} />}
</FormManager>}
</Schema>
<PropertiesProvider>
<Schema path={`/${BASE_PATH}assets/schema/configuration/configuration.json`}>
{(schema) =>
<FormManager initial={{}}>
{(data, errors) =>
<ConfigurationForm
configuration={data}
errors={errors}
schema={schema}
loading={loading}
onSave={(data) => save(data)}
onCancel={() => cancel()} />}
</FormManager>}
</Schema>
</PropertiesProvider>
</div>
</section>
</div>
Expand Down
18 changes: 9 additions & 9 deletions ui/src/app/admin/hoc/ConfigurationsProvider.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import React from 'react';
import { useProperties } from '../hooks';
import { useConfigurations } from '../hooks';
import { createNotificationAction, NotificationTypes, useNotificationDispatcher } from '../../notifications/hoc/Notifications';
import { useTranslator } from '../../i18n/hooks';

export function ConfigurationsProvider({ children, cache = 'no-cache' }) {

const [properties, setProperties] = React.useState([]);
const [configurations, setConfigurations] = React.useState([]);

const notifier = useNotificationDispatcher();
const translator = useTranslator();

const { get, del, response, loading } = useProperties({
const { get, del, response, loading } = useConfigurations({
cachePolicy: cache
});

async function loadProperties() {
async function loadConfigurations() {
const list = await get(`assets/data/properties.json`);
if (response.ok) {
setProperties(list);
setConfigurations(list);
}
}

async function removeProperty(id) {
async function removeConfiguration(id) {
let toast;
const resp = await del(`/${id}`);
if (response.ok) {
loadProperties();
loadConfigurations();
toast = createNotificationAction(`Deleted property successfully.`, NotificationTypes.SUCCESS);
} else {
toast = createNotificationAction(`${resp.errorCode} - ${translator(resp.errorMessage)}`, NotificationTypes.ERROR);
Expand All @@ -36,7 +36,7 @@ export function ConfigurationsProvider({ children, cache = 'no-cache' }) {
}

/*eslint-disable react-hooks/exhaustive-deps*/
React.useEffect(() => { loadProperties() }, []);
React.useEffect(() => { loadConfigurations() }, []);

return (<>{children(properties, removeProperty, loading)}</>);
return (<>{children(configurations, removeConfiguration, loading)}</>);
}
50 changes: 50 additions & 0 deletions ui/src/app/admin/hoc/PropertiesProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import useFetch from 'use-http';
import API_BASE_PATH, { BASE_PATH } from '../../App.constant';
import has from 'lodash/has';
import { groupBy } from 'lodash';


const PropertiesContext = React.createContext();

const { Provider, Consumer } = PropertiesContext;

function PropertiesProvider({ children, cache = 'no-cache' }) {

const [properties, setProperties] = React.useState([]);


const { get, response, loading } = useFetch('', {
cachePolicy: cache
});

async function loadProperties() {
const list = await get(`${API_BASE_PATH}/shib/properties`);
if (response.ok) {
setProperties(list);
}
}

/*eslint-disable react-hooks/exhaustive-deps*/
React.useEffect(() => { loadProperties() }, []);

return (<Provider value={{properties, loading}}>{children}</Provider>);
}

function useProperties() {
const { properties } = React.useContext(PropertiesContext);
return properties;
}

function usePropertiesLoading() {
const { loading } = React.useContext(PropertiesContext);
return loading;
}

export {
PropertiesProvider,
PropertiesContext,
Consumer as PropertiesConsumer,
useProperties,
usePropertiesLoading,
};
Loading

0 comments on commit f467c5a

Please sign in to comment.