Skip to content

Commit

Permalink
Billing enhancements: storage layers (#3080) - API, tables, layers ch…
Browse files Browse the repository at this point in the history
…art (WIP)
  • Loading branch information
rodichenko committed Mar 1, 2023
1 parent a01bab9 commit ea52562
Show file tree
Hide file tree
Showing 35 changed files with 1,331 additions and 288 deletions.
102 changes: 102 additions & 0 deletions client/src/components/billing/navigation/aggregate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2017-2022 EPAM Systems, Inc. (https://www.epam.com/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const StorageAggregate = {
default: 'default',
standard: 'standard',
deepArchive: 'deep-archive',
glacier: 'glacier',
glacierIR: 'glacier-ir'
};

export {StorageAggregate};

export function getBillingGroupingOrderAggregate (aggregate) {
switch (aggregate) {
case StorageAggregate.deepArchive:
return 'DEEP_ARCHIVE';
case StorageAggregate.glacier:
return 'GLACIER';
case StorageAggregate.glacierIR:
return 'GLACIER_IR';
case StorageAggregate.standard:
return 'STANDARD';
default:
return 'STORAGE';
}
}

export function getAggregateByStorageClass (storageClass) {
switch ((storageClass || '').toUpperCase()) {
case 'DEEP_ARCHIVE': return StorageAggregate.deepArchive;
case 'GLACIER': return StorageAggregate.glacier;
case 'GLACIER_IR': return StorageAggregate.glacierIR;
case 'STANDARD': return StorageAggregate.standard;
default:
return StorageAggregate.default;
}
}

const DEFAULT_STORAGE_CLASS_ORDER = [
'STANDARD',
'DEEP_ARCHIVE',
'GLACIER',
'GLACIER_IR'
];

export {DEFAULT_STORAGE_CLASS_ORDER};

export function getStorageClassName (storageClass) {
switch ((storageClass || '').toUpperCase()) {
case 'DEEP_ARCHIVE': return 'Deep Archive';
case 'GLACIER': return 'Glacier';
case 'GLACIER_IR': return 'Glacier IR';
case 'STANDARD':
default:
return 'Standard';
}
}

export function getStorageClassByAggregate (aggregate) {
return getBillingGroupingOrderAggregate(aggregate);
}

export function getStorageClassNameByAggregate (aggregate) {
switch (aggregate) {
case StorageAggregate.deepArchive: return 'Deep Archive';
case StorageAggregate.glacier: return 'Glacier';
case StorageAggregate.glacierIR: return 'Glacier IR';
case StorageAggregate.standard: return 'Standard';
default:
return '';
}
}

export function parseStorageAggregate (aggregate) {
switch ((aggregate || '').toLowerCase()) {
case StorageAggregate.standard:
return StorageAggregate.standard;
case StorageAggregate.deepArchive:
return StorageAggregate.deepArchive;
case StorageAggregate.glacier:
return StorageAggregate.glacier;
case StorageAggregate.glacierIR:
return StorageAggregate.glacierIR;
case StorageAggregate.default:
default:
return StorageAggregate.default;
}
}
53 changes: 49 additions & 4 deletions client/src/components/billing/navigation/filter-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {observable, isObservableArray} from 'mobx';
import {Period, getPeriod} from '../../special/periods';
import RunnerType from './runner-types';
import ReportsRouting from './reports-routing';
import {parseStorageMetrics} from './metrics';
import {parseStorageAggregate, StorageAggregate} from './aggregate';

class Filter {
static RUNNER_SEPARATOR = '|';
Expand All @@ -26,6 +28,8 @@ class Filter {
@observable range;
@observable report;
@observable runner;
@observable metrics;
@observable storageAggregate;

rebuild = ({location, router}) => {
this.router = router;
Expand All @@ -34,7 +38,9 @@ class Filter {
user,
group,
range,
region
region,
metrics,
layer: storageAggregate
} = (location || {}).query || {};
if (user) {
this.runner = {
Expand All @@ -53,10 +59,28 @@ class Filter {
this.period = period;
this.range = range;
this.region = (region || '').split(Filter.REGION_SEPARATOR).filter(Boolean);
if (ReportsRouting.isStorage(this.report)) {
this.metrics = parseStorageMetrics(metrics);
} else {
this.metrics = undefined;
}
if (ReportsRouting.isObjectStorage(this.report)) {
this.storageAggregate = parseStorageAggregate(storageAggregate);
} else {
this.storageAggregate = undefined;
}
};

navigate = (navigation, strictRange = false) => {
let {report, runner, period, range, region} = navigation || {};
let {
report,
runner,
period,
range,
region,
metrics,
storageAggregate
} = navigation || {};
if (report === undefined) {
report = this.report;
}
Expand All @@ -72,7 +96,22 @@ class Filter {
if (region === undefined) {
region = this.region;
}
if (/^quotas/i.test(report)) {
if (metrics === undefined) {
metrics = this.metrics;
}
if (!ReportsRouting.isStorage(report)) {
metrics = undefined;
}
if (storageAggregate === undefined) {
storageAggregate = this.storageAggregate;
}
if (
!ReportsRouting.isObjectStorage(report) ||
storageAggregate === StorageAggregate.default
) {
storageAggregate = undefined;
}
if (ReportsRouting.isQuota(report)) {
runner = undefined;
range = undefined;
period = undefined;
Expand All @@ -96,7 +135,9 @@ class Filter {
runner && runner.type === RunnerType.group && `group=${mapRunnerId(runner.id)}`,
period && `period=${period}`,
range && `range=${range}`,
regions.length > 0 && `region=${mapRegionId(regions)}`
regions.length > 0 && `region=${mapRegionId(regions)}`,
metrics && `metrics=${metrics}`,
storageAggregate && `layer=${storageAggregate}`
].filter(Boolean);
let query = '';
if (params.length) {
Expand Down Expand Up @@ -174,6 +215,10 @@ class Filter {
periodNavigation = (period, range) => this.navigate({period, range}, true);

reportNavigation = (report, runner) => this.navigate({report, runner});

metricsNavigation = (metrics) => this.navigate({metrics});

storageAggregateNavigation = (aggregate) => this.navigate({storageAggregate: aggregate});
}

export default Filter;
38 changes: 38 additions & 0 deletions client/src/components/billing/navigation/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2017-2022 EPAM Systems, Inc. (https://www.epam.com/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const StorageMetrics = {
costs: 'costs',
volume: 'volume'
};

export function getBillingGroupingSortField (metrics) {
switch (metrics) {
case StorageMetrics.volume:
return 'USAGE';
default:
return 'COST';
}
}

export function parseStorageMetrics (metrics) {
if (/^volume$/i.test(metrics)) {
return StorageMetrics.volume;
}
return StorageMetrics.costs;
}

export {StorageMetrics};
20 changes: 20 additions & 0 deletions client/src/components/billing/navigation/reports-routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ const reportsRouting = {
getTitle: function (name) {
const match = this.configurations.find(c => c.name === name);
return match?.title || this.general.title;
},
isStorage: function (report) {
const path = this.getPath(report);
return [
this.storages.path,
this.storages.file.path,
this.storages.object.path
].includes(path);
},
isObjectStorage: function (report) {
const path = this.getPath(report);
return path === this.storages.object.path;
},
isQuota: function (report) {
const path = this.getPath(report);
return [
this.quotas.path,
this.quotas.compute.path,
this.quotas.storage.path
].includes(path);
}
};

Expand Down
12 changes: 11 additions & 1 deletion client/src/components/billing/reports/charts/bar-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function BarChart (
subChart,
subChartTitleStyle,
top = 10,
topDescription,
valueFormatter = costTickFormatter,
useImageConsumer = true,
onImageDataReceived,
Expand Down Expand Up @@ -146,6 +147,15 @@ function BarChart (
}
]
};
const getTitle = () => {
if (top && topDescription) {
return `${title} (TOP ${top}, ${topDescription})`;
}
if (top) {
return `${title} (TOP ${top})`;
}
return title;
};
const options = {
animation: {duration: 0},
scales: {
Expand Down Expand Up @@ -190,7 +200,7 @@ function BarChart (
},
title: {
display: !subChart && !!title,
text: top ? `${title} (TOP ${top})` : title,
text: getTitle(),
fontColor: reportThemes.textColor
},
legend: {
Expand Down
12 changes: 7 additions & 5 deletions client/src/components/billing/reports/charts/storage-layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ function StorageLayers (
onImageDataReceived,
reportThemes,
highlightTickFn,
highlightTickStyle = {}
highlightTickStyle = {},
loading
}
) {
// todo: connect to API and get rid of mocks
Expand All @@ -63,7 +64,9 @@ function StorageLayers (
}, []);
const maximum = getMaximum(totals);
const disabled = isNaN(maximum);
const groups = rawData.labels; // mock
const {
aggregates
} = rawData;
const chartData = {
labels: rawData.labels,
datasets: rawData.datasets.map((dataset, index) => {
Expand Down Expand Up @@ -169,7 +172,7 @@ function StorageLayers (
valueFormatter
},
[ChartClickPlugin.id]: {
handler: onSelect ? index => onSelect({key: groups[index]}) : undefined,
handler: onSelect ? index => onSelect({key: aggregates[index]}) : undefined,
scaleHandler: onScaleSelect,
axis: 'x-axis'
}
Expand Down Expand Up @@ -211,9 +214,8 @@ function StorageLayers (
<div style={{flex: 1, overflow: 'hidden'}}>
<Chart
data={chartData}
// todo: connect to API
// error={error}
// loading={loading}
loading={loading}
type="bar"
options={options}
plugins={[
Expand Down
6 changes: 4 additions & 2 deletions client/src/components/billing/reports/charts/summaryHOC.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@

import React from 'react';
import {Radio} from 'antd';
import {observer} from 'mobx-react';
import Summary, {Display} from './summary';

const SummaryHOC = WrappedComponent => {
return class extends React.Component {
class SummaryWrappedComponent extends React.Component {
state= {
display: Display.accumulative
};
Expand Down Expand Up @@ -71,7 +72,8 @@ const SummaryHOC = WrappedComponent => {
</div>
);
}
};
}
return observer(SummaryWrappedComponent);
};

export default SummaryHOC(Summary);
Loading

0 comments on commit ea52562

Please sign in to comment.