Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
run: |
yarn run lint-check
yarn run build
yarn test --passWithNoTests --coverage --watchAll=false
yarn test --coverage --watchAll=false

- name: install python
uses: actions/setup-python@v7
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ install: yarn install
script:
- yarn run lint-check
- yarn run build
- yarn test --passWithNoTests --coverage --watchAll=false
- yarn test --coverage --watchAll=false
9 changes: 7 additions & 2 deletions Development/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import AdminMenu from './pages/menu';
import AppBar from './pages/appbar';
import About from './pages/about';
import { NodesList, NodesShow } from './pages/nodes';
import { DevicesList, DevicesShow } from './pages/devices';
import { DevicesEdit, DevicesList, DevicesShow } from './pages/devices';
import { SourcesList, SourcesShow } from './pages/sources';
import { FlowsList, FlowsShow } from './pages/flows';
import { ReceiversEdit, ReceiversList, ReceiversShow } from './pages/receivers';
Expand Down Expand Up @@ -59,7 +59,12 @@ const AppAdmin = () => {
>
<Resource name="Settings" list={Settings} />
<Resource name="nodes" list={NodesList} show={NodesShow} />
<Resource name="devices" list={DevicesList} show={DevicesShow} />
<Resource
name="devices"
list={DevicesList}
show={DevicesShow}
edit={DevicesEdit}
/>
<Resource name="sources" list={SourcesList} show={SourcesShow} />
<Resource name="flows" list={FlowsList} show={FlowsShow} />
<Resource
Expand Down
43 changes: 32 additions & 11 deletions Development/src/components/MappingButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,49 @@ import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUnchecked';
// de-emphasize the unchecked state
const faded = { opacity: 0.3 };

const styles = {
const styles = theme => ({
unchecked: faded,
constraintWarning: {
color:
theme.palette.type === 'light'
? theme.palette.warning.dark
: theme.palette.warning.light,
},
constraintWarningUnchecked: { opacity: 0.5 },
checked: {},
};
});

// filter out our classes to avoid the Material-UI console warning
const MappingButton = ({
checked,
constraintWarning,
classes: {
checked: checkedClass,
constraintWarning: constraintWarningClass,
constraintWarningUnchecked: constraintWarningUncheckedClass,
unchecked: uncheckedClass,
...inheritedClasses
},
...props
}) => (
<IconButton
className={checked ? checkedClass : uncheckedClass}
classes={inheritedClasses}
{...props}
>
{checked ? <CheckCircleOutlineIcon /> : <RadioButtonUncheckedIcon />}
</IconButton>
);
}) => {
const stateClass = checked
? checkedClass
: constraintWarning
? constraintWarningUncheckedClass
: uncheckedClass;
const className = [stateClass, constraintWarning && constraintWarningClass]
.filter(Boolean)
.join(' ');

return (
<IconButton className={className} classes={inheritedClasses} {...props}>
{checked ? (
<CheckCircleOutlineIcon />
) : (
<RadioButtonUncheckedIcon />
)}
</IconButton>
);
};

export default withStyles(styles)(MappingButton);
18 changes: 16 additions & 2 deletions Development/src/components/MappingShowActions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React from 'react';
import { Button, ListButton, TopToolbar, useRecordContext } from 'react-admin';
import {
Button,
EditButton,
ListButton,
TopToolbar,
useRecordContext,
} from 'react-admin';
import JsonIcon from '../icons/JsonIcon';
import { useTheme } from '@material-ui/styles';
import { concatUrl } from '../settings';
Expand All @@ -10,10 +16,15 @@ export default function MappingShowActions({ basePath, id, resource }) {
const { record } = useRecordContext();
let json_href;
const theme = useTheme();
const tab = window.location.href.split('/').pop();
if (record) {
const tab = window.location.href.split('/').pop();
if (tab === 'active_map' && record.$channelmappingAPI) {
json_href = concatUrl(record.$channelmappingAPI, '/map/active');
} else if (tab === 'activations' && record.$channelmappingAPI) {
json_href = concatUrl(
record.$channelmappingAPI,
'/map/activations'
);
} else {
json_href = resourceUrl(resource, `/${id}`);
}
Expand All @@ -40,6 +51,9 @@ export default function MappingShowActions({ basePath, id, resource }) {
<JsonIcon />
</Button>
) : null}
{record && tab === 'active_map' && record.$channelmappingAPI ? (
<EditButton basePath={basePath} record={record} />
) : null}
<ListButton
label={'List'}
title={'Return to ' + basePath}
Expand Down
102 changes: 101 additions & 1 deletion Development/src/dataProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ import {
UPDATE,
fetchUtils,
} from 'react-admin';
import { assign, get, has, isEmpty, pick, set } from 'lodash';
import {
assign,
cloneDeep,
get,
has,
isEmpty,
isEqual,
pick,
set,
setWith,
union,
} from 'lodash';
import { JsonPointer } from 'json-ptr';
import diff from 'deep-diff';
import { makeBearerAuthHeader } from './authProvider';
Expand Down Expand Up @@ -298,6 +309,39 @@ const isAuth = () => {
return token && usingAuth();
};

// map entries which differ between the active map and the requested map,
// cf. the deep-diff of '$staged' used to PATCH the Connection API
export const channelMappingAction = (activeMap, requestedMap) => {
const action = {};
for (const outputId of union(
Object.keys(activeMap || {}),
Object.keys(requestedMap || {})
)) {
const activeOutput = get(activeMap, outputId, {});
const requestedOutput = get(requestedMap, outputId, {});
for (const channelIndex of union(
Object.keys(activeOutput),
Object.keys(requestedOutput)
)) {
const activeEntry = get(activeOutput, channelIndex);
const requestedEntry = get(requestedOutput, channelIndex);
if (!isEqual(activeEntry, requestedEntry)) {
// use setWith rather than set to avoid creating arrays if any
// channel index is a number
setWith(
action,
[outputId, channelIndex],
requestedEntry === undefined
? null
: cloneDeep(requestedEntry),
Object
);
}
}
}
return action;
};

const convertDataProviderRequestToHTTP = (
type,
resource,
Expand Down Expand Up @@ -535,6 +579,42 @@ const convertDataProviderRequestToHTTP = (
}
}
case UPDATE: {
if (resource === 'devices') {
// an IS-08 activation request carries only the changed output
// channels, not the whole map
const action = channelMappingAction(
get(params, 'previousData.$active.map'),
get(params, 'data.$active.map')
);
const mode = get(
params,
'data.$activation.mode',
'activate_immediate'
);
const activation = { mode };
if (mode !== 'activate_immediate') {
activation.requested_time = get(
params,
'data.$activation.requested_time',
null
);
}
return {
url: concatUrl(
params.data.$channelmappingAPI,
'/map/activations/'
),
options: {
method: 'POST',
headers,
body: JSON.stringify({
activation,
action,
}),
},
};
}

let differences = [];
let allDifferences = diff(
get(params, 'previousData.$staged'),
Expand Down Expand Up @@ -624,6 +704,22 @@ const convertDataProviderRequestToHTTP = (
};
}
case DELETE: {
if (resource === 'devices') {
const activationId = params.activationId;
if (!activationId) {
throw new Error('missing activation id');
}
return {
url: concatUrl(
get(params, 'previousData.$channelmappingAPI'),
`/map/activations/${activationId}`
),
options: {
method: 'DELETE',
headers,
},
};
}
return {
url: resourceUrl(resource, `/${params.id}`),
options: {
Expand Down Expand Up @@ -1138,6 +1234,10 @@ const convertHTTPResponseToDataProvider = async (
total: null,
};
case UPDATE:
if (resource === 'devices') {
// the Channel Mapping API returns the activation, not the Device
return { data: { ...params.data, id: params.id } };
}
return { data: { ...json, id: json.id } };
case CREATE:
return { data: { ...params.data, id: json.id } };
Expand Down
Loading