Skip to content

Commit

Permalink
feat(gui): made deployment targets rely on filter information in the …
Browse files Browse the repository at this point in the history
…deployment to more reliably display target devices etc.

Ticket: MEN-7647
Changelog: Title
Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
  • Loading branch information
mzedel committed Dec 9, 2024
1 parent 9e43a49 commit 47c92d4
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ exports[`DeploymentOverview Component renders correctly 1`] = `
style="font-weight: 500;"
target="_blank"
>
test deployment
testGroup
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall margin-left-small emotion-1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,14 @@ import { Chip } from '@mui/material';
import { makeStyles } from 'tss-react/mui';

import { TwoColumnData } from '@northern.tech/common-ui/configurationobject';
import DeviceIdentityDisplay from '@northern.tech/common-ui/deviceidentity';
import Time from '@northern.tech/common-ui/time';
import { DEPLOYMENT_STATES, DEPLOYMENT_TYPES } from '@northern.tech/store/constants';
import { groupDeploymentStats } from '@northern.tech/store/utils';
import { isEmpty } from '@northern.tech/utils/helpers';
import pluralize from 'pluralize';
import isUUID from 'validator/lib/isUUID';

import failImage from '../../../../assets/img/largeFail.png';
import successImage from '../../../../assets/img/largeSuccess.png';
import { getDevicesLink } from '../deployment-wizard/softwaredevices';
import { getDeploymentTargetText, getDevicesLink } from '../deployment-wizard/softwaredevices';
import { defaultColumnDataProps } from '../report';

const useStyles = makeStyles()(theme => ({
Expand Down Expand Up @@ -60,7 +57,7 @@ const defaultLinkProps = {
rel: 'noopener noreferrer'
};

export const DeploymentOverview = ({ creator, deployment, devicesById, onScheduleClick, tenantCapabilities }) => {
export const DeploymentOverview = ({ creator, deployment, devicesById, idAttribute, onScheduleClick }) => {
const { classes } = useStyles();
const {
artifact_name,
Expand All @@ -75,43 +72,31 @@ export const DeploymentOverview = ({ creator, deployment, devicesById, onSchedul
} = deployment;
const { failures, successes } = groupDeploymentStats(deployment);
const finished = deployment.finished || status === DEPLOYMENT_STATES.finished;
const isDeviceDeployment = isUUID(name) && (isEmpty(devices) || Object.keys(devices).length === 1);
const isSoftwareDeployment = type === DEPLOYMENT_TYPES.software;
const { hasFullFiltering } = tenantCapabilities;

let deploymentRelease = (
const deploymentRelease = isSoftwareDeployment ? (
<Link {...defaultLinkProps} to={`/releases/${encodeURIComponent(artifact_name)}`}>
{artifact_name}
<LaunchIcon className="margin-left-small" fontSize="small" />
</Link>
) : (
type
);

const devicesLink = getDevicesLink({
devices: Object.values(devices),
filters: filter?.filters,
group: group || filter?.name,
hasFullFiltering,
name
});
let targetDevices = (
<Link {...defaultLinkProps} to={devicesLink}>
{isDeviceDeployment && devicesById[name] ? (
<DeviceIdentityDisplay device={devicesById[name]} isEditable={false} />
) : isUUID(name) ? (
Object.keys(devices).join(', ')
) : (
name
)}
{getDeploymentTargetText({ deployment, devicesById, idAttribute })}
<LaunchIcon className="margin-left-small" fontSize="small" />
<Chip className={`margin-left uppercased ${classes.chip}`} label={filter ? 'dynamic' : 'static'} size="small" />
<Chip className={`margin-left uppercased ${classes.chip}`} label={filter?.name ? 'dynamic' : 'static'} size="small" />
</Link>
);

if (!isSoftwareDeployment) {
deploymentRelease = type;
targetDevices = Object.keys(devices).join(', ') || name;
}

const deploymentInfo = {
'Release': deploymentRelease,
'Target device(s)': targetDevices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import AsyncAutocomplete from '@northern.tech/common-ui/asyncautocomplete';
import { getDeviceIdentityText } from '@northern.tech/common-ui/deviceidentity';
import InfoText from '@northern.tech/common-ui/infotext';
import { HELPTOOLTIPS, MenderHelpTooltip } from '@northern.tech/helptips/helptooltips';
import { ALL_DEVICES, DEPLOYMENT_TYPES } from '@northern.tech/store/constants';
import { ALL_DEVICES, ATTRIBUTE_SCOPES, DEPLOYMENT_TYPES, DEVICE_FILTERING_OPTIONS, DEVICE_STATES } from '@northern.tech/store/constants';
import { getReleases, getSystemDevices } from '@northern.tech/store/thunks';
import { stringToBoolean } from '@northern.tech/utils/helpers';
import { formatDeviceSearch } from '@northern.tech/utils/locationutils';
Expand All @@ -45,25 +45,63 @@ const hardCodedStyle = {
}
};

export const getDevicesLink = ({ devices, filters = [], group, hasFullFiltering, name }) => {
export const getDevicesLink = ({ devices, filters = [], group, name }) => {
let devicesLink = '/devices';
if (filters.length) {
return `${devicesLink}?${formatDeviceSearch({ pageState: {}, filters, selectedGroup: group })}`;
}
// older deployments won't have the filter set so we have to try to guess their targets based on other information
if (devices.length && (!name || isUUID(name))) {
devicesLink = `${devicesLink}?id=${devices[0].id}`;
if (hasFullFiltering) {
devicesLink = `/devices?${devices.map(({ id }) => `id=${id}`).join('&')}`;
}
devicesLink = `${devicesLink}?${devices.map(({ id }) => `id=${id}`).join('&')}`;
if (devices.length === 1) {
const { systemDeviceIds = [] } = devices[0];
devicesLink = `${devicesLink}${systemDeviceIds.map(id => `&id=${id}`).join('')}`;
}
} else if (group || filters.length) {
} else if (group) {
devicesLink = `${devicesLink}?${formatDeviceSearch({ pageState: {}, filters, selectedGroup: group })}`;
}
return devicesLink;
};

const deploymentFiltersToTargetText = ({ devicesById, filter, idAttribute }) => {
const { name, filters = [] } = filter;
if (name) {
return name;
}
if (
filters.some(
({ operator, scope, value }) => scope === ATTRIBUTE_SCOPES.identity && value === DEVICE_STATES.accepted && operator === DEVICE_FILTERING_OPTIONS.$eq.key
)
) {
return ALL_DEVICES;
}
const groupFilter = filters.find(
({ operator, scope, key }) => scope === ATTRIBUTE_SCOPES.system && operator === DEVICE_FILTERING_OPTIONS.$eq.key && key === 'group'
);
if (groupFilter) {
return groupFilter.value;
}
return filters
.reduce((accu, { operator, scope, key, value }) => {
if (!(key === 'id' && scope === ATTRIBUTE_SCOPES.identity)) {
return accu;
}
if (operator === DEVICE_FILTERING_OPTIONS.$in.key) {
const devices = value.map(deviceId => getDeviceIdentityText({ device: devicesById[deviceId], idAttribute }));
return [...accu, ...devices];
}
accu.push(getDeviceIdentityText({ device: devicesById[value], idAttribute }));
return accu;
}, [])
.join(', ');
};

export const getDeploymentTargetText = ({ deployment, devicesById, idAttribute }) => {
const { devices = {}, group = '', name = '', type = DEPLOYMENT_TYPES.software } = deployment;
const { devices = {}, filter = {}, group = '', name = '', type = DEPLOYMENT_TYPES.software } = deployment;
const text = deploymentFiltersToTargetText({ devicesById, filter, idAttribute });
if (text) {
return text;
}
let deviceList = Array.isArray(devices) ? devices : Object.values(devices);
if (isUUID(name) && devicesById[name]) {
deviceList = [devicesById[name]];
Expand Down
8 changes: 1 addition & 7 deletions frontend/src/js/components/deployments/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,13 +268,7 @@ export const DeploymentReport = ({ abort, onClose, past, retry, type }) => {
<Divider />
<div>
<DeploymentPhaseNotification deployment={deployment} onReviewClick={scrollToBottom} />
<DeploymentOverview
creator={creator}
deployment={deployment}
devicesById={devicesById}
onScheduleClick={scrollToBottom}
tenantCapabilities={tenantCapabilities}
/>
<DeploymentOverview creator={creator} deployment={deployment} devicesById={devicesById} idAttribute={idAttribute} onScheduleClick={scrollToBottom} />
{isConfigurationDeployment && (
<>
<LinedHeader className={classes.header} heading="Configuration" />
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/js/store/storehooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ const appInitActions = [
type: deviceActions.receivedGroups.type,
payload: {
testGroup: defaultState.devices.groups.byId.testGroup,
testGroupDynamic: { filters: [{ key: 'group', operator: '$eq', scope: 'system', value: 'things' }], id: 'filter1' }
testGroupDynamic: { filters: [{ key: 'group', operator: '$eq', scope: 'system', value: 'things' }], id: 'filter1', name: 'filter1' }
}
},
{ type: getDevicesByStatus.pending.type },
Expand All @@ -189,6 +189,7 @@ const appInitActions = [
{ key: 'kernel', operator: '$exists', scope: 'identity', value: true }
],
id: 'filter1',
name: 'filter1',
total: 0
}
}
Expand Down
1 change: 1 addition & 0 deletions frontend/tests/mockData.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ export const defaultState = {
},
testGroupDynamic: {
id: 'filter1',
name: 'filter1',
filters: [{ scope: 'system', key: 'group', operator: '$eq', value: 'things' }]
}
},
Expand Down

0 comments on commit 47c92d4

Please sign in to comment.