Skip to content

Commit 2838d7e

Browse files
authored
Merge pull request #131 from sapcc/enhancements
Implement various enhancements
2 parents 7d2ceca + 24fd8a2 commit 2838d7e

23 files changed

+578
-191
lines changed

.changeset/slimy-things-prove.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sapcc/limes-ui": minor
3+
---
4+
5+
Allow commitment management for resources that previously had commitments enabled but don't have now; add backend naming and capacity allocation to the resource explanations; rearrange max quota and label display at the resources

src/components/commitment/CommitmentTable.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import useCommitmentFilter from "../../hooks/useCommitmentFilter";
2222
import { createCommitmentStore } from "../StoreProvider";
2323
import { CustomZones } from "../../lib/constants";
2424
import { COMMITMENTID } from "../../lib/constants";
25+
import { getResourceDurations } from "../../lib/utils";
2526

2627
const CommitmentTable = (props) => {
27-
const durations = props.resource.commitment_config.durations;
2828
const unit = props.resource.unit;
2929
const { filterCommitments } = useCommitmentFilter();
3030
const { commitment: newCommitment } = createCommitmentStore();
@@ -34,6 +34,7 @@ const CommitmentTable = (props) => {
3434
const { serviceType, currentCategory, currentResource, currentAZ, commitmentData, mergeOps } = {
3535
...props,
3636
};
37+
const durations = getResourceDurations(currentResource);
3738
const { setMergeIsActive } = mergeOps;
3839
const resourceName = currentResource?.name;
3940
const { per_az: availabilityZones } = props.resource;

src/components/mainView/Resource.js

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { isAZUnaware } from "../../lib/utils";
2525
import ResourceBarBuilder from "../resourceBar/ResourceBarBuilder";
2626
import useResetCommitment from "../../hooks/useResetCommitment";
2727
import HistoricalUsage from "./subComponents/HistoricalUsage";
28-
import MaxQuota from "./subComponents/MaxQuota";
28+
import ForbidAutogrowth from "./subComponents/ForbidAutogrowth";
2929
import PhysicalUsage from "./subComponents/PhysicalUsage";
3030

3131
const barGroupContainer = `
@@ -80,18 +80,27 @@ const azContentHover = `
8080
`;
8181

8282
const Resource = (props) => {
83-
const { canEdit, project, resource, isPanelView, subRoute, setCurrentAZ, serviceType, setIsMerging, tracksQuota } =
84-
props;
83+
const {
84+
canEdit,
85+
categoryName,
86+
project,
87+
resource,
88+
isPanelView,
89+
subRoute,
90+
setCurrentAZ,
91+
serviceType,
92+
setIsMerging,
93+
tracksQuota,
94+
} = props;
8595
const { unit: unitName, editableResource } = resource;
8696
const { scope } = globalStore();
8797
const displayName = t(resource.name);
8898
const { isEditing } = createCommitmentStore();
8999
const { resetCommitment } = useResetCommitment();
90100
const [displayResourceInfo, setDisplayResourceInfo] = React.useState(false);
91-
const resourceHasQuota = resource?.quota > 0;
92101

93-
const maxQuotaForwardProps = {
94-
editMode: isPanelView || (canEdit && !editableResource),
102+
const forbidAutogrowthForwardProps = {
103+
editMode: canEdit,
95104
project: project,
96105
resource: resource,
97106
serviceType: serviceType,
@@ -114,7 +123,7 @@ const Resource = (props) => {
114123
>
115124
<Stack gap="2">
116125
<div className="m-auto">{displayName}</div>
117-
{resourceHasQuota && !isPanelView && (
126+
{!isPanelView && (
118127
<Button
119128
data-testid="detailedResourceInfo"
120129
icon={displayResourceInfo ? "expandMore" : "chevronRight"}
@@ -127,19 +136,11 @@ const Resource = (props) => {
127136
/>
128137
)}
129138
</Stack>
130-
{scope.isProject() && (
131-
<span className="font-light">
132-
<MaxQuota {...maxQuotaForwardProps} />
133-
</span>
134-
)}
135139
</Stack>
136140
{canEdit && (
137141
<Stack className="items-center" gap="1">
138-
{isAZUnaware(props.resource.per_az) && (
139-
<ProjectBadges az={props.resource.per_az[0]} unit={unitName} displayValues={true} />
140-
)}
141142
{canEdit && !isPanelView && editableResource && (
142-
<Link to={`/${props.area}/edit/${props.categoryName}/${props.resource.name}`} state={props}>
143+
<Link to={`/${props.area}/edit/${categoryName}/${props.resource.name}`} state={props}>
143144
<Button
144145
data-cy={`edit/${props.resource.name}`}
145146
data-testid={`edit/${props.resource.name}`}
@@ -153,7 +154,7 @@ const Resource = (props) => {
153154
)}
154155
{!scope.isProject() && tracksQuota && (
155156
<Link
156-
to={`/${props.area}/edit/${props.categoryName}/${props.resource.name}/${PanelType.quota.name}`}
157+
to={`/${props.area}/edit/${categoryName}/${props.resource.name}/${PanelType.quota.name}`}
157158
state={props}
158159
>
159160
<Button data-testid={"setMaxQuotaPanel"} className="ml-1" size="small" icon="edit">
@@ -164,9 +165,18 @@ const Resource = (props) => {
164165
</Stack>
165166
)}
166167
</Stack>
168+
{!isPanelView && (
169+
<Stack distribution="end" gap="1">
170+
{isAZUnaware(props.resource.per_az) && (
171+
<ProjectBadges az={props.resource.per_az[0]} unit={unitName} displayValues={true} />
172+
)}
173+
{scope.isProject() && <ForbidAutogrowth {...forbidAutogrowthForwardProps} />}
174+
</Stack>
175+
)}
167176
<ResourceBarBuilder
168177
scope={scope}
169178
resource={resource}
179+
categoryName={categoryName}
170180
unit={unitName}
171181
barType={"total"}
172182
isEditableResource={editableResource}

src/components/mainView/Resource.test.js

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe("Resource tests", () => {
4343
quota: 500,
4444
capacity: 0,
4545
commitmentSum: 10,
46-
editableResource: false,
46+
editableResource: true,
4747
per_az: [["az1", { projects_usage: 10 }]],
4848
};
4949
let forwardProps = {
@@ -64,7 +64,7 @@ describe("Resource tests", () => {
6464
</StoreProvider>
6565
</PortalProvider>
6666
);
67-
const { result, rerender } = await waitFor(() => {
67+
const { result } = await waitFor(() => {
6868
return renderHook(
6969
() => ({
7070
globalStore: globalStore(),
@@ -78,40 +78,29 @@ describe("Resource tests", () => {
7878
act(() => {
7979
result.current.globalStoreActions.setScope(scope);
8080
});
81-
// resource does not allow commitments, therfore the maxQuota edit option should be displayed.
82-
expect(screen.getByTestId("maxQuotaEdit")).toBeInTheDocument();
83-
// edit option should not be invisible if no edit previleges are present
84-
forwardProps = { ...forwardProps, canEdit: false };
85-
rerender();
86-
act(() => {
87-
result.current.globalStoreActions.setScope(scope);
88-
});
89-
expect(screen.queryByTestId("maxQuotaEdit")).not.toBeInTheDocument();
90-
forwardProps = { ...forwardProps, canEdit: true };
91-
rerender();
92-
// resource allows commitments, therefore the maxQuota edit option should not be displayed.
93-
res.editableResource = true;
94-
rerender();
81+
// The forbidAutogrowthSwitch edit option is available for project level.
9582
act(() => {
9683
result.current.globalStoreActions.setScope(scope);
9784
});
98-
expect(screen.queryByTestId("maxQuotaEdit")).not.toBeInTheDocument();
85+
expect(screen.queryByTestId("forbidAutogrowthSwitch")).toBeInTheDocument();
9986
expect(screen.getByTestId("edit/testResource")).toBeInTheDocument();
10087
expect(screen.queryByTestId("setMaxQuotaPanel")).not.toBeInTheDocument();
10188

102-
// Domain level
89+
// Domain level allows setting Max-Quota.
10390
scope = new Scope({ domainID: "456" });
10491
act(() => {
10592
result.current.globalStoreActions.setScope(scope);
10693
});
10794
expect(screen.getByTestId("setMaxQuotaPanel")).toBeInTheDocument();
95+
expect(screen.queryByTestId("forbidAutogrowthSwitch")).not.toBeInTheDocument();
10896

109-
// Cluster level
97+
// Cluster level allows setting Max-Quota.
11098
scope = new Scope({});
11199
act(() => {
112100
result.current.globalStoreActions.setScope(scope);
113101
});
114102
expect(screen.getByTestId("setMaxQuotaPanel")).toBeInTheDocument();
103+
expect(screen.queryByTestId("forbidAutogrowthSwitch")).not.toBeInTheDocument();
115104
});
116105
});
117106

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Copyright 2024 SAP SE
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from "react";
18+
import { Icon, Message, Modal, Spinner, Switch } from "@cloudoperators/juno-ui-components/index";
19+
import ToolTipWrapper from "../../shared/ToolTipWrapper";
20+
import { useMutation } from "@tanstack/react-query";
21+
import { projectStoreActions } from "../../StoreProvider";
22+
import { Unit } from "../../../lib/unit";
23+
24+
const ForbidAutogrowth = (props) => {
25+
const { editMode = false } = props;
26+
const { resource, serviceType } = props;
27+
const { setRefetchProjectAPI } = projectStoreActions();
28+
const [autogrowthForbidden, setAutogrowthForbidden] = React.useState(resource?.forbid_autogrowth ?? false);
29+
const [showModal, setShowModal] = React.useState(false);
30+
const mutatation = useMutation({ mutationKey: ["forbidAutogrowth"] });
31+
const maxQuotaValue = resource?.max_quota;
32+
const unit = new Unit(resource?.unit);
33+
34+
function handleAction(autogrowthForbidden) {
35+
const parseTarget = {
36+
project: {
37+
services: [
38+
{
39+
type: serviceType,
40+
resources: [
41+
{
42+
name: resource.name,
43+
forbid_autogrowth: autogrowthForbidden,
44+
},
45+
],
46+
},
47+
],
48+
},
49+
};
50+
51+
mutatation.mutate(
52+
{ payload: parseTarget },
53+
{
54+
onSuccess: () => {
55+
setRefetchProjectAPI(true);
56+
setShowModal(false);
57+
},
58+
}
59+
);
60+
}
61+
62+
return (
63+
<>
64+
<span className={"gap-1 inline-flex"}>
65+
{mutatation.isLoading && <Spinner className="mr-0" />}
66+
Disable PAYG:
67+
{maxQuotaValue >= 0 && (
68+
<ToolTipWrapper
69+
trigger={<Icon icon="info" size="18" />}
70+
content={`Current Max-Quota: ${unit.format(maxQuotaValue)}`}
71+
/>
72+
)}
73+
<Switch
74+
data-testid="forbidAutogrowthSwitch"
75+
on={autogrowthForbidden}
76+
disabled={!editMode}
77+
onClick={() => {
78+
setAutogrowthForbidden(!autogrowthForbidden);
79+
setShowModal(true);
80+
}}
81+
/>
82+
</span>
83+
<Modal
84+
open={showModal}
85+
heading="Pay-As-You-Go"
86+
confirmButtonLabel="Yes"
87+
cancelButtonLabel="No"
88+
onConfirm={() => {
89+
handleAction(autogrowthForbidden);
90+
}}
91+
onCancel={() => {
92+
setAutogrowthForbidden(!autogrowthForbidden);
93+
setShowModal(false);
94+
mutatation.reset();
95+
}}
96+
>
97+
{mutatation.isError && (
98+
<Message
99+
data-testid="forbidAutgrowthError"
100+
className="mb-2"
101+
variant="error"
102+
text={mutatation.error.message}
103+
/>
104+
)}
105+
<p data-testid="forbidAutogrowthConfirmText">
106+
Do you want to <strong>{autogrowthForbidden ? "disable" : "enable"}</strong> Pay-As-You-Go?
107+
</p>
108+
</Modal>
109+
</>
110+
);
111+
};
112+
113+
export default ForbidAutogrowth;

0 commit comments

Comments
 (0)