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

mendersoftware / gui / 1057188406

01 Nov 2023 04:24AM UTC coverage: 82.824% (-17.1%) from 99.964%
1057188406

Pull #4134

gitlab-ci

web-flow
chore: Bump uuid from 9.0.0 to 9.0.1

Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.0 to 9.0.1.
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.0...v9.0.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4134: chore: Bump uuid from 9.0.0 to 9.0.1

4349 of 6284 branches covered (0.0%)

8313 of 10037 relevant lines covered (82.82%)

200.97 hits per line

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

97.03
/src/js/selectors/index.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 { createSelector } from '@reduxjs/toolkit';
15

16
import { defaultReports } from '../actions/deviceActions';
17
import { mapUserRolesToUiPermissions } from '../actions/userActions';
18
import { PLANS } from '../constants/appConstants';
19
import { DEPLOYMENT_STATES } from '../constants/deploymentConstants';
20
import {
21
  ALL_DEVICES,
22
  ATTRIBUTE_SCOPES,
23
  DEVICE_ISSUE_OPTIONS,
24
  DEVICE_LIST_MAXIMUM_LENGTH,
25
  DEVICE_ONLINE_CUTOFF,
26
  DEVICE_STATES,
27
  EXTERNAL_PROVIDER,
28
  UNGROUPED_GROUP
29
} from '../constants/deviceConstants';
30
import { READ_STATES, rolesByName, twoFAStates, uiPermissionsById } from '../constants/userConstants';
31
import { attributeDuplicateFilter, duplicateFilter, getDemoDeviceAddress as getDemoDeviceAddressHelper, versionCompare } from '../helpers';
32
import { onboardingSteps } from '../utils/onboardingmanager';
33

34
const getAppDocsVersion = state => state.app.docsVersion;
1,312✔
35
export const getFeatures = state => state.app.features;
18,831✔
36
export const getTooltipsById = state => state.users.tooltips.byId;
2,492✔
37
export const getRolesById = state => state.users.rolesById;
1,619✔
38
export const getOrganization = state => state.organization.organization;
6,655✔
39
export const getAcceptedDevices = state => state.devices.byStatus.accepted;
7,024✔
40
const getDevicesByStatus = state => state.devices.byStatus;
1,355✔
41
export const getDevicesById = state => state.devices.byId;
26,099✔
42
export const getDeviceReports = state => state.devices.reports;
3,513✔
43
export const getGroupsById = state => state.devices.groups.byId;
2,850✔
44
const getSelectedGroup = state => state.devices.groups.selectedGroup;
184✔
45
const getSearchedDevices = state => state.app.searchState.deviceIds;
1,036✔
46
const getListedDevices = state => state.devices.deviceList.deviceIds;
184✔
47
const getFilteringAttributes = state => state.devices.filteringAttributes;
1,167✔
48
export const getDeviceFilters = state => state.devices.filters || [];
197!
49
const getFilteringAttributesFromConfig = state => state.devices.filteringAttributesConfig.attributes;
1,138✔
50
export const getSortedFilteringAttributes = createSelector([getFilteringAttributes], filteringAttributes => ({
184✔
51
  ...filteringAttributes,
52
  identityAttributes: [...filteringAttributes.identityAttributes, 'id']
53
}));
54
export const getDeviceLimit = state => state.devices.limit;
1,950✔
55
const getDevicesList = state => Object.values(state.devices.byId);
184✔
56
const getOnboarding = state => state.onboarding;
1,495✔
57
export const getGlobalSettings = state => state.users.globalSettings;
3,526✔
58
const getIssueCountsByType = state => state.monitor.issueCounts.byType;
1,145✔
59
const getSelectedReleaseId = state => state.releases.selectedRelease;
184✔
60
export const getReleasesById = state => state.releases.byId;
1,494✔
61
export const getReleaseTags = state => state.releases.tags;
256✔
62
export const getReleaseListState = state => state.releases.releasesList;
848✔
63
const getListedReleases = state => state.releases.releasesList.releaseIds;
184✔
64
export const getUpdateTypes = state => state.releases.updateTypes;
184✔
65
export const getExternalIntegrations = state => state.organization.externalDeviceIntegrations;
184✔
66
const getDeploymentsById = state => state.deployments.byId;
2,333✔
67
export const getDeploymentsByStatus = state => state.deployments.byStatus;
1,893✔
68
export const getFullVersionInformation = state => state.app.versionInformation;
1,073✔
69
const getCurrentUserId = state => state.users.currentUser;
2,836✔
70
const getUsersById = state => state.users.byId;
1,698✔
71
export const getCurrentUser = createSelector([getUsersById, getCurrentUserId], (usersById, userId) => usersById[userId] ?? {});
184✔
72
export const getUserSettings = state => state.users.userSettings;
10,491✔
73

74
export const getVersionInformation = createSelector([getFullVersionInformation, getFeatures], ({ Integration, ...remainder }, { isHosted }) =>
184✔
75
  isHosted && Integration !== 'next' ? remainder : { ...remainder, Integration }
16!
76
);
77
export const getIsPreview = createSelector([getFullVersionInformation], ({ Integration }) => versionCompare(Integration, 'next') > -1);
184✔
78

79
export const getShowHelptips = createSelector([getTooltipsById], tooltips =>
184✔
80
  Object.values(tooltips).reduce((accu, { readState }) => accu || readState === READ_STATES.unread, false)
4!
81
);
82

83
export const getDeploymentsSelectionState = state => state.deployments.selectionState;
1,973✔
84

85
export const getMappedDeploymentSelection = createSelector(
184✔
86
  [getDeploymentsSelectionState, (_, deploymentsState) => deploymentsState, getDeploymentsById],
1,179✔
87
  (selectionState, deploymentsState, deploymentsById) => {
88
    const { selection = [] } = selectionState[deploymentsState] ?? {};
1,148!
89
    return selection.reduce((accu, id) => {
1,148✔
90
      if (deploymentsById[id]) {
2,085!
91
        accu.push(deploymentsById[id]);
2,085✔
92
      }
93
      return accu;
2,085✔
94
    }, []);
95
  }
96
);
97

98
export const getDeploymentRelease = createSelector(
184✔
99
  [getDeploymentsById, getDeploymentsSelectionState, getReleasesById],
100
  (deploymentsById, { selectedId }, releasesById) => {
101
    const deployment = deploymentsById[selectedId] || {};
3✔
102
    return deployment.artifact_name && releasesById[deployment.artifact_name] ? releasesById[deployment.artifact_name] : { device_types_compatible: [] };
3✔
103
  }
104
);
105

106
export const getHas2FA = createSelector(
184✔
107
  [getCurrentUser],
108
  currentUser => currentUser.hasOwnProperty('tfa_status') && currentUser.tfa_status === twoFAStates.enabled
5✔
109
);
110

111
export const getDemoDeviceAddress = createSelector([getDevicesList, getOnboarding], (devices, { approach, demoArtifactPort }) => {
184✔
112
  const demoDeviceAddress = `http://${getDemoDeviceAddressHelper(devices, approach)}`;
3✔
113
  return demoArtifactPort ? `${demoDeviceAddress}:${demoArtifactPort}` : demoDeviceAddress;
3!
114
});
115

116
export const getDeviceReportsForUser = createSelector(
184✔
117
  [getUserSettings, getCurrentUserId, getGlobalSettings, getDevicesById],
118
  ({ reports }, currentUserId, globalSettings, devicesById) => {
119
    return reports || globalSettings[`${currentUserId}-reports`] || (Object.keys(devicesById).length ? defaultReports : []);
47✔
120
  }
121
);
122

123
const listItemMapper = (byId, ids, { defaultObject = {}, cutOffSize = DEVICE_LIST_MAXIMUM_LENGTH }) => {
184✔
124
  return ids.slice(0, cutOffSize).reduce((accu, id) => {
1,101✔
125
    if (id && byId[id]) {
308!
126
      accu.push({ ...defaultObject, ...byId[id] });
308✔
127
    }
128
    return accu;
308✔
129
  }, []);
130
};
131

132
const listTypeDeviceIdMap = {
184✔
133
  deviceList: getListedDevices,
134
  search: getSearchedDevices
135
};
136
const getDeviceMappingDefaults = () => ({ defaultObject: { auth_sets: [] }, cutOffSize: DEVICE_LIST_MAXIMUM_LENGTH });
1,063✔
137
export const getMappedDevicesList = createSelector(
184✔
138
  [getDevicesById, (state, listType) => listTypeDeviceIdMap[listType](state), getDeviceMappingDefaults],
1,063✔
139
  listItemMapper
140
);
141

142
export const getDeviceCountsByStatus = createSelector([getDevicesByStatus], byStatus =>
184✔
143
  Object.values(DEVICE_STATES).reduce((accu, state) => {
411✔
144
    accu[state] = byStatus[state].total || 0;
1,644✔
145
    return accu;
1,644✔
146
  }, {})
147
);
148

149
export const getDeviceById = createSelector([getDevicesById, (_, deviceId) => deviceId], (devicesById, deviceId = '') => devicesById[deviceId] ?? {});
1,550✔
150

151
export const getDeviceConfigDeployment = createSelector([getDeviceById, getDeploymentsById], (device, deploymentsById) => {
184✔
152
  const { config = {} } = device;
7✔
153
  const { deployment_id: configDeploymentId } = config;
7✔
154
  const deviceConfigDeployment = deploymentsById[configDeploymentId] || {};
7✔
155
  return { device, deviceConfigDeployment };
7✔
156
});
157

158
export const getSelectedGroupInfo = createSelector(
184✔
159
  [getAcceptedDevices, getGroupsById, getSelectedGroup],
160
  ({ total: acceptedDeviceTotal }, groupsById, selectedGroup) => {
161
    let groupCount = acceptedDeviceTotal;
7✔
162
    let groupFilters = [];
7✔
163
    if (selectedGroup && groupsById[selectedGroup]) {
7✔
164
      groupCount = groupsById[selectedGroup].total;
2✔
165
      groupFilters = groupsById[selectedGroup].filters || [];
2!
166
    }
167
    return { groupCount, selectedGroup, groupFilters };
7✔
168
  }
169
);
170

171
const defaultIdAttribute = Object.freeze({ attribute: 'id', scope: ATTRIBUTE_SCOPES.identity });
184✔
172
export const getIdAttribute = createSelector([getGlobalSettings], ({ id_attribute = { ...defaultIdAttribute } }) => id_attribute);
184✔
173

174
export const getLimitMaxed = createSelector([getAcceptedDevices, getDeviceLimit], ({ total: acceptedDevices = 0 }, deviceLimit) =>
184!
175
  Boolean(deviceLimit && deviceLimit <= acceptedDevices)
7✔
176
);
177

178
export const getFilterAttributes = createSelector(
184✔
179
  [getGlobalSettings, getFilteringAttributes],
180
  ({ previousFilters }, { identityAttributes, inventoryAttributes, systemAttributes, tagAttributes }) => {
181
    const deviceNameAttribute = { key: 'name', value: 'Name', scope: ATTRIBUTE_SCOPES.tags, category: ATTRIBUTE_SCOPES.tags, priority: 1 };
3✔
182
    const deviceIdAttribute = { key: 'id', value: 'Device ID', scope: ATTRIBUTE_SCOPES.identity, category: ATTRIBUTE_SCOPES.identity, priority: 1 };
3✔
183
    const checkInAttribute = { key: 'check_in_time', value: 'Latest activity', scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 };
3✔
184
    const updateAttribute = { ...checkInAttribute, key: 'updated_ts', value: 'Last inventory update' };
3✔
185
    const firstRequestAttribute = { key: 'created_ts', value: 'First request', scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 };
3✔
186
    const attributes = [
3✔
187
      ...previousFilters.map(item => ({
×
188
        ...item,
189
        value: deviceIdAttribute.key === item.key ? deviceIdAttribute.value : item.key,
×
190
        category: 'recently used',
191
        priority: 0
192
      })),
193
      deviceNameAttribute,
194
      deviceIdAttribute,
195
      ...identityAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.identity, category: ATTRIBUTE_SCOPES.identity, priority: 1 })),
3✔
196
      ...inventoryAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.inventory, category: ATTRIBUTE_SCOPES.inventory, priority: 2 })),
3✔
197
      ...tagAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.tags, category: ATTRIBUTE_SCOPES.tags, priority: 3 })),
×
198
      checkInAttribute,
199
      updateAttribute,
200
      firstRequestAttribute,
201
      ...systemAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 }))
×
202
    ];
203
    return attributeDuplicateFilter(attributes, 'key');
3✔
204
  }
205
);
206

207
// eslint-disable-next-line no-unused-vars
208
export const getGroupsByIdWithoutUngrouped = createSelector([getGroupsById], ({ [UNGROUPED_GROUP.id]: ungrouped, ...groups }) => groups);
184✔
209

210
export const getGroups = createSelector([getGroupsById], groupsById => {
184✔
211
  const groupNames = Object.keys(groupsById).sort();
13✔
212
  const groupedGroups = Object.entries(groupsById)
13✔
213
    .sort((a, b) => a[0].localeCompare(b[0]))
40✔
214
    .reduce(
215
      (accu, [groupname, group]) => {
216
        const name = groupname === UNGROUPED_GROUP.id ? UNGROUPED_GROUP.name : groupname;
37✔
217
        const groupItem = { ...group, groupId: name, name: groupname };
37✔
218
        if (group.filters.length > 0) {
37✔
219
          if (groupname !== UNGROUPED_GROUP.id) {
24✔
220
            accu.dynamic.push(groupItem);
13✔
221
          } else {
222
            accu.ungrouped.push(groupItem);
11✔
223
          }
224
        } else {
225
          accu.static.push(groupItem);
13✔
226
        }
227
        return accu;
37✔
228
      },
229
      { dynamic: [], static: [], ungrouped: [] }
230
    );
231
  return { groupNames, ...groupedGroups };
13✔
232
});
233

234
export const getDeviceTwinIntegrations = createSelector([getExternalIntegrations], integrations =>
184✔
235
  integrations.filter(integration => integration.id && EXTERNAL_PROVIDER[integration.provider]?.deviceTwin)
4✔
236
);
237

238
export const getOfflineThresholdSettings = createSelector([getGlobalSettings], ({ offlineThreshold }) => ({
184✔
239
  interval: offlineThreshold?.interval || DEVICE_ONLINE_CUTOFF.interval,
26✔
240
  intervalUnit: offlineThreshold?.intervalUnit || DEVICE_ONLINE_CUTOFF.intervalName
26✔
241
}));
242

243
export const getOnboardingState = createSelector([getOnboarding, getUserSettings], ({ complete, progress, showTips, ...remainder }, { onboarding = {} }) => ({
184!
244
  ...remainder,
245
  ...onboarding,
246
  complete: onboarding.complete || complete,
167✔
247
  progress:
248
    Object.keys(onboardingSteps).findIndex(step => step === progress) > Object.keys(onboardingSteps).findIndex(step => step === onboarding.progress)
1,382✔
249
      ? progress
250
      : onboarding.progress,
251
  showTips: !onboarding.showTips ? onboarding.showTips : showTips
85✔
252
}));
253

254
export const getTooltipsState = createSelector([getTooltipsById, getUserSettings], (byId, { tooltips = {} }) =>
184✔
255
  Object.entries(byId).reduce(
54✔
256
    (accu, [id, value]) => {
257
      accu[id] = { ...accu[id], ...value };
×
258
      return accu;
×
259
    },
260
    { ...tooltips }
261
  )
262
);
263

264
export const getDocsVersion = createSelector([getAppDocsVersion, getFeatures], (appDocsVersion, { isHosted }) => {
184✔
265
  // if hosted, use latest docs version
266
  const docsVersion = appDocsVersion ? `${appDocsVersion}/` : 'development/';
41!
267
  return isHosted ? '' : docsVersion;
41✔
268
});
269

270
export const getIsEnterprise = createSelector(
184✔
271
  [getOrganization, getFeatures],
272
  ({ plan = PLANS.os.id }, { isEnterprise, isHosted }) => isEnterprise || (isHosted && plan === PLANS.enterprise.id)
81✔
273
);
274

275
export const getAttributesList = createSelector(
184✔
276
  [getFilteringAttributes, getFilteringAttributesFromConfig],
277
  ({ identityAttributes = [], inventoryAttributes = [] }, { identity = [], inventory = [] }) =>
40!
278
    [...identityAttributes, ...inventoryAttributes, ...identity, ...inventory].filter(duplicateFilter)
20✔
279
);
280

281
export const getRolesList = createSelector([getRolesById], rolesById => Object.entries(rolesById).map(([id, role]) => ({ id, ...role })));
184✔
282

283
export const getUserRoles = createSelector(
184✔
284
  [getCurrentUser, getRolesById, getIsEnterprise, getFeatures, getOrganization],
285
  (currentUser, rolesById, isEnterprise, { isHosted, hasMultitenancy }, { plan = PLANS.os.id }) => {
9✔
286
    const isAdmin = currentUser.roles?.length
69✔
287
      ? currentUser.roles.some(role => role === rolesByName.admin)
62✔
288
      : !(hasMultitenancy || isEnterprise || (isHosted && plan !== PLANS.os.id));
15!
289
    const uiPermissions = isAdmin
69✔
290
      ? mapUserRolesToUiPermissions([rolesByName.admin], rolesById)
291
      : mapUserRolesToUiPermissions(currentUser.roles || [], rolesById);
6✔
292
    return { isAdmin, uiPermissions };
69✔
293
  }
294
);
295

296
const hasPermission = (thing, permission) => Object.values(thing).some(permissions => permissions.includes(permission));
319✔
297

298
export const getUserCapabilities = createSelector([getUserRoles], ({ uiPermissions }) => {
184✔
299
  const canManageReleases = hasPermission(uiPermissions.releases, uiPermissionsById.manage.value);
62✔
300
  const canReadReleases = canManageReleases || hasPermission(uiPermissions.releases, uiPermissionsById.read.value);
62✔
301
  const canUploadReleases = canManageReleases || hasPermission(uiPermissions.releases, uiPermissionsById.upload.value);
62✔
302

303
  const canAuditlog = uiPermissions.auditlog.includes(uiPermissionsById.read.value);
62✔
304

305
  const canReadUsers = uiPermissions.userManagement.includes(uiPermissionsById.read.value);
62✔
306
  const canManageUsers = uiPermissions.userManagement.includes(uiPermissionsById.manage.value);
62✔
307

308
  const canReadDevices = hasPermission(uiPermissions.groups, uiPermissionsById.read.value);
62✔
309
  const canWriteDevices = Object.values(uiPermissions.groups).some(
62✔
310
    groupPermissions => groupPermissions.includes(uiPermissionsById.read.value) && groupPermissions.length > 1
59✔
311
  );
312
  const canTroubleshoot = hasPermission(uiPermissions.groups, uiPermissionsById.connect.value);
62✔
313
  const canManageDevices = hasPermission(uiPermissions.groups, uiPermissionsById.manage.value);
62✔
314
  const canConfigure = hasPermission(uiPermissions.groups, uiPermissionsById.configure.value);
62✔
315

316
  const canDeploy = uiPermissions.deployments.includes(uiPermissionsById.deploy.value) || hasPermission(uiPermissions.groups, uiPermissionsById.deploy.value);
62✔
317
  const canReadDeployments = uiPermissions.deployments.includes(uiPermissionsById.read.value);
62✔
318

319
  return {
62✔
320
    canAuditlog,
321
    canConfigure,
322
    canDeploy,
323
    canManageDevices,
324
    canManageReleases,
325
    canManageUsers,
326
    canReadDeployments,
327
    canReadDevices,
328
    canReadReleases,
329
    canReadUsers,
330
    canTroubleshoot,
331
    canUploadReleases,
332
    canWriteDevices,
333
    groupsPermissions: uiPermissions.groups,
334
    releasesPermissions: uiPermissions.releases
335
  };
336
});
337

338
export const getTenantCapabilities = createSelector(
184✔
339
  [getFeatures, getOrganization, getIsEnterprise],
340
  (
341
    {
342
      hasAddons,
343
      hasAuditlogs: isAuditlogEnabled,
344
      hasDeviceConfig: isDeviceConfigEnabled,
345
      hasDeviceConnect: isDeviceConnectEnabled,
346
      hasMonitor: isMonitorEnabled,
347
      isHosted
348
    },
349
    { addons = [], plan = PLANS.os.id },
12✔
350
    isEnterprise
351
  ) => {
352
    const canDelta = isEnterprise || plan === PLANS.professional.id;
47✔
353
    const hasAuditlogs = isAuditlogEnabled && (!isHosted || isEnterprise || plan === PLANS.professional.id);
47!
354
    const hasDeviceConfig = hasAddons || (isDeviceConfigEnabled && (!isHosted || addons.some(addon => addon.name === 'configure' && Boolean(addon.enabled))));
47!
355
    const hasDeviceConnect =
356
      hasAddons || (isDeviceConnectEnabled && (!isHosted || addons.some(addon => addon.name === 'troubleshoot' && Boolean(addon.enabled))));
47!
357
    const hasMonitor = hasAddons || (isMonitorEnabled && (!isHosted || addons.some(addon => addon.name === 'monitor' && Boolean(addon.enabled))));
47!
358
    return {
47✔
359
      canDelta,
360
      canRetry: canDelta,
361
      canSchedule: canDelta,
362
      hasAuditlogs,
363
      hasDeviceConfig,
364
      hasDeviceConnect,
365
      hasFullFiltering: canDelta,
366
      hasMonitor,
367
      isEnterprise,
368
      plan
369
    };
370
  }
371
);
372

373
export const getAvailableIssueOptionsByType = createSelector(
184✔
374
  [getFeatures, getTenantCapabilities, getIssueCountsByType],
375
  ({ hasReporting }, { hasFullFiltering, hasMonitor }, issueCounts) =>
376
    Object.values(DEVICE_ISSUE_OPTIONS).reduce((accu, { isCategory, key, needsFullFiltering, needsMonitor, needsReporting, title }) => {
17✔
377
      if (isCategory || (needsReporting && !hasReporting) || (needsFullFiltering && !hasFullFiltering) || (needsMonitor && !hasMonitor)) {
102✔
378
        return accu;
96✔
379
      }
380
      accu[key] = { count: issueCounts[key].filtered, key, title };
6✔
381
      return accu;
6✔
382
    }, {})
383
);
384

385
export const getDeviceTypes = createSelector([getAcceptedDevices, getDevicesById], ({ deviceIds = [] }, devicesById) =>
184!
386
  Object.keys(
1✔
387
    deviceIds.slice(0, 200).reduce((accu, item) => {
388
      const { device_type: deviceTypes = [] } = devicesById[item] ? devicesById[item].attributes : {};
2!
389
      accu = deviceTypes.reduce((deviceTypeAccu, deviceType) => {
2✔
390
        if (deviceType.length > 1) {
2!
391
          deviceTypeAccu[deviceType] = deviceTypeAccu[deviceType] ? deviceTypeAccu[deviceType] + 1 : 1;
2!
392
        }
393
        return deviceTypeAccu;
2✔
394
      }, accu);
395
      return accu;
2✔
396
    }, {})
397
  )
398
);
399

400
export const getGroupNames = createSelector([getGroupsById, getUserRoles, (_, options = {}) => options], (groupsById, { uiPermissions }, { staticOnly }) => {
1,367✔
401
  // eslint-disable-next-line no-unused-vars
402
  const { [UNGROUPED_GROUP.id]: ungrouped, ...groups } = groupsById;
1,367✔
403
  if (staticOnly) {
1,367!
404
    return Object.keys(uiPermissions.groups).sort();
×
405
  }
406
  return Object.keys(
1,367✔
407
    Object.entries(groups).reduce((accu, [groupName, group]) => {
408
      if (group.filter || uiPermissions.groups[ALL_DEVICES]) {
2,686✔
409
        accu[groupName] = group;
1,758✔
410
      }
411
      return accu;
2,686✔
412
    }, uiPermissions.groups)
413
  ).sort();
414
});
415

416
const getReleaseMappingDefaults = () => ({});
184✔
417
export const getReleasesList = createSelector([getReleasesById, getListedReleases, getReleaseMappingDefaults], listItemMapper);
184✔
418

419
export const getReleaseTagsById = createSelector([getReleaseTags], releaseTags => releaseTags.reduce((accu, key) => ({ ...accu, [key]: key }), {}));
184✔
420
export const getHasReleases = createSelector(
184✔
421
  [getReleaseListState, getReleasesById],
422
  ({ searchTotal, total }, byId) => !!(Object.keys(byId).length || total || searchTotal)
32!
423
);
424

425
export const getSelectedRelease = createSelector([getReleasesById, getSelectedReleaseId], (byId, id) => byId[id] ?? {});
184✔
426

427
const relevantDeploymentStates = [DEPLOYMENT_STATES.pending, DEPLOYMENT_STATES.inprogress, DEPLOYMENT_STATES.finished];
184✔
428
export const DEPLOYMENT_CUTOFF = 3;
184✔
429
export const getRecentDeployments = createSelector([getDeploymentsById, getDeploymentsByStatus], (deploymentsById, deploymentsByStatus) =>
184✔
430
  Object.entries(deploymentsByStatus).reduce(
409✔
431
    (accu, [state, byStatus]) => {
432
      if (!relevantDeploymentStates.includes(state) || !byStatus.deploymentIds.length) {
1,636✔
433
        return accu;
433✔
434
      }
435
      accu[state] = byStatus.deploymentIds
1,203✔
436
        .reduce((accu, id) => {
437
          if (deploymentsById[id]) {
2,340✔
438
            accu.push(deploymentsById[id]);
2,334✔
439
          }
440
          return accu;
2,340✔
441
        }, [])
442
        .slice(0, DEPLOYMENT_CUTOFF);
443
      accu.total += byStatus.total;
1,203✔
444
      return accu;
1,203✔
445
    },
446
    { total: 0 }
447
  )
448
);
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