web/satellite/vuetify-poc: add project usages to billing
This change adds project usage and cost per project to the vuetify app. Issue: https://github.com/storj/storj/issues/6117 Change-Id: I8921aacb6bb24b41794008100ea6e52deed76b60
This commit is contained in:
parent
4964ca5ffb
commit
f1e8cdfe3e
@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<h4 class="mt-4">Costs per project</h4>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<usage-and-charges-item-component v-for="projectID of projectIds" :key="projectID" :project-id="projectID" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
VRow,
|
||||
VCol,
|
||||
} from 'vuetify/components';
|
||||
|
||||
import UsageAndChargesItemComponent from '@poc/components/billing/UsageAndChargesItemComponent.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
projectIds: string[],
|
||||
}>();
|
||||
</script>
|
@ -0,0 +1,205 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<v-card rounded="lg" variant="flat" :border="true" class="mb-4">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel rounded="lg">
|
||||
<v-expansion-panel-title>
|
||||
<v-row justify="space-between" align="center">
|
||||
<v-col>
|
||||
{{ projectName }}
|
||||
</v-col>
|
||||
<v-col class="text-start text-sm-end">
|
||||
<span class="align-end">
|
||||
Estimated Total
|
||||
<span
|
||||
class="font-weight-bold"
|
||||
>{{ centsToDollars(projectCharges.getProjectPrice(projectId)) }}
|
||||
</span>
|
||||
</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<v-table density="compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">
|
||||
Resource
|
||||
</th>
|
||||
<th class="text-left d-none d-md-table-cell">
|
||||
Period
|
||||
</th>
|
||||
<th class="text-left d-none d-sm-table-cell">
|
||||
Usage
|
||||
</th>
|
||||
<th class="text-right">
|
||||
Cost
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="[partner, charge] in partnerCharges"
|
||||
:key="partner"
|
||||
>
|
||||
<td>
|
||||
<p>Storage <span class="d-none d-md-inline">({{ getStoragePrice(partner) }} per Gigabyte-Month)</span></p>
|
||||
<p>Egress <span class="d-none d-md-inline">({{ getEgressPrice(partner) }} per GB)</span></p>
|
||||
<p>Segments <span class="d-none d-md-inline">({{ getSegmentPrice(partner) }} per Segment-Month)</span></p>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell"><p v-for="i in 3" :key="i">{{ getPeriod(charge) }}</p></td>
|
||||
<td class="d-none d-sm-table-cell">
|
||||
<p>{{ getStorageFormatted(charge) }} Gigabyte-month</p>
|
||||
<p>{{ getEgressAmountAndDimension(charge) }}</p>
|
||||
<p>{{ getSegmentCountFormatted(charge) }} Segment-month</p>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<p>{{ centsToDollars(charge.storagePrice) }}</p>
|
||||
<p>{{ centsToDollars(charge.egressPrice) }}</p>
|
||||
<p>{{ centsToDollars(charge.segmentPrice) }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
VCard,
|
||||
VCol,
|
||||
VExpansionPanel,
|
||||
VExpansionPanels,
|
||||
VExpansionPanelText,
|
||||
VExpansionPanelTitle,
|
||||
VRow,
|
||||
VTable,
|
||||
} from 'vuetify/components';
|
||||
|
||||
import { CENTS_MB_TO_DOLLARS_GB_SHIFT, centsToDollars, decimalShift, formatPrice } from '@/utils/strings';
|
||||
import { ProjectCharge, ProjectCharges, ProjectUsagePriceModel } from '@/types/payments';
|
||||
import { Project } from '@/types/projects';
|
||||
import { Size } from '@/utils/bytesSize';
|
||||
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
|
||||
import { useBillingStore } from '@/store/modules/billingStore';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
|
||||
/**
|
||||
* HOURS_IN_MONTH constant shows amount of hours in 30-day month.
|
||||
*/
|
||||
const HOURS_IN_MONTH = 720;
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
/**
|
||||
* The ID of the project for which to show the usage and charge information.
|
||||
*/
|
||||
projectId?: string;
|
||||
}>(), {
|
||||
projectId: '',
|
||||
});
|
||||
|
||||
const billingStore = useBillingStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
/**
|
||||
* An array of tuples containing the partner name and usage charge for the specified project ID.
|
||||
*/
|
||||
const partnerCharges = computed((): [partner: string, charge: ProjectCharge][] => {
|
||||
const arr = billingStore.state.projectCharges.toArray();
|
||||
arr.sort(([partner1], [partner2]) => partner1.localeCompare(partner2));
|
||||
const tuple = arr.find(tuple => tuple[0] === props.projectId);
|
||||
return tuple ? tuple[1] : [];
|
||||
});
|
||||
|
||||
/**
|
||||
* projectName returns project name.
|
||||
*/
|
||||
const projectName = computed((): string => {
|
||||
const projects: Project[] = projectsStore.state.projects;
|
||||
const project: Project | undefined = projects.find(project => project.id === props.projectId);
|
||||
|
||||
return project?.name || '';
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns project usage price model from store.
|
||||
*/
|
||||
const projectCharges = computed((): ProjectCharges => {
|
||||
return billingStore.state.projectCharges as ProjectCharges;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns project usage price model from store.
|
||||
*/
|
||||
function getPriceModel(partner: string): ProjectUsagePriceModel {
|
||||
return projectCharges.value.getUsagePriceModel(partner) || billingStore.state.usagePriceModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string of date range.
|
||||
*/
|
||||
function getPeriod(charge: ProjectCharge): string {
|
||||
const since = `${SHORT_MONTHS_NAMES[charge.since.getUTCMonth()]} ${charge.since.getUTCDate()}`;
|
||||
const before = `${SHORT_MONTHS_NAMES[charge.before.getUTCMonth()]} ${charge.before.getUTCDate()}`;
|
||||
|
||||
return `${since} - ${before}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns formatted egress depending on amount of bytes.
|
||||
*/
|
||||
function egressFormatted(charge: ProjectCharge): Size {
|
||||
return new Size(charge.egress, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns formatted storage used in GB x month dimension.
|
||||
*/
|
||||
function getStorageFormatted(charge: ProjectCharge): string {
|
||||
const bytesInGB = 1000000000;
|
||||
|
||||
return (charge.storage / HOURS_IN_MONTH / bytesInGB).toFixed(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns formatted segment count in segment x month dimension.
|
||||
*/
|
||||
function getSegmentCountFormatted(charge: ProjectCharge): string {
|
||||
return (charge.segmentCount / HOURS_IN_MONTH).toFixed(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns storage price per GB.
|
||||
*/
|
||||
function getStoragePrice(partner: string): string {
|
||||
return formatPrice(decimalShift(getPriceModel(partner).storageMBMonthCents, CENTS_MB_TO_DOLLARS_GB_SHIFT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns egress price per GB.
|
||||
*/
|
||||
function getEgressPrice(partner: string): string {
|
||||
return formatPrice(decimalShift(getPriceModel(partner).egressMBCents, CENTS_MB_TO_DOLLARS_GB_SHIFT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns segment price.
|
||||
*/
|
||||
function getSegmentPrice(partner: string): string {
|
||||
return formatPrice(decimalShift(getPriceModel(partner).segmentMonthCents, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string of egress amount and dimension.
|
||||
*/
|
||||
function getEgressAmountAndDimension(charge: ProjectCharge): string {
|
||||
const egress = egressFormatted(charge);
|
||||
return `${egress.formattedBytes} ${egress.label}`;
|
||||
}
|
||||
</script>
|
@ -119,43 +119,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<h4 class="mt-4">Costs per project</h4>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-card rounded="lg" variant="flat" :border="true" class="mb-4">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel
|
||||
title="My First Project"
|
||||
text="Costs..."
|
||||
rounded="lg"
|
||||
/>
|
||||
</v-expansion-panels>
|
||||
</v-card>
|
||||
<v-card rounded="lg" variant="flat" :border="true" class="mb-4">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel
|
||||
title="Storj Labs"
|
||||
text="Costs..."
|
||||
rounded="lg"
|
||||
/>
|
||||
</v-expansion-panels>
|
||||
</v-card>
|
||||
<v-card rounded="lg" variant="flat" :border="true" class="mb-4">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel
|
||||
title="Pictures"
|
||||
text="Costs..."
|
||||
rounded="lg"
|
||||
/>
|
||||
</v-expansion-panels>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<usage-and-charges-component :project-ids="projectIDs" />
|
||||
</v-window-item>
|
||||
|
||||
<v-window-item>
|
||||
@ -304,15 +268,18 @@ import { Coupon, CouponDuration, CreditCard } from '@/types/payments';
|
||||
import { centsToDollars } from '@/utils/strings';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
|
||||
import CreditCardComponent from '@poc/components/CreditCardComponent.vue';
|
||||
import AddCreditCardComponent from '@poc/components/AddCreditCardComponent.vue';
|
||||
import UsageAndChargesComponent from '@poc/components/billing/UsageAndChargesComponent.vue';
|
||||
|
||||
const tab = ref<string>('Overview');
|
||||
const search = ref<string>('');
|
||||
const selected = ref([]);
|
||||
|
||||
const billingStore = useBillingStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const { isLoading, withLoading } = useLoading();
|
||||
const notify = useNotify();
|
||||
@ -349,6 +316,16 @@ const invoices = [
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* projectIDs is an array of all of the project IDs for which there exist project usage charges.
|
||||
*/
|
||||
const projectIDs = computed((): string[] => {
|
||||
return projectsStore.state.projects
|
||||
.filter(proj => billingStore.state.projectCharges.hasProject(proj.id))
|
||||
.sort((proj1, proj2) => proj1.name.localeCompare(proj2.name))
|
||||
.map(proj => proj.id);
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns price summary of all project usages.
|
||||
*/
|
||||
@ -420,6 +397,9 @@ onMounted(() => {
|
||||
billingStore.getBalance(),
|
||||
billingStore.getCoupon(),
|
||||
billingStore.getCreditCards(),
|
||||
projectsStore.getProjects(),
|
||||
billingStore.getProjectUsageAndChargesCurrentRollup(),
|
||||
billingStore.getProjectUsagePriceModel(),
|
||||
]);
|
||||
} catch (error) {
|
||||
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_AREA);
|
||||
|
Loading…
Reference in New Issue
Block a user