• 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

72.97
/frontend/src/js/components/settings/Upgrade.tsx
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 React, { useEffect, useState } from 'react';
15
import { useSelector } from 'react-redux';
16

17
import { ConfirmUpgrade } from '@northern.tech/common-ui/dialogs/ConfirmUpgrade';
18
import { AvailablePlans, PLANS, Plan } from '@northern.tech/store/constants';
19
import { Addon } from '@northern.tech/store/organizationSlice/types';
20
import { getFeatures, getOrganization } from '@northern.tech/store/selectors';
21
import { useAppDispatch } from '@northern.tech/store/store';
22
import { getUserOrganization, requestPlanChange } from '@northern.tech/store/thunks';
23

24
import AddOnSelection from './AddonSelection';
25
import { EnterpriseRequestExpanded } from './EnterpriseRequestExpanded';
26
import { PlanExpanded } from './PlanExpanded';
27
import PlanSelection from './PlanSelection';
28

29
export const updateLocalStorage = (orgId: string, name: string, isAdd: boolean) => {
6✔
30
  const currentState = JSON.parse(localStorage.getItem(orgId + '_upgrades') || '{}') || {};
4!
31
  currentState[name] = { pending: true, isAdd };
4✔
32
  localStorage.setItem(orgId + '_upgrades', JSON.stringify(currentState));
4✔
33
};
34
export const clearLocalStorageEntry = (orgId: string, name: string) => {
6✔
35
  const currentState = JSON.parse(localStorage.getItem(orgId + '_upgrades') || '{}') || {};
×
36
  delete currentState[name];
×
37
  localStorage.setItem(orgId + '_upgrades', JSON.stringify(currentState));
×
38
};
39

40
export const isUpgrade = (orgId: string): Partial<{ plan: AvailablePlans }> => {
6✔
41
  const currentState = JSON.parse(localStorage.getItem(orgId + '_upgrades') || '{}') || {};
17!
42
  let upgrade = {};
17✔
43
  Object.keys(PLANS).forEach(key => {
17✔
44
    if (currentState[key]) {
51✔
45
      upgrade = { plan: PLANS[key].id };
1✔
46
    }
47
  });
48
  return upgrade;
17✔
49
};
50
export const addOnsToString = (addons: Addon[] = []) =>
6!
51
  addons
8✔
52
    .reduce((accu: string[], item) => {
53
      if (item.enabled) {
×
54
        accu.push(item.name);
×
55
      }
56
      return accu;
×
57
    }, [])
58
    .join(', ');
59
export const PricingContactNote = () => (
6✔
60
  <p>
17✔
61
    * Device limits can be adjusted on request; prices can change for larger limits. If you have any questions about the plan pricing or device limits,{' '}
62
    <a href="mailto:support@mender.io" target="_blank" rel="noopener noreferrer">
63
      contact our team.
64
    </a>
65
  </p>
66
);
67

68
const upgradeNotes = {
6✔
69
  default: {
70
    title: 'Upgrades and add-ons',
71
    description: 'Upgrade your plan or purchase an add-on to connect more devices, access more features and advanced support.'
72
  },
73
  trial: {
74
    title: 'Upgrade now',
75
    description: 'Upgrade to one of our plans to connect more devices, continue using advanced features, and get access to support.'
76
  }
77
};
78

79
export const Upgrade = () => {
6✔
80
  const [addOns, setAddOns] = useState<Addon[]>([]);
17✔
81
  const [updatedPlan, setUpdatedPlan] = useState<string>(PLANS.os.id);
16✔
82
  const [selectedPlan, setSelectedPlan] = useState<Plan | null>(null);
16✔
83

84
  const dispatch = useAppDispatch();
16✔
85
  const features = useSelector(getFeatures);
16✔
86
  const org = useSelector(getOrganization);
16✔
87
  const { addons: orgAddOns = [], plan: currentPlan = PLANS.os.id as AvailablePlans, trial: isTrial = true } = org;
16!
88

89
  useEffect(() => {
16✔
90
    dispatch(getUserOrganization());
5✔
91
  }, [dispatch]);
92

93
  useEffect(() => {
16✔
94
    const currentAddOns = orgAddOns.reduce((accu: Addon[], addon) => {
5✔
95
      if (addon.enabled) {
×
96
        accu.push(addon);
×
97
      }
98
      return accu;
×
99
    }, []);
100
    const plan = Object.values(PLANS).find(plan => plan.id === (isTrial ? PLANS.os.id : currentPlan));
5✔
101
    setAddOns(currentAddOns);
5✔
102
    if (plan) {
5!
103
      setUpdatedPlan(plan.id);
5✔
104
    }
105
    // eslint-disable-next-line react-hooks/exhaustive-deps
106
  }, [currentPlan, isTrial, JSON.stringify(orgAddOns)]);
107

108
  const selectPlan = (plan: string) => {
16✔
109
    setUpdatedPlan(plan);
3✔
110
    setSelectedPlan(PLANS[plan]);
3✔
111
  };
112
  const onEnterpriseRequest = ({ message, selectedAddons }) => {
16✔
113
    onSendRequest(message, selectedAddons.join(', '));
1✔
114
  };
115

116
  const onSendRequest = async (message = '', requestedAddons = '') => {
16✔
117
    try {
2✔
118
      await dispatch(
2✔
119
        requestPlanChange({
120
          tenantId: org.id,
121
          content: {
122
            current_plan: PLANS[org.plan || PLANS.os.id].name,
2!
123
            requested_plan: PLANS[updatedPlan].name,
124
            current_addons: addOnsToString(org.addons) || '-',
4✔
125
            requested_addons: requestedAddons || addOnsToString(org.addons) || '-',
6✔
126
            user_message: message
127
          }
128
        })
129
      ).unwrap();
130
    } catch (error) {
131
      console.error(error);
×
132
      return;
×
133
    }
134
    updateLocalStorage(org.id, PLANS[updatedPlan].id, true);
2✔
135
    setSelectedPlan(null);
2✔
136
  };
137
  const { description, title } = isTrial ? upgradeNotes.trial : upgradeNotes.default;
16✔
138
  return (
16✔
139
    <div style={{ maxWidth: 750 }}>
140
      <h2>{title}</h2>
141
      <p className="margin-bottom-small">{description}</p>
142
      <p className="margin-bottom-large">
143
        See the full details of plans and features at {/* eslint-disable-next-line react/jsx-no-target-blank */}
144
        <a href="https://mender.io/plans/pricing" target="_blank" rel="noopener">
145
          mender.io/plans/pricing
146
        </a>
147
      </p>
148
      <PlanSelection currentPlan={currentPlan} isTrial={isTrial} setUpdatedPlan={selectPlan} updatedPlan={updatedPlan} orgId={org.id} />
149

150
      <PricingContactNote />
151
      {!isTrial && <AddOnSelection org={org} currentPlan={currentPlan} addons={orgAddOns} features={features} isTrial={isTrial} />}
29✔
152

153
      {isTrial && selectedPlan && updatedPlan !== PLANS.enterprise.id ? (
36✔
NEW
154
        <PlanExpanded isEdit={false} plan={selectedPlan} organization={org} onCloseClick={() => setSelectedPlan(null)} />
×
155
      ) : updatedPlan === PLANS.enterprise.id && selectedPlan ? (
32✔
156
        <EnterpriseRequestExpanded addons={addOns} onClose={() => setSelectedPlan(null)} onSendRequest={onEnterpriseRequest} />
×
157
      ) : (
158
        selectedPlan &&
16✔
159
        selectedPlan.id !== currentPlan && (
160
          <ConfirmUpgrade
161
            currentPlan={PLANS[currentPlan]}
162
            onClose={() => setSelectedPlan(null)}
×
163
            onConfirm={() => onSendRequest()}
1✔
164
            addOns={addOns}
165
            newPlan={selectedPlan}
166
          />
167
        )
168
      )}
169
    </div>
170
  );
171
};
172

173
export default Upgrade;
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