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

mendersoftware / mender-server / 1590815032

16 Dec 2024 01:53PM UTC coverage: 73.522% (+0.7%) from 72.839%
1590815032

Pull #253

gitlab-ci

mineralsfree
feat: updated billing section in My Organization settings

Ticket: MEN-7466
Changelog: None

Signed-off-by: Mikita Pilinka <mikita.pilinka@northern.tech>
Pull Request #253: MEN-7466-feat: updated billing section in My Organization settings

4257 of 6186 branches covered (68.82%)

Branch coverage included in aggregate %.

57 of 89 new or added lines in 11 files covered. (64.04%)

1 existing line in 1 file now uncovered.

40090 of 54132 relevant lines covered (74.06%)

22.98 hits per line

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

80.0
/frontend/src/js/store/organizationSlice/thunks.ts
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
// @ts-nocheck
15
import storeActions from '@northern.tech/store/actions';
16
import Api from '@northern.tech/store/api/general-api';
17
import {
18
  AvailablePlans,
19
  DEVICE_LIST_DEFAULTS,
20
  SORTING_OPTIONS,
21
  TENANT_LIST_DEFAULT,
22
  TIMEOUTS,
23
  deviceAuthV2,
24
  headerNames,
25
  iotManagerBaseURL,
26
  locations
27
} from '@northern.tech/store/constants';
28
import { BillingProfile } from '@northern.tech/store/organizationSlice/types';
29
import { getCurrentSession, getTenantCapabilities, getTenantsList } from '@northern.tech/store/selectors';
30
import { commonErrorFallback, commonErrorHandler } from '@northern.tech/store/store';
31
import { getDeviceLimit, setFirstLoginAfterSignup } from '@northern.tech/store/thunks';
32
import { deepCompare } from '@northern.tech/utils/helpers';
33
import { createAsyncThunk } from '@reduxjs/toolkit';
34
import { jwtDecode } from 'jwt-decode';
35
import hashString from 'md5';
36
import Cookies from 'universal-cookie';
37

38
import { actions, sliceName } from '.';
39
import { Tenant } from '../../components/tenants/types';
40
import { SSO_TYPES, auditLogsApiUrl, ssoIdpApiUrlv1, tenantadmApiUrlv1, tenantadmApiUrlv2 } from './constants';
41
import { getAuditlogState, getOrganization } from './selectors';
42

43
const cookies = new Cookies();
110✔
44

45
const { setAnnouncement, setSnackbar } = storeActions;
110✔
46
const { page: defaultPage, perPage: defaultPerPage } = DEVICE_LIST_DEFAULTS;
110✔
47

48
export const cancelRequest = createAsyncThunk(`${sliceName}/cancelRequest`, (reason, { dispatch, getState }) => {
110✔
49
  const { id: tenantId } = getOrganization(getState());
1✔
50
  return Api.post(`${tenantadmApiUrlv2}/tenants/${tenantId}/cancel`, { reason }).then(() =>
1✔
51
    Promise.resolve(dispatch(setSnackbar({ message: 'Deactivation request was sent successfully', autoHideDuration: TIMEOUTS.fiveSeconds })))
1✔
52
  );
53
});
54

55
export const getTargetLocation = key => {
110✔
56
  if (devLocations.includes(window.location.hostname)) {
14✔
57
    return '';
6✔
58
  }
59
  let subdomainSections = window.location.hostname.substring(0, window.location.hostname.indexOf(locations.us.location)).split('.');
8✔
60
  subdomainSections = subdomainSections.splice(0, subdomainSections.length - 1);
8✔
61
  if (!subdomainSections.find(section => section === key)) {
8✔
62
    subdomainSections = key === locations.us.key ? subdomainSections.filter(section => !locations[section]) : [...subdomainSections, key];
7✔
63
    return `https://${[...subdomainSections, ...locations.us.location.split('.')].join('.')}`;
7✔
64
  }
65
  return `https://${window.location.hostname}`;
1✔
66
};
67

68
const devLocations = ['localhost', 'docker.mender.io'];
110✔
69
export const createOrganizationTrial = createAsyncThunk(`${sliceName}/createOrganizationTrial`, (data, { dispatch }) => {
110✔
70
  const { key } = locations[data.location];
2✔
71
  const targetLocation = getTargetLocation(key);
2✔
72
  const target = `${targetLocation}${tenantadmApiUrlv2}/tenants/trial`;
2✔
73
  return Api.postUnauthorized(target, data)
2✔
74
    .catch(err => {
75
      if (err.response.status >= 400 && err.response.status < 500) {
×
76
        dispatch(setSnackbar({ message: err.response.data.error, autoHideDuration: TIMEOUTS.fiveSeconds }));
×
77
        return Promise.reject(err);
×
78
      }
79
    })
80
    .then(({ headers }) => {
81
      cookies.remove('oauth');
2✔
82
      cookies.remove('externalID');
2✔
83
      cookies.remove('email');
2✔
84
      dispatch(setFirstLoginAfterSignup(true));
2✔
85
      return new Promise(resolve =>
2✔
86
        setTimeout(() => {
2✔
87
          window.location.assign(`${targetLocation}${headers.location || ''}`);
2✔
88
          return resolve();
2✔
89
        }, TIMEOUTS.fiveSeconds)
90
      );
91
    });
92
});
93

94
export const startCardUpdate = createAsyncThunk(`${sliceName}/startCardUpdate`, (_, { dispatch }) =>
110✔
95
  Api.post(`${tenantadmApiUrlv2}/billing/card`)
1✔
96
    .then(({ data }) => {
97
      dispatch(actions.receiveSetupIntent(data.intent_id));
1✔
98
      return Promise.resolve(data.secret);
1✔
99
    })
100
    .catch(err => commonErrorHandler(err, `Updating the card failed:`, dispatch))
×
101
);
102

103
export const confirmCardUpdate = createAsyncThunk(`${sliceName}/confirmCardUpdate`, (_, { dispatch, getState }) =>
110✔
104
  Api.post(`${tenantadmApiUrlv2}/billing/card/${getState().organization.intentId}/confirm`)
1✔
105
    .then(() => Promise.all([dispatch(setSnackbar('Payment card was updated successfully')), dispatch(actions.receiveSetupIntent(null))]))
1✔
106
    .catch(err => commonErrorHandler(err, `Updating the card failed:`, dispatch))
×
107
);
108

109
export const getCurrentCard = createAsyncThunk(`${sliceName}/getCurrentCard`, (_, { dispatch }) =>
110✔
110
  Api.get(`${tenantadmApiUrlv2}/billing`).then(res => {
3✔
111
    const { last4, exp_month, exp_year, brand } = res.data.card || {};
3!
112
    return Promise.resolve(dispatch(actions.receiveCurrentCard({ brand, last4, expiration: { month: exp_month, year: exp_year } })));
3✔
113
  })
114
);
115

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

122
export const cancelUpgrade = createAsyncThunk(`${sliceName}/cancelUpgrade`, (tenantId: string) =>
110✔
123
  Api.post(`${tenantadmApiUrlv2}/tenants/${tenantId}/upgrade/cancel`)
1✔
124
);
125

126
interface completeUpgradePayload {
127
  billing_profile: BillingProfile;
128
  plan: AvailablePlans;
129
  tenantId: string;
130
}
131
export const completeUpgrade = createAsyncThunk(`${sliceName}/completeUpgrade`, ({ tenantId, plan, billing_profile }: completeUpgradePayload, { dispatch }) =>
110✔
132
  Api.post(`${tenantadmApiUrlv2}/tenants/${tenantId}/upgrade/complete`, { plan, billing_profile })
1✔
133
    .catch(err => commonErrorHandler(err, `There was an error upgrading your account:`, dispatch))
×
134
    .then(() => Promise.all([dispatch(getTenants()), dispatch(getDeviceLimit()), dispatch(getUserOrganization())]))
1✔
135
);
136

137
const prepareAuditlogQuery = ({ startDate, endDate, user: userFilter, type, detail: detailFilter, sort = {} }) => {
110✔
138
  const userId = userFilter?.id || userFilter;
8✔
139
  const detail = detailFilter?.id || detailFilter;
8✔
140
  const createdAfter = startDate ? `&created_after=${Math.round(Date.parse(startDate) / 1000)}` : '';
8✔
141
  const createdBefore = endDate ? `&created_before=${Math.round(Date.parse(endDate) / 1000)}` : '';
8✔
142
  const typeSearch = type ? `&object_type=${type.value}`.toLowerCase() : '';
8!
143
  const userSearch = userId ? `&actor_id=${userId}` : '';
8!
144
  const objectSearch = type && detail ? `&${type.queryParameter}=${encodeURIComponent(detail)}` : '';
8!
145
  const { direction = SORTING_OPTIONS.desc } = sort;
8✔
146
  return `${createdAfter}${createdBefore}${userSearch}${typeSearch}${objectSearch}&sort=${direction}`;
8✔
147
};
148

149
export const getAuditLogs = createAsyncThunk(`${sliceName}/getAuditLogs`, (selectionState, { dispatch, getState }) => {
110✔
150
  const { page, perPage } = selectionState;
7✔
151
  const { hasAuditlogs } = getTenantCapabilities(getState());
7✔
152
  if (!hasAuditlogs) {
7✔
153
    return Promise.resolve();
1✔
154
  }
155
  return Api.get(`${auditLogsApiUrl}/logs?page=${page}&per_page=${perPage}${prepareAuditlogQuery(selectionState)}`)
6✔
156
    .then(({ data, headers }) => {
157
      let total = headers[headerNames.total];
6✔
158
      total = Number(total || data.length);
6!
159
      return Promise.resolve(dispatch(actions.receiveAuditLogs({ events: data, total })));
6✔
160
    })
161
    .catch(err => commonErrorHandler(err, `There was an error retrieving audit logs:`, dispatch));
×
162
});
163

164
export const getAuditLogsCsvLink = createAsyncThunk(`${sliceName}/getAuditLogsCsvLink`, (_, { getState }) =>
110✔
165
  Promise.resolve(`${window.location.origin}${auditLogsApiUrl}/logs/export?limit=20000${prepareAuditlogQuery(getAuditlogState(getState()))}`)
2✔
166
);
167

168
export const setAuditlogsState = createAsyncThunk(`${sliceName}/setAuditlogsState`, (selectionState, { dispatch, getState }) => {
110✔
169
  const currentState = getAuditlogState(getState());
4✔
170
  let nextState = {
4✔
171
    ...currentState,
172
    ...selectionState,
173
    sort: { ...currentState.sort, ...selectionState.sort }
174
  };
175
  let tasks = [];
4✔
176
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
177
  const { isLoading: currentLoading, selectedIssue: currentIssue, ...currentRequestState } = currentState;
4✔
178
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
179
  const { isLoading: selectionLoading, selectedIssue: selectionIssue, ...selectionRequestState } = nextState;
4✔
180
  if (!deepCompare(currentRequestState, selectionRequestState)) {
4!
181
    nextState.isLoading = true;
4✔
182
    tasks.push(dispatch(getAuditLogs(nextState)).finally(() => dispatch(actions.setAuditLogState({ isLoading: false }))));
4✔
183
  }
184
  tasks.push(dispatch(actions.setAuditLogState(nextState)));
4✔
185
  return Promise.all(tasks);
4✔
186
});
187

188
/*
189
  Tenant management + Hosted Mender
190
*/
191
export const tenantDataDivergedMessage = 'The system detected there is a change in your plan or purchased add-ons. Please log out and log in again';
110✔
192

193
export const addTenant = createAsyncThunk(`${sliceName}/createTenant`, (selectionState, { dispatch }) => {
110✔
194
  return Api.post(`${tenantadmApiUrlv2}/tenants`, selectionState)
4✔
195
    .then(() =>
196
      Promise.all([
3✔
197
        dispatch(setSnackbar('Tenant was created successfully.')),
198
        new Promise(resolve => setTimeout(() => resolve(dispatch(getTenants())), TIMEOUTS.oneSecond))
3✔
199
      ])
200
    )
201
    .catch(err => commonErrorHandler(err, 'There was an error creating tenant', dispatch, commonErrorFallback));
×
202
});
203

204
const tenantListRetrieval = async (config): Promise<[Tenant[], number]> => {
110✔
205
  const { page, perPage } = config;
7✔
206
  const params = new URLSearchParams({ page, per_page: perPage }).toString();
7✔
207
  const tenantList = await Api.get(`${tenantadmApiUrlv2}/tenants?${params}`);
7✔
208
  const totalCount = tenantList.headers[headerNames.total] || TENANT_LIST_DEFAULT.perPage;
7✔
209
  return [tenantList.data, Number(totalCount)];
5✔
210
};
211
export const getTenants = createAsyncThunk(`${sliceName}/getTenants`, async (_, { dispatch, getState }) => {
110✔
212
  const currentState = getTenantsList(getState());
7✔
213
  const [tenants, pageCount] = await tenantListRetrieval(currentState);
7✔
214
  dispatch(actions.setTenantListState({ ...currentState, total: pageCount, tenants }));
5✔
215
});
216

217
export const setTenantsListState = createAsyncThunk(`${sliceName}/setTenantsListState`, async (selectionState: any, { dispatch, getState }) => {
110✔
218
  const currentState = getTenantsList(getState());
×
219
  const nextState = {
×
220
    ...currentState,
221
    ...selectionState
222
  };
223
  if (!deepCompare(currentState, selectionState)) {
×
224
    const [tenants, pageCount] = await tenantListRetrieval(nextState);
×
225
    return dispatch(actions.setTenantListState({ ...nextState, tenants, total: pageCount }));
×
226
  }
227
  return dispatch(actions.setTenantListState({ ...nextState }));
×
228
});
229

230
interface editTenantBody {
231
  name: string;
232
  newLimit: number;
233
  id: string;
234
}
235
export const editTenantDeviceLimit = createAsyncThunk(`${sliceName}/editDeviceLimit`, ({ newLimit, id, name }: editTenantBody, { dispatch }) => {
110✔
236
  return Api.put(`${tenantadmApiUrlv2}/tenants/${id}/child`, { device_limit: newLimit, name })
2✔
237
    .catch(err => commonErrorHandler(err, `Device Limit cannot be changed`, dispatch))
×
238
    .then(() => {
239
      return Promise.all([
2✔
240
        dispatch(setSnackbar('Device Limit was changed successfully')),
241
        dispatch(getUserOrganization()),
242
        new Promise(resolve => setTimeout(() => resolve(dispatch(getTenants())), TIMEOUTS.oneSecond))
2✔
243
      ]);
244
    });
245
});
246
export const editBillingProfile = createAsyncThunk(
110✔
247
  `${sliceName}/editBillingProfileEmail`,
248
  ({ billingProfile }: { billingProfile: BillingProfile }, { dispatch }) => {
NEW
249
    return Api.patch(`${tenantadmApiUrlv2}/billing/profile`, { ...billingProfile })
×
NEW
250
      .catch(err => commonErrorHandler(err, `Failed to change billing profile`, dispatch))
×
251
      .then(() => {
NEW
252
        return Promise.all([dispatch(setSnackbar('Billing Profile was changed successfully')), dispatch(getUserBilling())]);
×
253
      });
254
  }
255
);
256
export const removeTenant = createAsyncThunk(`${sliceName}/editDeviceLimit`, ({ id }: { id: string }, { dispatch }) => {
110✔
257
  return Api.post(`${tenantadmApiUrlv2}/tenants/${id}/remove/start`)
×
258
    .catch(err => commonErrorHandler(err, `There was an error removing the tenant`, dispatch))
×
259
    .then(() =>
260
      Promise.all([
×
261
        dispatch(setSnackbar('The tenant was removed successfully')),
262
        dispatch(getUserOrganization()),
263
        new Promise(resolve => setTimeout(() => resolve(dispatch(getTenants())), TIMEOUTS.oneSecond))
×
264
      ])
265
    );
266
});
267
export const getUserOrganization = createAsyncThunk(`${sliceName}/getUserOrganization`, (_, { dispatch, getState }) => {
110✔
268
  return Api.get(`${tenantadmApiUrlv1}/user/tenant`).then(res => {
16✔
269
    let tasks = [dispatch(actions.setOrganization(res.data))];
15✔
270
    const { addons, plan, trial } = res.data;
10✔
271
    const { token } = getCurrentSession(getState());
10✔
272
    const jwt = jwtDecode(token);
10✔
273
    const jwtData = { addons: jwt['mender.addons'], plan: jwt['mender.plan'], trial: jwt['mender.trial'] };
10✔
274
    if (!deepCompare({ addons, plan, trial }, jwtData)) {
10!
275
      const hash = hashString(tenantDataDivergedMessage);
10✔
276
      cookies.remove(`${jwt.sub}${hash}`);
10✔
277
      tasks.push(dispatch(setAnnouncement(tenantDataDivergedMessage)));
10✔
278
    }
279
    return Promise.all(tasks);
10✔
280
  });
281
});
282
export const getUserBilling = createAsyncThunk(`${sliceName}/getUserBilling`, (_, { dispatch }) => {
110✔
283
  return Api.get(`${tenantadmApiUrlv2}/billing/profile`).then(res => {
2✔
284
    dispatch(actions.setBillingProfile(res.data));
2✔
285
  });
286
});
287

288
export const sendSupportMessage = createAsyncThunk(`${sliceName}/sendSupportMessage`, (content, { dispatch }) =>
110✔
289
  Api.post(`${tenantadmApiUrlv2}/contact/support`, content)
1✔
290
    .catch(err => commonErrorHandler(err, 'There was an error sending your request', dispatch, commonErrorFallback))
×
291
    .then(() => Promise.resolve(dispatch(setSnackbar({ message: 'Your request was sent successfully', autoHideDuration: TIMEOUTS.fiveSeconds }))))
1✔
292
);
293
interface requestPlanChangePayload {
294
  tenantId: string;
295
  content: {
296
    current_plan: string;
297
    requested_plan: string;
298
    current_addons: string;
299
    requested_addons: string;
300
    user_message: string;
301
  };
302
}
303
export const requestPlanChange = createAsyncThunk(
110✔
304
  `${sliceName}/requestPlanChange`,
305
  ({ content, tenantId }: requestPlanChangePayload, { dispatch, rejectWithValue }) =>
306
    Api.post(`${tenantadmApiUrlv2}/tenants/${tenantId}/plan`, content)
5✔
307
      .catch(async err => {
308
        await commonErrorHandler(err, 'There was an error sending your request', dispatch, commonErrorFallback);
×
309
        rejectWithValue(err);
×
310
      })
311
      .then(() => Promise.resolve(dispatch(setSnackbar({ message: 'Your request was sent successfully', autoHideDuration: TIMEOUTS.fiveSeconds }))))
5✔
312
);
313

314
export const downloadLicenseReport = createAsyncThunk(`${sliceName}/downloadLicenseReport`, (_, { dispatch }) =>
110✔
315
  Api.get(`${deviceAuthV2}/reports/devices`)
1✔
316
    .catch(err => commonErrorHandler(err, 'There was an error downloading the report', dispatch, commonErrorFallback))
×
317
    .then(res => res.data)
1✔
318
);
319

320
// eslint-disable-next-line @typescript-eslint/no-unused-vars
321
export const createIntegration = createAsyncThunk(`${sliceName}/createIntegration`, ({ id, ...integration }, { dispatch }) =>
110✔
322
  Api.post(`${iotManagerBaseURL}/integrations`, integration)
1✔
323
    .catch(err => commonErrorHandler(err, 'There was an error creating the integration', dispatch, commonErrorFallback))
×
324
    .then(() => Promise.all([dispatch(setSnackbar('The integration was set up successfully')), dispatch(getIntegrations())]))
1✔
325
);
326

327
export const changeIntegration = createAsyncThunk(`${sliceName}/changeIntegration`, ({ id, credentials }, { dispatch }) =>
110✔
328
  Api.put(`${iotManagerBaseURL}/integrations/${id}/credentials`, credentials)
1✔
329
    .catch(err => commonErrorHandler(err, 'There was an error updating the integration', dispatch, commonErrorFallback))
×
330
    .then(() => Promise.all([dispatch(setSnackbar('The integration was updated successfully')), dispatch(getIntegrations())]))
1✔
331
);
332

333
export const deleteIntegration = createAsyncThunk(`${sliceName}/deleteIntegration`, ({ id, provider }, { dispatch, getState }) =>
110✔
334
  Api.delete(`${iotManagerBaseURL}/integrations/${id}`, {})
1✔
335
    .catch(err => commonErrorHandler(err, 'There was an error removing the integration', dispatch, commonErrorFallback))
×
336
    .then(() => {
337
      const integrations = getState().organization.externalDeviceIntegrations.filter(item => provider !== item.provider);
1✔
338
      return Promise.all([
1✔
339
        dispatch(setSnackbar('The integration was removed successfully')),
340
        dispatch(actions.receiveExternalDeviceIntegrations(integrations))
341
      ]);
342
    })
343
);
344

345
export const getIntegrations = createAsyncThunk(`${sliceName}/getIntegrations`, (_, { dispatch, getState }) =>
110✔
346
  Api.get(`${iotManagerBaseURL}/integrations`)
10✔
347
    .catch(err => commonErrorHandler(err, 'There was an error retrieving the integration', dispatch, commonErrorFallback))
×
348
    .then(({ data }) => {
349
      const existingIntegrations = getState().organization.externalDeviceIntegrations;
10✔
350
      const integrations = data.reduce((accu, item) => {
10✔
351
        const existingIntegration = existingIntegrations.find(integration => item.id === integration.id) ?? {};
20✔
352
        const integration = { ...existingIntegration, ...item };
20✔
353
        accu.push(integration);
20✔
354
        return accu;
20✔
355
      }, []);
356
      return Promise.resolve(dispatch(actions.receiveExternalDeviceIntegrations(integrations)));
10✔
357
    })
358
);
359

360
export const getWebhookEvents = createAsyncThunk(`${sliceName}/getWebhookEvents`, (config = {}, { dispatch, getState }) => {
110✔
361
  const { isFollowUp, page = defaultPage, perPage = defaultPerPage } = config;
4✔
362
  return Api.get(`${iotManagerBaseURL}/events?page=${page}&per_page=${perPage}`)
4✔
363
    .catch(err => commonErrorHandler(err, 'There was an error retrieving activity for this integration', dispatch, commonErrorFallback))
×
364
    .then(({ data }) => {
365
      let tasks = [
4✔
366
        dispatch(
367
          actions.receiveWebhookEvents({
368
            value: isFollowUp ? getState().organization.webhooks.events : data,
4✔
369
            total: (page - 1) * perPage + data.length
370
          })
371
        )
372
      ];
373
      if (data.length >= perPage && !isFollowUp) {
4✔
374
        tasks.push(dispatch(getWebhookEvents({ isFollowUp: true, page: page + 1, perPage: 1 })));
1✔
375
      }
376
      return Promise.all(tasks);
4✔
377
    });
378
});
379

380
const ssoConfigActions = {
110✔
381
  create: { success: 'stored', error: 'storing' },
382
  edit: { success: 'updated', error: 'updating' },
383
  read: { success: '', error: 'retrieving' },
384
  remove: { success: 'removed', error: 'removing' },
385
  readMultiple: { success: '', error: 'retrieving' }
386
};
387

388
const ssoConfigActionErrorHandler = (err, type) => dispatch =>
110✔
389
  commonErrorHandler(err, `There was an error ${ssoConfigActions[type].error} the SSO configuration.`, dispatch, commonErrorFallback);
×
390

391
const ssoConfigActionSuccessHandler = type => dispatch => dispatch(setSnackbar(`The SSO configuration was ${ssoConfigActions[type].success} successfully`));
110✔
392

393
export const storeSsoConfig = createAsyncThunk(`${sliceName}/storeSsoConfig`, ({ config, contentType }, { dispatch }) =>
110✔
394
  Api.post(ssoIdpApiUrlv1, config, { headers: { 'Content-Type': contentType, Accept: 'application/json' } })
1✔
395
    .catch(err => dispatch(ssoConfigActionErrorHandler(err, 'create')))
×
396
    .then(() => Promise.all([dispatch(ssoConfigActionSuccessHandler('create')), dispatch(getSsoConfigs())]))
1✔
397
);
398

399
export const changeSsoConfig = createAsyncThunk(`${sliceName}/changeSsoConfig`, ({ config, contentType }, { dispatch }) =>
110✔
400
  Api.put(`${ssoIdpApiUrlv1}/${config.id}`, config, { headers: { 'Content-Type': contentType, Accept: 'application/json' } })
1✔
401
    .catch(err => dispatch(ssoConfigActionErrorHandler(err, 'edit')))
×
402
    .then(() => Promise.all([dispatch(ssoConfigActionSuccessHandler('edit')), dispatch(getSsoConfigs())]))
1✔
403
);
404

405
export const deleteSsoConfig = createAsyncThunk(`${sliceName}/deleteSsoConfig`, ({ id }, { dispatch, getState }) =>
110✔
406
  Api.delete(`${ssoIdpApiUrlv1}/${id}`)
1✔
407
    .catch(err => dispatch(ssoConfigActionErrorHandler(err, 'remove')))
×
408
    .then(() => {
409
      const configs = getState().organization.ssoConfigs.filter(item => id !== item.id);
2✔
410
      return Promise.all([dispatch(ssoConfigActionSuccessHandler('remove')), dispatch(actions.receiveSsoConfigs(configs))]);
1✔
411
    })
412
);
413

414
export const getSsoConfigById = createAsyncThunk(`${sliceName}/getSsoConfigById`, (config, { dispatch }) =>
110✔
415
  Api.get(`${ssoIdpApiUrlv1}/${config.id}`)
10✔
416
    .catch(err => dispatch(ssoConfigActionErrorHandler(err, 'read')))
×
417
    .then(({ data, headers }) => {
418
      const sso = Object.values(SSO_TYPES).find(({ contentType }) => contentType === headers['content-type']);
20✔
419
      return sso ? Promise.resolve({ ...config, config: data, type: sso.id }) : Promise.reject('Unsupported SSO config content type.');
10!
420
    })
421
);
422

423
export const getSsoConfigs = createAsyncThunk(`${sliceName}/getSsoConfigs`, (_, { dispatch }) =>
110✔
424
  Api.get(ssoIdpApiUrlv1)
5✔
425
    .catch(err => dispatch(ssoConfigActionErrorHandler(err, 'readMultiple')))
×
426
    .then(({ data }) =>
427
      Promise.all(data.map(config => dispatch(getSsoConfigById(config)).unwrap()))
10✔
428
        .then(configs => dispatch(actions.receiveSsoConfigs(configs)))
5✔
429
        .catch(err => commonErrorHandler(err, err, dispatch, ''))
×
430
    )
431
);
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