• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / gui / 901187442

pending completion
901187442

Pull #3795

gitlab-ci

mzedel
feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

Ticket: None
Changelog: None
Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3795: feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

4389 of 6365 branches covered (68.96%)

5 of 5 new or added lines in 1 file covered. (100.0%)

1729 existing lines in 165 files now uncovered.

8274 of 10019 relevant lines covered (82.58%)

144.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.0
/src/js/components/devices/base-devices.js
1
// Copyright 2020 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14
import React from 'react';
15
import { Link } from 'react-router-dom';
16

17
import pluralize from 'pluralize';
18

19
import preauthImage from '../../../assets/img/preauthorize.png';
20
import { DEVICE_STATES } from '../../constants/deviceConstants';
21
import { rootfsImageVersion } from '../../constants/releaseConstants';
22
import Time, { ApproximateRelativeDate } from '../common/time';
23
import DeviceStatus from './device-status';
24

25
const propertyNameMap = {
190✔
26
  inventory: 'attributes',
27
  identity: 'identity_data',
28
  system: 'system',
29
  monitor: 'monitor',
30
  tags: 'tags'
31
};
32

33
export const defaultTextRender = ({ column, device }) => {
190✔
34
  const propertyName = propertyNameMap[column.attribute.scope] ?? column.attribute.scope;
4!
35
  const accessorTarget = device[propertyName] ?? device;
4!
36
  const attributeValue = accessorTarget[column.attribute.name] || accessorTarget[column.attribute.alternative] || device[column.attribute.name];
4✔
37
  return typeof attributeValue === 'object' ? JSON.stringify(attributeValue) : attributeValue;
4!
38
};
39

40
export const getDeviceIdentityText = ({ device = {}, idAttribute }) => {
190!
41
  const { id = '', identity_data = {}, tags = {} } = device;
24!
42
  // eslint-disable-next-line no-unused-vars
43
  const { status, ...remainingIds } = identity_data;
24✔
44
  const nonIdKey = Object.keys(remainingIds)[0];
24✔
45
  let text = id;
24✔
46
  if (!idAttribute || idAttribute === 'id' || idAttribute === 'Device ID') {
24✔
47
    return text;
21✔
48
  } else if (idAttribute === 'name') {
3!
UNCOV
49
    return tags[idAttribute] ?? `${id.substring(0, 6)}...`;
×
50
  }
51
  return identity_data[idAttribute] ?? identity_data[nonIdKey];
3✔
52
};
53

54
const AttributeRenderer = ({ content, textContent }) => (
190✔
55
  <div title={textContent}>
21✔
56
    <div className="text-overflow">{content}</div>
57
  </div>
58
);
59

60
export const DefaultAttributeRenderer = ({ column, device, idAttribute }) => (
190✔
61
  <AttributeRenderer content={column.textRender({ device, column, idAttribute })} textContent={column.textRender({ device, column, idAttribute })} />
1✔
62
);
63

64
export const getDeviceSoftwareText = (attributes = {}) => attributes[rootfsImageVersion] || attributes.artifact_name || '-';
190!
65
export const DeviceSoftware = ({ device }) => (
190✔
66
  <AttributeRenderer content={getDeviceSoftwareText(device.attributes)} textContent={getDeviceSoftwareText(device.attributes)} />
10✔
67
);
68

69
export const getDeviceTypeText = (attributes = {}) => (attributes.device_type?.length ? attributes.device_type.join(',') : '-');
190!
70
export const DeviceTypes = ({ device }) => (
190✔
71
  <AttributeRenderer content={getDeviceTypeText(device.attributes)} textContent={getDeviceTypeText(device.attributes)} />
10✔
72
);
73

74
export const RelativeDeviceTime = ({ device }) => (
190✔
75
  <div>
10✔
76
    <ApproximateRelativeDate updateTime={device.updated_ts} />
77
  </div>
78
);
79

80
export const DeviceCreationTime = ({ device }) =>
190✔
81
  device.created_ts ? (
1!
82
    <div>
83
      <Time value={device.created_ts} />
84
    </div>
85
  ) : (
86
    '-'
87
  );
88

89
export const DeviceStatusRenderer = ({ device }) => (
190✔
90
  <div>
10✔
91
    <DeviceStatus device={device} />
92
  </div>
93
);
94

95
export const AcceptedEmptyState = ({ allCount }) => (
190✔
96
  <div className="dashboard-placeholder">
2✔
97
    <p>No devices found</p>
98
    {!allCount && (
3✔
99
      <>
100
        <p>No devices have been authorized to connect to the Mender server yet.</p>
101
        <p>
102
          Visit the <Link to="/help/get-started">Help section</Link> to learn how to connect devices to the Mender server.
103
        </p>
104
      </>
105
    )}
106
  </div>
107
);
108

109
export const PreauthorizedEmptyState = ({ canManageDevices, limitMaxed, onClick }) => (
190✔
110
  <div className="dashboard-placeholder">
1✔
111
    <p>There are no preauthorized devices.</p>
112
    {canManageDevices && (
1!
113
      <p>
114
        {limitMaxed ? 'Preauthorize devices' : <a onClick={onClick}>Preauthorize devices</a>} so that when they come online, they will connect to the server
×
115
        immediately
116
      </p>
117
    )}
118
    <img src={preauthImage} alt="preauthorize" />
119
  </div>
120
);
121

122
export const PendingEmptyState = ({ filters, highlightHelp }) => (
190✔
123
  <div className="dashboard-placeholder">
1✔
124
    <p>
125
      {filters.length
1!
126
        ? `There are no pending devices matching the selected ${pluralize('filters', filters.length)}`
127
        : 'There are no devices pending authorization'}
128
    </p>
129
    {highlightHelp ? (
1!
130
      <p>
131
        Visit the <Link to="/help/get-started">Help section</Link> to learn how to connect devices to the Mender server.
132
      </p>
133
    ) : null}
134
  </div>
135
);
136

137
export const RejectedEmptyState = ({ filters }) => (
190✔
138
  <div className="dashboard-placeholder">
1✔
139
    <p>{filters.length ? `There are no rejected devices matching the selected ${pluralize('filters', filters.length)}` : 'There are no rejected devices'}</p>
1!
140
  </div>
141
);
142

143
export const defaultHeaders = {
190✔
144
  currentSoftware: {
145
    title: 'Current software',
146
    attribute: { name: rootfsImageVersion, scope: 'inventory', alternative: 'artifact_name' },
147
    component: DeviceSoftware,
148
    sortable: true,
149
    textRender: getDeviceSoftwareText
150
  },
151
  deviceCreationTime: {
152
    title: 'First request',
153
    attribute: { name: 'created_ts', scope: 'system' },
154
    component: DeviceCreationTime,
155
    sortable: true
156
  },
157
  deviceId: {
158
    title: 'Device ID',
159
    attribute: { name: 'id', scope: 'identity' },
160
    sortable: true,
UNCOV
161
    textRender: ({ device }) => device.id
×
162
  },
163
  deviceStatus: {
164
    title: 'Status',
165
    attribute: { name: 'status', scope: 'identity' },
166
    component: DeviceStatusRenderer,
167
    sortable: true,
168
    textRender: defaultTextRender
169
  },
170
  deviceType: {
171
    title: 'Device type',
172
    attribute: { name: 'device_type', scope: 'inventory' },
173
    component: DeviceTypes,
174
    sortable: true,
175
    textRender: getDeviceTypeText
176
  },
177
  lastCheckIn: {
178
    title: 'Last check-in',
179
    attribute: { name: 'updated_ts', scope: 'system' },
180
    component: RelativeDeviceTime,
181
    sortable: true
182
  }
183
};
184

185
const baseDevicesRoute = '/devices';
190✔
186

187
const acceptedDevicesRoute = {
190✔
188
  key: DEVICE_STATES.accepted,
189
  groupRestricted: false,
190
  route: `${baseDevicesRoute}/${DEVICE_STATES.accepted}`,
191
  title: () => DEVICE_STATES.accepted,
10✔
192
  emptyState: AcceptedEmptyState,
193
  defaultHeaders: [defaultHeaders.deviceType, defaultHeaders.currentSoftware, defaultHeaders.lastCheckIn]
194
};
195

196
export const routes = {
190✔
197
  allDevices: {
198
    ...acceptedDevicesRoute,
199
    route: baseDevicesRoute,
200
    key: 'any',
201
    title: () => 'any'
10✔
202
  },
203
  devices: acceptedDevicesRoute,
204
  [DEVICE_STATES.accepted]: acceptedDevicesRoute,
205
  [DEVICE_STATES.pending]: {
206
    key: DEVICE_STATES.pending,
207
    groupRestricted: true,
208
    route: `${baseDevicesRoute}/${DEVICE_STATES.pending}`,
209
    title: count => `${DEVICE_STATES.pending}${count ? ` (${count})` : ''}`,
10!
210
    emptyState: PendingEmptyState,
211
    defaultHeaders: [defaultHeaders.deviceCreationTime, defaultHeaders.lastCheckIn]
212
  },
213
  [DEVICE_STATES.preauth]: {
214
    key: DEVICE_STATES.preauth,
215
    groupRestricted: true,
216
    route: `${baseDevicesRoute}/${DEVICE_STATES.preauth}`,
217
    title: () => DEVICE_STATES.preauth,
10✔
218
    emptyState: PreauthorizedEmptyState,
219
    defaultHeaders: [
220
      {
221
        ...defaultHeaders.deviceCreationTime,
222
        title: 'Date added'
223
      }
224
    ]
225
  },
226
  [DEVICE_STATES.rejected]: {
227
    key: DEVICE_STATES.rejected,
228
    groupRestricted: true,
229
    route: `${baseDevicesRoute}/${DEVICE_STATES.rejected}`,
230
    title: () => DEVICE_STATES.rejected,
10✔
231
    emptyState: RejectedEmptyState,
232
    defaultHeaders: [defaultHeaders.deviceCreationTime, defaultHeaders.lastCheckIn]
233
  }
234
};
235

236
export const sortingAlternatives = Object.values(routes)
190✔
237
  .reduce((accu, item) => [...accu, ...item.defaultHeaders], [])
1,140✔
238
  .reduce((accu, item) => {
239
    if (item.attribute.alternative) {
2,660✔
240
      accu[item.attribute.name] = item.attribute.alternative;
570✔
241
      accu[item.attribute.alternative] = item.attribute.name;
570✔
242
    }
243
    return accu;
2,660✔
244
  }, {});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc