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

mendersoftware / gui / 951400782

pending completion
951400782

Pull #3900

gitlab-ci

web-flow
chore: bump @testing-library/jest-dom from 5.16.5 to 5.17.0

Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.16.5 to 5.17.0.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.16.5...v5.17.0)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #3900: chore: bump @testing-library/jest-dom from 5.16.5 to 5.17.0

4446 of 6414 branches covered (69.32%)

8342 of 10084 relevant lines covered (82.73%)

186.0 hits per line

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

81.48
/src/js/actions/organizationActions.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 jwtDecode from 'jwt-decode';
15
import hashString from 'md5';
16
import Cookies from 'universal-cookie';
17

18
import Api, { apiUrl, headerNames } from '../api/general-api';
19
import { getToken } from '../auth';
20
import { SET_ANNOUNCEMENT, SORTING_OPTIONS, TIMEOUTS, locations } from '../constants/appConstants';
21
import { DEVICE_LIST_DEFAULTS } from '../constants/deviceConstants';
22
import {
23
  RECEIVE_AUDIT_LOGS,
24
  RECEIVE_CURRENT_CARD,
25
  RECEIVE_EXTERNAL_DEVICE_INTEGRATIONS,
26
  RECEIVE_SAML_CONFIGS,
27
  RECEIVE_SETUP_INTENT,
28
  RECEIVE_WEBHOOK_EVENTS,
29
  SET_AUDITLOG_STATE,
30
  SET_ORGANIZATION
31
} from '../constants/organizationConstants';
32
import { deepCompare } from '../helpers';
33
import { getTenantCapabilities } from '../selectors';
34
import { commonErrorFallback, commonErrorHandler, setFirstLoginAfterSignup, setSnackbar } from './appActions';
35
import { deviceAuthV2, iotManagerBaseURL } from './deviceActions';
36

37
const cookies = new Cookies();
187✔
38
export const auditLogsApiUrl = `${apiUrl.v1}/auditlogs`;
187✔
39
export const tenantadmApiUrlv1 = `${apiUrl.v1}/tenantadm`;
187✔
40
export const tenantadmApiUrlv2 = `${apiUrl.v2}/tenantadm`;
187✔
41
export const samlIdpApiUrlv1 = `${apiUrl.v1}/useradm/sso/idp/metadata`;
187✔
42

43
const { page: defaultPage, perPage: defaultPerPage } = DEVICE_LIST_DEFAULTS;
187✔
44

45
export const cancelRequest = (tenantId, reason) => dispatch =>
187✔
46
  Api.post(`${tenantadmApiUrlv2}/tenants/${tenantId}/cancel`, { reason: reason }).then(() =>
1✔
47
    Promise.resolve(dispatch(setSnackbar('Deactivation request was sent successfully', TIMEOUTS.fiveSeconds, '')))
1✔
48
  );
49

50
const devLocations = ['localhost', 'docker.mender.io'];
187✔
51
export const createOrganizationTrial = data => dispatch => {
187✔
52
  const { location } = locations[data.location];
2✔
53
  const targetLocation = devLocations.includes(window.location.hostname)
2!
54
    ? ''
55
    : `https://${window.location.hostname.startsWith('staging') ? 'staging.' : ''}${location}`;
×
56
  const target = `${targetLocation}${tenantadmApiUrlv2}/tenants/trial`;
2✔
57
  return Api.postUnauthorized(target, data)
2✔
58
    .catch(err => {
59
      if (err.response.status >= 400 && err.response.status < 500) {
×
60
        dispatch(setSnackbar(err.response.data.error, TIMEOUTS.fiveSeconds, ''));
×
61
        return Promise.reject(err);
×
62
      }
63
    })
64
    .then(({ headers }) => {
65
      cookies.remove('oauth');
2✔
66
      cookies.remove('externalID');
2✔
67
      cookies.remove('email');
2✔
68
      dispatch(setFirstLoginAfterSignup(true));
2✔
69
      return new Promise(resolve =>
2✔
70
        setTimeout(() => {
2✔
71
          window.location.assign(`${targetLocation}${headers.location || ''}`);
×
72
          return resolve();
×
73
        }, TIMEOUTS.fiveSeconds)
74
      );
75
    });
76
};
77

78
export const startCardUpdate = () => dispatch =>
187✔
79
  Api.post(`${tenantadmApiUrlv2}/billing/card`)
1✔
80
    .then(res => {
81
      dispatch({
1✔
82
        type: RECEIVE_SETUP_INTENT,
83
        intentId: res.data.intent_id
84
      });
85
      return Promise.resolve(res.data.secret);
1✔
86
    })
87
    .catch(err => commonErrorHandler(err, `Updating the card failed:`, dispatch));
×
88

89
export const confirmCardUpdate = () => (dispatch, getState) =>
187✔
90
  Api.post(`${tenantadmApiUrlv2}/billing/card/${getState().organization.intentId}/confirm`)
1✔
91
    .then(() =>
92
      Promise.all([
1✔
93
        dispatch(setSnackbar('Payment card was updated successfully')),
94
        dispatch({
95
          type: RECEIVE_SETUP_INTENT,
96
          intentId: null
97
        })
98
      ])
99
    )
100
    .catch(err => commonErrorHandler(err, `Updating the card failed:`, dispatch));
×
101

102
export const getCurrentCard = () => dispatch =>
187✔
103
  Api.get(`${tenantadmApiUrlv2}/billing`).then(res => {
2✔
104
    const { last4, exp_month, exp_year, brand } = res.data.card || {};
1!
105
    return Promise.resolve(
1✔
106
      dispatch({
107
        type: RECEIVE_CURRENT_CARD,
108
        card: {
109
          brand,
110
          last4,
111
          expiration: { month: exp_month, year: exp_year }
112
        }
113
      })
114
    );
115
  });
116

117
export const startUpgrade = tenantId => dispatch =>
187✔
118
  Api.post(`${tenantadmApiUrlv2}/tenants/${tenantId}/upgrade/start`)
1✔
119
    .then(({ data }) => Promise.resolve(data.secret))
1✔
120
    .catch(err => commonErrorHandler(err, `There was an error upgrading your account:`, dispatch));
×
121

122
export const cancelUpgrade = tenantId => () => Api.post(`${tenantadmApiUrlv2}/tenants/${tenantId}/upgrade/cancel`);
187✔
123

124
export const completeUpgrade = (tenantId, plan) => dispatch =>
187✔
125
  Api.post(`${tenantadmApiUrlv2}/tenants/${tenantId}/upgrade/complete`, { plan })
1✔
126
    .catch(err => commonErrorHandler(err, `There was an error upgrading your account:`, dispatch))
×
127
    .then(() => Promise.resolve(dispatch(getUserOrganization())));
1✔
128

129
const prepareAuditlogQuery = ({ startDate, endDate, user: userFilter, type, detail: detailFilter, sort = {} }) => {
187✔
130
  const userId = userFilter?.id || userFilter;
10✔
131
  const detail = detailFilter?.id || detailFilter;
10✔
132
  const createdAfter = endDate ? `&created_after=${Math.round(Date.parse(startDate) / 1000)}` : '';
10✔
133
  const createdBefore = startDate ? `&created_before=${Math.round(Date.parse(endDate) / 1000)}` : '';
10✔
134
  const typeSearch = type ? `&object_type=${type.value}`.toLowerCase() : '';
10!
135
  const userSearch = userId ? `&actor_id=${userId}` : '';
10!
136
  const objectSearch = type && detail ? `&${type.queryParameter}=${encodeURIComponent(detail)}` : '';
10!
137
  const { direction = SORTING_OPTIONS.desc } = sort;
10✔
138
  return `${createdAfter}${createdBefore}${userSearch}${typeSearch}${objectSearch}&sort=${direction}`;
10✔
139
};
140

141
export const getAuditLogs = selectionState => (dispatch, getState) => {
187✔
142
  const { page, perPage } = selectionState;
9✔
143
  const { hasAuditlogs } = getTenantCapabilities(getState());
9✔
144
  if (!hasAuditlogs) {
9✔
145
    return Promise.resolve();
1✔
146
  }
147
  return Api.get(`${auditLogsApiUrl}/logs?page=${page}&per_page=${perPage}${prepareAuditlogQuery(selectionState)}`)
8✔
148
    .then(res => {
149
      let total = res.headers[headerNames.total];
1✔
150
      total = Number(total || res.data.length);
1!
151
      return Promise.resolve(dispatch({ type: RECEIVE_AUDIT_LOGS, events: res.data, total }));
1✔
152
    })
153
    .catch(err => commonErrorHandler(err, `There was an error retrieving audit logs:`, dispatch));
×
154
};
155

156
export const getAuditLogsCsvLink = () => (dispatch, getState) =>
187✔
157
  Promise.resolve(`${auditLogsApiUrl}/logs/export?limit=20000${prepareAuditlogQuery(getState().organization.auditlog.selectionState)}`);
2✔
158

159
export const setAuditlogsState = selectionState => (dispatch, getState) => {
187✔
160
  const currentState = getState().organization.auditlog.selectionState;
12✔
161
  let nextState = {
12✔
162
    ...currentState,
163
    ...selectionState,
164
    sort: { ...currentState.sort, ...selectionState.sort }
165
  };
166
  let tasks = [];
12✔
167
  // eslint-disable-next-line no-unused-vars
168
  const { isLoading: currentLoading, selectedIssue: currentIssue, ...currentRequestState } = currentState;
12✔
169
  // eslint-disable-next-line no-unused-vars
170
  const { isLoading: selectionLoading, selectedIssue: selectionIssue, ...selectionRequestState } = nextState;
12✔
171
  if (!deepCompare(currentRequestState, selectionRequestState)) {
12✔
172
    nextState.isLoading = true;
8✔
173
    tasks.push(dispatch(getAuditLogs(nextState)).finally(() => dispatch(setAuditlogsState({ isLoading: false }))));
8✔
174
  }
175
  tasks.push(dispatch({ type: SET_AUDITLOG_STATE, state: nextState }));
12✔
176
  return Promise.all(tasks);
12✔
177
};
178

179
/*
180
  Tenant management + Hosted Mender
181
*/
182
export const tenantDataDivergedMessage = 'The system detected there is a change in your plan or purchased add-ons. Please log out and log in again';
187✔
183
export const getUserOrganization = () => dispatch => {
187✔
184
  const token = getToken();
10✔
185
  return Api.get(`${tenantadmApiUrlv1}/user/tenant`).then(res => {
10✔
186
    let tasks = [dispatch({ type: SET_ORGANIZATION, organization: res.data })];
9✔
187
    const { addons, plan, trial } = res.data;
9✔
188
    const jwt = jwtDecode(token);
9✔
189
    const jwtData = { addons: jwt['mender.addons'], plan: jwt['mender.plan'], trial: jwt['mender.trial'] };
8✔
190
    if (!deepCompare({ addons, plan, trial }, jwtData)) {
8!
191
      const hash = hashString(tenantDataDivergedMessage);
8✔
192
      cookies.remove(`${jwt.sub}${hash}`);
8✔
193
      tasks.push(dispatch({ type: SET_ANNOUNCEMENT, announcement: tenantDataDivergedMessage }));
8✔
194
    }
195
    return Promise.all(tasks);
8✔
196
  });
197
};
198

199
export const sendSupportMessage = content => dispatch =>
187✔
200
  Api.post(`${tenantadmApiUrlv2}/contact/support`, content)
1✔
201
    .catch(err => commonErrorHandler(err, 'There was an error sending your request', dispatch, commonErrorFallback))
×
202
    .then(() => Promise.resolve(dispatch(setSnackbar('Your request was sent successfully', TIMEOUTS.fiveSeconds, ''))));
1✔
203

204
export const requestPlanChange = (tenantId, content) => dispatch =>
187✔
205
  Api.post(`${tenantadmApiUrlv2}/tenants/${tenantId}/plan`, content)
1✔
206
    .catch(err => commonErrorHandler(err, 'There was an error sending your request', dispatch, commonErrorFallback))
×
207
    .then(() => Promise.resolve(dispatch(setSnackbar('Your request was sent successfully', TIMEOUTS.fiveSeconds, ''))));
1✔
208

209
export const downloadLicenseReport = () => dispatch =>
187✔
210
  Api.get(`${deviceAuthV2}/reports/devices`)
1✔
211
    .catch(err => commonErrorHandler(err, 'There was an error downloading the report', dispatch, commonErrorFallback))
×
212
    .then(res => res.data);
1✔
213

214
export const createIntegration = integration => dispatch => {
187✔
215
  // eslint-disable-next-line no-unused-vars
216
  const { credentials, id, provider, ...remainder } = integration;
1✔
217
  return Api.post(`${iotManagerBaseURL}/integrations`, { provider, credentials, ...remainder })
1✔
218
    .catch(err => commonErrorHandler(err, 'There was an error creating the integration', dispatch, commonErrorFallback))
×
219
    .then(() => Promise.all([dispatch(setSnackbar('The integration was set up successfully')), dispatch(getIntegrations())]));
1✔
220
};
221

222
export const changeIntegration = integration => dispatch =>
187✔
223
  Api.put(`${iotManagerBaseURL}/integrations/${integration.id}/credentials`, integration.credentials)
1✔
224
    .catch(err => commonErrorHandler(err, 'There was an error updating the integration', dispatch, commonErrorFallback))
×
225
    .then(() => Promise.all([dispatch(setSnackbar('The integration was updated successfully')), dispatch(getIntegrations())]));
1✔
226

227
export const deleteIntegration = integration => (dispatch, getState) =>
187✔
228
  Api.delete(`${iotManagerBaseURL}/integrations/${integration.id}`, {})
1✔
229
    .catch(err => commonErrorHandler(err, 'There was an error removing the integration', dispatch, commonErrorFallback))
×
230
    .then(() => {
231
      const integrations = getState().organization.externalDeviceIntegrations.filter(item => integration.provider !== item.provider);
1✔
232
      return Promise.all([
1✔
233
        dispatch(setSnackbar('The integration was removed successfully')),
234
        dispatch({ type: RECEIVE_EXTERNAL_DEVICE_INTEGRATIONS, value: integrations })
235
      ]);
236
    });
237

238
export const getIntegrations = () => (dispatch, getState) =>
187✔
239
  Api.get(`${iotManagerBaseURL}/integrations`)
11✔
240
    .catch(err => commonErrorHandler(err, 'There was an error retrieving the integration', dispatch, commonErrorFallback))
×
241
    .then(({ data }) => {
242
      const existingIntegrations = getState().organization.externalDeviceIntegrations;
8✔
243
      const integrations = data.reduce((accu, item) => {
8✔
244
        const existingIntegration = existingIntegrations.find(integration => item.id === integration.id) ?? {};
16✔
245
        const integration = { ...existingIntegration, ...item };
16✔
246
        accu.push(integration);
16✔
247
        return accu;
16✔
248
      }, []);
249
      return Promise.resolve(dispatch({ type: RECEIVE_EXTERNAL_DEVICE_INTEGRATIONS, value: integrations }));
8✔
250
    });
251

252
export const getWebhookEvents =
253
  (config = {}) =>
187✔
254
  (dispatch, getState) => {
3✔
255
    const { isFollowUp, page = defaultPage, perPage = defaultPerPage } = config;
3✔
256
    return Api.get(`${iotManagerBaseURL}/events?page=${page}&per_page=${perPage}`)
3✔
257
      .catch(err => commonErrorHandler(err, 'There was an error retrieving activity for this integration', dispatch, commonErrorFallback))
×
258
      .then(({ data }) => {
259
        let tasks = [
3✔
260
          dispatch({
261
            type: RECEIVE_WEBHOOK_EVENTS,
262
            value: isFollowUp ? getState().organization.webhooks.events : data,
3✔
263
            total: (page - 1) * perPage + data.length
264
          })
265
        ];
266
        if (data.length >= perPage && !isFollowUp) {
3✔
267
          tasks.push(dispatch(getWebhookEvents({ isFollowUp: true, page: page + 1, perPage: 1 })));
1✔
268
        }
269
        return Promise.all(tasks);
3✔
270
      });
271
  };
272

273
const samlConfigActions = {
187✔
274
  create: { success: 'stored', error: 'storing' },
275
  edit: { success: 'updated', error: 'updating' },
276
  read: { success: '', error: 'retrieving' },
277
  remove: { success: 'removed', error: 'removing' }
278
};
279

280
const samlConfigActionErrorHandler = (err, type) => dispatch =>
187✔
281
  commonErrorHandler(err, `There was an error ${samlConfigActions[type].error} the SAML configuration.`, dispatch, commonErrorFallback);
×
282

283
const samlConfigActionSuccessHandler = type => dispatch => dispatch(setSnackbar(`The SAML configuration was ${samlConfigActions[type].success} successfully`));
187✔
284

285
export const storeSamlConfig = config => dispatch =>
187✔
286
  Api.post(samlIdpApiUrlv1, config, { headers: { 'Content-Type': 'application/samlmetadata+xml', Accept: 'application/json' } })
1✔
287
    .catch(err => dispatch(samlConfigActionErrorHandler(err, 'create')))
×
288
    .then(() => Promise.all([dispatch(samlConfigActionSuccessHandler('create')), dispatch(getSamlConfigs())]));
1✔
289

290
export const changeSamlConfig =
291
  ({ id, config }) =>
187✔
292
  dispatch =>
1✔
293
    Api.put(`${samlIdpApiUrlv1}/${id}`, config, { headers: { 'Content-Type': 'application/samlmetadata+xml', Accept: 'application/json' } })
1✔
294
      .catch(err => dispatch(samlConfigActionErrorHandler(err, 'edit')))
×
295
      .then(() => Promise.all([dispatch(samlConfigActionSuccessHandler('edit')), dispatch(getSamlConfigs())]));
1✔
296

297
export const deleteSamlConfig =
298
  ({ id }) =>
187✔
299
  (dispatch, getState) =>
5✔
300
    Api.delete(`${samlIdpApiUrlv1}/${id}`)
5✔
301
      .catch(err => dispatch(samlConfigActionErrorHandler(err, 'remove')))
×
302
      .then(() => {
303
        const configs = getState().organization.samlConfigs.filter(item => id !== item.id);
6✔
304
        return Promise.all([dispatch(samlConfigActionSuccessHandler('remove')), dispatch({ type: RECEIVE_SAML_CONFIGS, value: configs })]);
5✔
305
      });
306

307
const getSamlConfigById = config => dispatch =>
187✔
308
  Api.get(`${samlIdpApiUrlv1}/${config.id}`)
12✔
309
    .catch(err => dispatch(samlConfigActionErrorHandler(err, 'read')))
×
310
    .then(({ data }) => Promise.resolve({ ...config, config: data }));
12✔
311

312
export const getSamlConfigs = () => dispatch =>
187✔
313
  Api.get(samlIdpApiUrlv1)
6✔
314
    .catch(err => commonErrorHandler(err, 'There was an error retrieving SAML configurations', dispatch, commonErrorFallback))
×
315
    .then(({ data }) =>
316
      Promise.all(data.map(config => Promise.resolve(dispatch(getSamlConfigById(config))))).then(configs => {
12✔
317
        return dispatch({ type: RECEIVE_SAML_CONFIGS, value: configs });
6✔
318
      })
319
    );
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