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

mendersoftware / mender-server / 1539946168

13 Nov 2024 09:11AM UTC coverage: 72.77% (+0.03%) from 72.743%
1539946168

push

gitlab-ci

web-flow
Merge pull request #191 from mzedel/MEN-7570

MEN-7570 - SP RBAC + ensured linter runs on all TS files

4170 of 6064 branches covered (68.77%)

Branch coverage included in aggregate %.

182 of 199 new or added lines in 18 files covered. (91.46%)

2 existing lines in 1 file now uncovered.

42469 of 58027 relevant lines covered (73.19%)

16.79 hits per line

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

88.29
/frontend/src/js/store/commonSelectors.ts
1
// Copyright 2024 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
// @ts-nocheck
15
import { attributeDuplicateFilter, getDemoDeviceAddress as getDemoDeviceAddressHelper } from '@northern.tech/utils/helpers';
16
import { createSelector } from '@reduxjs/toolkit';
17

18
import { PLANS, defaultReports } from './appSlice/constants';
19
import { getFeatures, getSearchedDevices } from './appSlice/selectors';
20
import { ALL_DEVICES, ATTRIBUTE_SCOPES, DEVICE_ISSUE_OPTIONS, DEVICE_LIST_MAXIMUM_LENGTH, UNGROUPED_GROUP } from './commonConstants';
21
import { getDeploymentsById, getDeploymentsSelectionState, getSelectedDeploymentDeviceIds } from './deploymentsSlice/selectors';
22
import { inventoryApiUrlV2, reportingApiUrl } from './devicesSlice/constants';
23
import { getDeviceById, getDevicesById, getFilteringAttributes, getGroupsById, getListedDevices } from './devicesSlice/selectors';
24
import { getIssueCountsByType } from './monitorSlice/selectors';
25
import { onboardingSteps } from './onboardingSlice/constants';
26
import { getOnboarding } from './onboardingSlice/selectors';
27
import { getAuditLogEntry, getIsServiceProvider, getOrganization } from './organizationSlice/selectors';
28
import { getReleasesById } from './releasesSlice/selectors';
29
import { rolesById, rolesByName, serviceProviderRolesById, uiPermissionsById } from './usersSlice/constants';
30
import { getCurrentUser, getGlobalSettings, getRolesById, getRolesList, getUserSettings } from './usersSlice/selectors';
31
import { listItemMapper, mapUserRolesToUiPermissions } from './utils';
32

33
export const getIsEnterprise = createSelector(
127✔
34
  [getOrganization, getFeatures],
35
  ({ plan = PLANS.os.id }, { isEnterprise, isHosted }) => isEnterprise || (isHosted && plan === PLANS.enterprise.id)
91✔
36
);
37

38
export const getAttrsEndpoint = createSelector([getFeatures], ({ hasReporting }) =>
127✔
39
  hasReporting ? `${reportingApiUrl}/devices/search/attributes` : `${inventoryApiUrlV2}/filters/attributes`
17✔
40
);
41
export const getSearchEndpoint = createSelector([getFeatures], ({ hasReporting }) =>
127✔
42
  hasReporting ? `${reportingApiUrl}/devices/search` : `${inventoryApiUrlV2}/filters/search`
33!
43
);
44

45
export const getUserRoles = createSelector(
127✔
46
  [getCurrentUser, getRolesById, getIsEnterprise, getFeatures, getOrganization],
47
  (currentUser, rolesById, isEnterprise, { isHosted, hasMultitenancy }, { plan = PLANS.os.id }) => {
8✔
48
    const isAdmin = currentUser.roles?.length
81✔
49
      ? currentUser.roles.some(role => role === rolesByName.admin)
74✔
50
      : !(hasMultitenancy || isEnterprise || (isHosted && plan !== PLANS.os.id));
15!
51
    const uiPermissions = isAdmin
81✔
52
      ? mapUserRolesToUiPermissions([rolesByName.admin], rolesById)
53
      : mapUserRolesToUiPermissions(currentUser.roles || [], rolesById);
6✔
54
    return { isAdmin, uiPermissions };
81✔
55
  }
56
);
57

58
const hasPermission = (thing, permission) => Object.values(thing).some(permissions => permissions.includes(permission));
394✔
59

60
export const getUserCapabilities = createSelector([getUserRoles, getIsServiceProvider], ({ uiPermissions }, isServiceProvider) => {
127✔
61
  const canManageReleases = hasPermission(uiPermissions.releases, uiPermissionsById.manage.value);
77✔
62
  const canReadReleases = canManageReleases || hasPermission(uiPermissions.releases, uiPermissionsById.read.value);
77✔
63
  const canUploadReleases = canManageReleases || hasPermission(uiPermissions.releases, uiPermissionsById.upload.value);
77✔
64

65
  const canAuditlog = uiPermissions.auditlog.includes(uiPermissionsById.read.value);
77✔
66

67
  const canReadUsers = uiPermissions.userManagement.includes(uiPermissionsById.read.value);
77✔
68
  const canManageUsers = uiPermissions.userManagement.includes(uiPermissionsById.manage.value);
77✔
69

70
  const canReadDevices = hasPermission(uiPermissions.groups, uiPermissionsById.read.value);
77✔
71
  const canWriteDevices = Object.values(uiPermissions.groups).some(
77✔
72
    groupPermissions => groupPermissions.includes(uiPermissionsById.read.value) && groupPermissions.length > 1
74✔
73
  );
74
  const canTroubleshoot = hasPermission(uiPermissions.groups, uiPermissionsById.connect.value);
77✔
75
  const canManageDevices = hasPermission(uiPermissions.groups, uiPermissionsById.manage.value);
77✔
76
  const canConfigure = hasPermission(uiPermissions.groups, uiPermissionsById.configure.value);
77✔
77

78
  const canDeploy = uiPermissions.deployments.includes(uiPermissionsById.deploy.value) || hasPermission(uiPermissions.groups, uiPermissionsById.deploy.value);
77✔
79
  const canReadDeployments = uiPermissions.deployments.includes(uiPermissionsById.read.value);
77✔
80

81
  return {
77✔
82
    canAuditlog,
83
    canConfigure,
84
    canDeploy,
85
    canManageDevices,
86
    canManageReleases,
87
    canManageUsers,
88
    canReadDeployments,
89
    canReadDevices,
90
    canReadReleases,
91
    canReadUsers,
92
    canTroubleshoot,
93
    canUploadReleases,
94
    canWriteDevices,
95
    groupsPermissions: uiPermissions.groups,
96
    releasesPermissions: uiPermissions.releases,
97
    SPTenant: isServiceProvider
98
  };
99
});
100

101
export const getTenantCapabilities = createSelector(
127✔
102
  [getFeatures, getOrganization, getIsEnterprise],
103
  (
104
    { hasAuditlogs: isAuditlogEnabled, hasDeviceConfig: isDeviceConfigEnabled, hasDeviceConnect: isDeviceConnectEnabled, hasMonitor: isMonitorEnabled },
105
    { addons = [], plan = PLANS.os.id },
8✔
106
    isEnterprise
107
  ) => {
108
    const canDelta = isEnterprise || plan === PLANS.professional.id;
53✔
109
    const hasAuditlogs = isAuditlogEnabled && isEnterprise;
53✔
110
    const hasDeviceConfig = isDeviceConfigEnabled && addons.some(addon => addon.name === 'configure' && Boolean(addon.enabled));
53✔
111
    const hasDeviceConnect = isDeviceConnectEnabled && (!isEnterprise || addons.some(addon => addon.name === 'troubleshoot' && Boolean(addon.enabled)));
53✔
112
    const hasMonitor = isMonitorEnabled && addons.some(addon => addon.name === 'monitor' && Boolean(addon.enabled));
53✔
113
    return {
53✔
114
      canDelta,
115
      canRetry: canDelta,
116
      canSchedule: canDelta,
117
      hasAuditlogs,
118
      hasDeviceConfig,
119
      hasDeviceConnect,
120
      hasFullFiltering: canDelta,
121
      hasMonitor,
122
      isEnterprise,
123
      plan
124
    };
125
  }
126
);
127

128
export const getFilterAttributes = createSelector(
127✔
129
  [getGlobalSettings, getFilteringAttributes],
130
  ({ previousFilters }, { identityAttributes, inventoryAttributes, systemAttributes, tagAttributes }) => {
131
    const deviceNameAttribute = { key: 'name', value: 'Name', scope: ATTRIBUTE_SCOPES.tags, category: ATTRIBUTE_SCOPES.tags, priority: 1 };
3✔
132
    const deviceIdAttribute = { key: 'id', value: 'Device ID', scope: ATTRIBUTE_SCOPES.identity, category: ATTRIBUTE_SCOPES.identity, priority: 1 };
3✔
133
    const checkInAttribute = { key: 'check_in_time', value: 'Latest activity', scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 };
3✔
134
    const updateAttribute = { ...checkInAttribute, key: 'updated_ts', value: 'Last inventory update' };
3✔
135
    const firstRequestAttribute = { key: 'created_ts', value: 'First request', scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 };
3✔
136
    const attributes = [
3✔
137
      ...previousFilters.map(item => ({
×
138
        ...item,
139
        value: deviceIdAttribute.key === item.key ? deviceIdAttribute.value : item.key,
×
140
        category: 'recently used',
141
        priority: 0
142
      })),
143
      deviceNameAttribute,
144
      deviceIdAttribute,
145
      ...identityAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.identity, category: ATTRIBUTE_SCOPES.identity, priority: 1 })),
3✔
146
      ...inventoryAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.inventory, category: ATTRIBUTE_SCOPES.inventory, priority: 2 })),
3✔
147
      ...tagAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.tags, category: ATTRIBUTE_SCOPES.tags, priority: 3 })),
×
148
      checkInAttribute,
149
      updateAttribute,
150
      firstRequestAttribute,
151
      ...systemAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 }))
×
152
    ];
153
    return attributeDuplicateFilter(attributes, 'key');
3✔
154
  }
155
);
156

157
export const getOnboardingState = createSelector([getOnboarding, getUserSettings], ({ complete, progress, showTips, ...remainder }, { onboarding = {} }) => ({
127!
158
  ...remainder,
159
  ...onboarding,
160
  complete: onboarding.complete || complete,
184✔
161
  progress:
162
    Object.keys(onboardingSteps).findIndex(step => step === progress) > Object.keys(onboardingSteps).findIndex(step => step === onboarding.progress)
1,674!
163
      ? progress
164
      : onboarding.progress,
165
  showTips: !onboarding.showTips ? onboarding.showTips : showTips
93✔
166
}));
167

168
export const getDemoDeviceAddress = createSelector([getDevicesById, getOnboarding], (devicesById, { approach, demoArtifactPort }) => {
127✔
169
  const demoDeviceAddress = `http://${getDemoDeviceAddressHelper(Object.values(devicesById), approach)}`;
4✔
170
  return demoArtifactPort ? `${demoDeviceAddress}:${demoArtifactPort}` : demoDeviceAddress;
4!
171
});
172

173
export const getDeviceConfigDeployment = createSelector([getDeviceById, getDeploymentsById], (device, deploymentsById) => {
127✔
174
  const { config = {} } = device;
15✔
175
  const { deployment_id: configDeploymentId } = config;
15✔
176
  const deviceConfigDeployment = deploymentsById[configDeploymentId] || {};
15✔
177
  return { device, deviceConfigDeployment };
15✔
178
});
179

180
export const getDeploymentRelease = createSelector(
127✔
181
  [getDeploymentsById, getDeploymentsSelectionState, getReleasesById],
182
  (deploymentsById, { selectedId }, releasesById) => {
183
    const deployment = deploymentsById[selectedId] || {};
3✔
184
    return deployment.artifact_name && releasesById[deployment.artifact_name] ? releasesById[deployment.artifact_name] : { device_types_compatible: [] };
3✔
185
  }
186
);
187

188
export const getSelectedDeploymentData = createSelector(
127✔
189
  [getDeploymentsById, getDeploymentsSelectionState, getDevicesById, getSelectedDeploymentDeviceIds],
190
  (deploymentsById, { selectedId }, devicesById, selectedDeviceIds) => {
191
    const deployment = deploymentsById[selectedId] ?? {};
3✔
192
    const { devices = {} } = deployment;
3✔
193
    return {
3✔
194
      deployment,
195
      selectedDevices: selectedDeviceIds.map(deviceId => ({ ...devicesById[deviceId], ...devices[deviceId] }))
1✔
196
    };
197
  }
198
);
199

200
export const getAvailableIssueOptionsByType = createSelector(
127✔
201
  [getFeatures, getTenantCapabilities, getIssueCountsByType],
202
  ({ hasReporting }, { hasFullFiltering, hasMonitor }, issueCounts) =>
203
    Object.values(DEVICE_ISSUE_OPTIONS).reduce((accu, { isCategory, key, needsFullFiltering, needsMonitor, needsReporting, title }) => {
17✔
204
      if (isCategory || (needsReporting && !hasReporting) || (needsFullFiltering && !hasFullFiltering) || (needsMonitor && !hasMonitor)) {
102✔
205
        return accu;
100✔
206
      }
207
      accu[key] = { count: issueCounts[key].filtered, key, title };
2✔
208
      return accu;
2✔
209
    }, {})
210
);
211

212
export const getGroupNames = createSelector([getGroupsById, getUserRoles, (_, options = {}) => options], (groupsById, { uiPermissions }, { staticOnly }) => {
168✔
213
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
214
  const { [UNGROUPED_GROUP.id]: ungrouped, ...groups } = groupsById;
164✔
215
  if (staticOnly) {
164!
216
    return Object.keys(uiPermissions.groups).sort();
×
217
  }
218
  return Object.keys(
164✔
219
    Object.entries(groups).reduce((accu, [groupName, group]) => {
220
      if (group.filterId || uiPermissions.groups[ALL_DEVICES]) {
328!
221
        accu[groupName] = group;
328✔
222
      }
223
      return accu;
328✔
224
    }, uiPermissions.groups)
225
  ).sort();
226
});
227

228
export const getDeviceReportsForUser = createSelector(
127✔
229
  [getUserSettings, getCurrentUser, getGlobalSettings, getDevicesById],
230
  ({ reports }, { id: currentUserId }, globalSettings, devicesById) => {
231
    return reports || globalSettings[`${currentUserId}-reports`] || (Object.keys(devicesById).length ? defaultReports : []);
103✔
232
  }
233
);
234

235
const listTypeDeviceIdMap = {
127✔
236
  deviceList: getListedDevices,
237
  search: getSearchedDevices
238
};
239
const deviceMapDefault = { defaultObject: { auth_sets: [] }, cutOffSize: DEVICE_LIST_MAXIMUM_LENGTH };
127✔
240
const getDeviceMappingDefaults = () => deviceMapDefault;
1,420✔
241

242
export const getMappedDevicesList = createSelector(
127✔
243
  [getDevicesById, (state, listType) => listTypeDeviceIdMap[listType](state), getDeviceMappingDefaults],
1,420✔
244
  listItemMapper
245
);
246

247
export const getAuditlogDevice = createSelector([getAuditLogEntry, getDevicesById], (auditlogEvent, devicesById) => {
127✔
248
  let auditlogDevice = {};
7✔
249
  if (auditlogEvent) {
7!
250
    const { object = {} } = auditlogEvent;
7!
251
    const { device = {}, id, type } = object;
7✔
252
    auditlogDevice = type === 'device' ? { id, ...device } : auditlogDevice;
7!
253
  }
254
  return { ...auditlogDevice, ...devicesById[auditlogDevice.id] };
7✔
255
});
256

257
export const getRelevantRoles = createSelector([getOrganization, getRolesList], ({ service_provider }, roles) => {
127✔
258
  if (service_provider) {
8!
NEW
259
    return roles.reduce((accu, role) => {
×
NEW
260
      if (rolesById[role.id]) {
×
NEW
261
        return accu;
×
262
      }
NEW
263
      accu.push(role);
×
NEW
264
      return accu;
×
265
    }, Object.values(serviceProviderRolesById));
266
  }
267
  return Object.keys(rolesById)
8✔
268
    .reverse()
269
    .reduce((accu, key) => {
270
      const index = accu.findIndex(({ id }) => id === key);
200✔
271
      accu = [accu[index], ...accu.filter((item, itemIndex) => index !== itemIndex)];
360✔
272
      return accu;
40✔
273
    }, roles);
274
});
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