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

mendersoftware / mender-server / 10423

11 Nov 2025 04:53PM UTC coverage: 74.435% (-0.1%) from 74.562%
10423

push

gitlab-ci

web-flow
Merge pull request #1071 from mendersoftware/dependabot/npm_and_yarn/frontend/main/development-dependencies-92732187be

3868 of 5393 branches covered (71.72%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 2 files covered. (100.0%)

176 existing lines in 95 files now uncovered.

64605 of 86597 relevant lines covered (74.6%)

7.74 hits per line

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

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

2✔
18
// material ui
2✔
19
import { Payment as PaymentIcon } from '@mui/icons-material';
2✔
20

2✔
21
import LeftNav from '@northern.tech/common-ui/LeftNav';
2✔
22
import { TIMEOUTS, canAccess } from '@northern.tech/store/constants';
2✔
23
import {
2✔
24
  getCurrentUser,
2✔
25
  getFeatures,
2✔
26
  getOrganization,
2✔
27
  getStripeKey,
2✔
28
  getTenantCapabilities,
2✔
29
  getUserCapabilities,
2✔
30
  getUserRoles
2✔
31
} from '@northern.tech/store/selectors';
2✔
32
import { Elements } from '@stripe/react-stripe-js';
2✔
33

2✔
34
import { SubscriptionPage } from '../subscription/SubscriptionPage';
2✔
35
import Global from './Global';
2✔
36
import Integrations from './Integrations';
2✔
37
import Billing from './organization/Billing';
2✔
38
import Organization from './organization/Organization';
2✔
39
import { RoleManagement } from './role-management/RoleManagement';
2✔
40
import SelfUserManagement from './user-management/SelfUserManagement';
2✔
41
import UserManagement from './user-management/UserManagement';
2✔
42

2✔
43
let stripePromise = null;
6✔
44

2✔
45
const sectionMap = {
6✔
46
  'global-settings': {
2✔
47
    component: Global,
2✔
48
    text: () => 'Global settings',
4✔
49
    canAccess: ({ organization: { service_provider }, userCapabilities: { canManageUsers } }) => !service_provider && canManageUsers
4✔
50
  },
2✔
51
  'my-profile': { component: SelfUserManagement, text: () => 'My profile', canAccess },
4✔
52
  'organization': {
2✔
53
    component: Organization,
2✔
54
    text: () => 'Organization',
4✔
55
    canAccess: ({ hasMultitenancy }) => hasMultitenancy
4✔
56
  },
2✔
57
  'user-management': {
2✔
58
    component: UserManagement,
2✔
59
    text: () => 'User management',
4✔
60
    canAccess: ({ userCapabilities: { canManageUsers } }) => canManageUsers
4✔
61
  },
2✔
62
  'role-management': {
2✔
63
    component: RoleManagement,
2✔
64
    text: () => 'Roles',
4✔
65
    canAccess: ({ currentUser, userRoles: { isAdmin } }) => currentUser && isAdmin
4✔
66
  },
2✔
67
  integrations: {
2✔
68
    component: Integrations,
2✔
69
    text: () => 'Integrations',
4✔
70
    canAccess: ({ organization: { service_provider }, userRoles: { isAdmin } }) => !service_provider && isAdmin
4✔
71
  },
2✔
72
  'billing': {
2✔
73
    component: Billing,
2✔
UNCOV
74
    text: () => 'Billing',
2✔
75
    canAccess: ({ isHosted }) => isHosted
4✔
76
  },
2✔
77
  subscription: {
2✔
78
    component: SubscriptionPage,
2✔
79
    icon: <PaymentIcon />,
2✔
80
    text: ({ organization: { trial } }) => (trial ? 'Upgrade to a plan' : 'Upgrades and add-ons'),
4!
81
    canAccess: ({ hasMultitenancy, organization: { service_provider } }) => !service_provider && hasMultitenancy
4✔
82
  }
2✔
83
};
2✔
84

2✔
85
export const Settings = () => {
6✔
86
  const currentUser = useSelector(getCurrentUser);
4✔
87
  const { hasMultitenancy, isHosted } = useSelector(getFeatures);
4✔
88
  const organization = useSelector(getOrganization);
4✔
89
  const stripeAPIKey = useSelector(getStripeKey);
4✔
90
  const tenantCapabilities = useSelector(getTenantCapabilities);
4✔
91
  const userCapabilities = useSelector(getUserCapabilities);
4✔
92
  const userRoles = useSelector(getUserRoles);
4✔
93

2✔
94
  const [loadingFinished, setLoadingFinished] = useState(!stripeAPIKey);
4✔
95
  const { section: sectionParam } = useParams();
4✔
96

2✔
97
  useEffect(() => {
4✔
98
    // Make sure to call `loadStripe` outside of a component’s render to avoid recreating
2✔
99
    // the `Stripe` object on every render - but don't initialize twice.
2✔
100
    if (!stripePromise) {
3!
101
      import(/* webpackChunkName: "stripe" */ '@stripe/stripe-js').then(({ loadStripe }) => {
3✔
102
        if (stripeAPIKey) {
3!
103
          stripePromise = loadStripe(stripeAPIKey).finally(() => setLoadingFinished(true));
2✔
104
        }
2✔
105
      });
2✔
106
    } else {
2✔
107
      const notStripePromise = new Promise(resolve => setTimeout(resolve, TIMEOUTS.debounceDefault));
2✔
108
      Promise.race([stripePromise, notStripePromise]).then(result => setLoadingFinished(result !== notStripePromise));
2✔
109
    }
2✔
110
  }, [stripeAPIKey]);
2✔
111

2✔
112
  const checkDenyAccess = item =>
4✔
113
    currentUser.id && !item.canAccess({ currentUser, hasMultitenancy, isHosted, organization, tenantCapabilities, userCapabilities, userRoles });
20✔
114

2✔
115
  const getCurrentSection = (sections, section = sectionParam) => {
4✔
116
    if (!sections.hasOwnProperty(section) || checkDenyAccess(sections[section])) {
4!
117
      return;
2✔
118
    }
2✔
119
    return sections[section];
4✔
120
  };
2✔
121

2✔
122
  const links = Object.entries(sectionMap).reduce((accu, [key, item]) => {
4✔
123
    if (!checkDenyAccess(item)) {
18✔
124
      accu.push({
16✔
125
        path: `/settings/${key}`,
2✔
126
        icon: item.icon,
2✔
127
        title: item.text({ organization })
2✔
128
      });
2✔
129
    }
2✔
130
    return accu;
18✔
131
  }, []);
2✔
132

2✔
133
  const section = getCurrentSection(sectionMap, sectionParam);
4✔
134
  if (!section) {
4!
135
    return <Navigate to="/settings/my-profile" replace />;
2✔
136
  }
2✔
137
  const Component = section.component;
4✔
138
  return (
4✔
139
    <div className="tab-container with-sub-panels" style={{ minHeight: '95%' }}>
2✔
140
      <LeftNav sections={[{ itemClass: 'settingsNav', items: links, title: 'Settings' }]} />
2✔
141
      <div className="rightFluid padding-right-large" style={{ paddingBottom: '15%' }}>
2✔
142
        {loadingFinished && (
2✔
143
          <Elements stripe={stripePromise}>
2✔
144
            <Component />
2✔
145
          </Elements>
2✔
146
        )}
2✔
147
      </div>
2✔
148
    </div>
2✔
149
  );
2✔
150
};
2✔
151

2✔
152
export default Settings;
2✔
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