web/storagenode: held returned block added

WHAT: held information block on payout page

WHY: to show held and held disposed amounts

Change-Id: I34b8f17993f93d7fdbc65021d0a088c8a5490f8d
This commit is contained in:
NickolaiYurchenko 2020-07-22 18:15:24 +03:00
parent 84b6b91ee1
commit 92a336cb5a
7 changed files with 279 additions and 18 deletions

View File

@ -0,0 +1,120 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<section class="total-held-area">
<div class="total-held-area__united-info-area">
<div class="total-held-area__united-info-area__item">
<p class="total-held-area__united-info-area__item__label">Held Amount Rate</p>
<p class="total-held-area__united-info-area__item__amount">{{ heldPercentage }}%</p>
</div>
<div class="total-held-area__united-info-area__item align-center">
<p class="total-held-area__united-info-area__item__label">Total Held Amount</p>
<p class="total-held-area__united-info-area__item__amount">{{ totalHeldAndPaid.held | centsToDollars }}</p>
</div>
<div class="total-held-area__united-info-area__item align-end">
<p class="total-held-area__united-info-area__item__label">Total Held Returned</p>
<p class="total-held-area__united-info-area__item__amount">{{ totalHeldAndPaid.disposed | centsToDollars }}</p>
</div>
</div>
<div class="total-held-area__info-area">
<SingleInfo width="100%" label="Held Amount Rate" :value="heldPercentage + '%'" />
<SingleInfo width="100%" label="Total Held Amount" :value="totalHeldAndPaid.held | centsToDollars" />
<SingleInfo width="100%" label="Total Held Returned" :value="totalHeldAndPaid.disposed | centsToDollars" />
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import SingleInfo from '@/app/components/payments/SingleInfo.vue';
import { TotalHeldAndPaid } from '@/storagenode/payouts/payouts';
@Component({
components: {
SingleInfo,
},
})
export default class TotalPayoutArea extends Vue {
public get totalHeldAndPaid(): TotalHeldAndPaid {
return this.$store.state.payoutModule.totalHeldAndPaid;
}
public get heldPercentage(): string {
return this.$store.state.payoutModule.heldPercentage;
}
}
</script>
<style scoped lang="scss">
.total-held-area {
width: 100%;
&__united-info-area {
width: calc(100% - 60px);
padding: 24px 30px;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--block-background-color);
border: 1px solid var(--block-border-color);
border-radius: 10px;
&__item {
display: flex;
flex-direction: column;
align-items: flex-start;
color: var(--regular-text-color);
&__label {
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
}
&__amount {
font-family: 'font_medium', sans-serif;
font-size: 20px;
line-height: 20px;
margin-top: 12px;
}
}
}
&__info-area {
display: none;
align-items: center;
justify-content: space-between;
}
}
.align-center {
align-items: center;
}
.align-end {
align-items: flex-end;
}
@media screen and (max-width: 780px) {
.total-held-area {
&__united-info-area {
display: none;
}
&__info-area {
display: flex;
flex-direction: column;
.info-container {
width: 100% !important;
margin-bottom: 12px;
}
}
}
}
</style>

View File

@ -4,7 +4,8 @@
import {
EstimatedPayout,
PayoutPeriod,
SatelliteHeldHistory, SatellitePayoutForPeriod,
SatelliteHeldHistory,
SatellitePayoutForPeriod,
TotalHeldAndPaid,
TotalPaystubForPeriod,
} from '@/storagenode/payouts/payouts';

View File

@ -27,10 +27,13 @@
</a>
</p>
<section class="payout-area-container__held-info-area">
<SingleInfo v-if="selectedSatellite" width="48%" label="Held Amount Rate" :value="heldPercentage + '%'" />
<SingleInfo width="48%" label="Total Held Amount" :value="totalHeld | centsToDollars" />
<TotalHeldArea v-if="isSatelliteSelected" />
<div class="row" v-else >
<SingleInfo width="48%" label="Total Held Amount" :value="totalHeldAndPaid.held | centsToDollars" />
<SingleInfo width="48%" label="Total Held Returned" :value="totalHeldAndPaid.disposed | centsToDollars" />
</div>
</section>
<HeldProgress v-if="selectedSatellite" class="payout-area-container__process-area" />
<HeldProgress v-if="isSatelliteSelected" class="payout-area-container__process-area" />
<HeldHistoryArea />
</div>
</div>
@ -45,6 +48,7 @@ import HeldHistoryTable from '@/app/components/payments/HeldHistoryMonthlyBreakd
import HeldProgress from '@/app/components/payments/HeldProgress.vue';
import PayoutHistoryTable from '@/app/components/payments/PayoutHistoryTable.vue';
import SingleInfo from '@/app/components/payments/SingleInfo.vue';
import TotalHeldArea from '@/app/components/payments/TotalHeldArea.vue';
import SatelliteSelection from '@/app/components/SatelliteSelection.vue';
import BackArrowIcon from '@/../static/images/notifications/backArrow.svg';
@ -54,11 +58,11 @@ import { NODE_ACTIONS } from '@/app/store/modules/node';
import { NOTIFICATIONS_ACTIONS } from '@/app/store/modules/notifications';
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
import { NotificationsCursor } from '@/app/types/notifications';
import { SatelliteInfo } from '@/storagenode/dashboard';
import { PayoutPeriod } from '@/storagenode/payouts/payouts';
import { PayoutPeriod, TotalHeldAndPaid } from '@/storagenode/payouts/payouts';
@Component ({
components: {
TotalHeldArea,
PayoutHistoryTable,
HeldHistoryArea,
HeldProgress,
@ -110,20 +114,15 @@ export default class PayoutArea extends Vue {
await this.$store.dispatch(APPSTATE_ACTIONS.SET_LOADING, false);
}
public get totalHeld(): number {
return this.$store.state.payoutModule.totalHeldAndPaid.held;
}
public get heldPercentage(): number {
return this.$store.state.payoutModule.heldPercentage;
public get totalHeldAndPaid(): TotalHeldAndPaid {
return this.$store.state.payoutModule.totalHeldAndPaid;
}
/**
* selectedSatellite - current selected satellite from store.
* @return SatelliteInfo - current selected satellite
* Indicates if satellite is selected.
*/
public get selectedSatellite(): SatelliteInfo {
return this.$store.state.node.selectedSatellite.id;
public get isSatelliteSelected(): boolean {
return !!this.$store.state.node.selectedSatellite.id;
}
public get payoutPeriods(): PayoutPeriod[] {
@ -222,6 +221,12 @@ export default class PayoutArea extends Vue {
}
}
.row {
display: flex;
justify-content: space-between;
width: 100%;
}
@media screen and (max-width: 890px) {
.payout-area-container {
@ -259,5 +264,9 @@ export default class PayoutArea extends Vue {
}
}
}
.row {
flex-direction: column;
}
}
</style>

View File

@ -172,6 +172,7 @@ export class TotalPaystubForPeriod {
export class TotalHeldAndPaid {
public held: number = 0;
public paid: number = 0;
public disposed: number = 0;
// TODO: remove
public currentMonthEarnings: number = 0;
@ -181,6 +182,7 @@ export class TotalHeldAndPaid {
paystubs.forEach(paystub => {
this.held += this.convertToCents(paystub.held - paystub.disposed);
this.paid += this.convertToCents(paystub.paid);
this.disposed += this.convertToCents(paystub.disposed);
});
}

View File

@ -0,0 +1,77 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import TotalHeldArea from '@/app/components/payments/TotalHeldArea.vue';
import { makeNodeModule, NODE_MUTATIONS } from '@/app/store/modules/node';
import { makePayoutModule, PAYOUT_MUTATIONS } from '@/app/store/modules/payout';
import { PayoutHttpApi } from '@/storagenode/api/payout';
import { SNOApi } from '@/storagenode/api/storagenode';
import { Paystub, TotalHeldAndPaid } from '@/storagenode/payouts/payouts';
import { PayoutService } from '@/storagenode/payouts/service';
import { Metric, Satellite, Stamp } from '@/storagenode/satellite';
import { createLocalVue, shallowMount } from '@vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.filter('centsToDollars', (cents: number): string => {
return `$${(cents / 100).toFixed(2)}`;
});
const payoutApi = new PayoutHttpApi();
const payoutService = new PayoutService(payoutApi);
const payoutModule = makePayoutModule(payoutApi, payoutService);
const nodeApi = new SNOApi();
const nodeModule = makeNodeModule(nodeApi);
const store = new Vuex.Store({ modules: { payoutModule, node: nodeModule }});
describe('TotalHeldArea', (): void => {
it('renders correctly', (): void => {
const wrapper = shallowMount(TotalHeldArea, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
it('renders correctly with actual values', async (): Promise<void> => {
const wrapper = shallowMount(TotalHeldArea, {
store,
localVue,
});
const testJoinAt = new Date(Date.UTC(2018, 0, 30));
const satelliteInfo = new Satellite(
'3',
[new Stamp()],
[],
[],
[],
111,
222,
50,
70,
new Metric(1, 1, 1, 0, 1),
new Metric(2, 1, 1, 0, 1),
testJoinAt,
);
const paystub = new Paystub();
paystub.held = 600000;
paystub.disposed = 100000;
paystub.paid = 1000000;
const totalHeldAndPaid = new TotalHeldAndPaid([paystub]);
await store.commit(NODE_MUTATIONS.SELECT_SATELLITE, satelliteInfo);
await store.commit(PAYOUT_MUTATIONS.SET_TOTAL, totalHeldAndPaid);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TotalHeldArea renders correctly 1`] = `
<section class="total-held-area">
<div class="total-held-area__united-info-area">
<div class="total-held-area__united-info-area__item">
<p class="total-held-area__united-info-area__item__label">Held Amount Rate</p>
<p class="total-held-area__united-info-area__item__amount">0%</p>
</div>
<div class="total-held-area__united-info-area__item align-center">
<p class="total-held-area__united-info-area__item__label">Total Held Amount</p>
<p class="total-held-area__united-info-area__item__amount">$0.00</p>
</div>
<div class="total-held-area__united-info-area__item align-end">
<p class="total-held-area__united-info-area__item__label">Total Held Returned</p>
<p class="total-held-area__united-info-area__item__amount">$0.00</p>
</div>
</div>
<div class="total-held-area__info-area">
<singleinfo-stub width="100%" label="Held Amount Rate" value="0%"></singleinfo-stub>
<singleinfo-stub width="100%" label="Total Held Amount" value="$0.00"></singleinfo-stub>
<singleinfo-stub width="100%" label="Total Held Returned" value="$0.00"></singleinfo-stub>
</div>
</section>
`;
exports[`TotalHeldArea renders correctly with actual values 1`] = `
<section class="total-held-area">
<div class="total-held-area__united-info-area">
<div class="total-held-area__united-info-area__item">
<p class="total-held-area__united-info-area__item__label">Held Amount Rate</p>
<p class="total-held-area__united-info-area__item__amount">0%</p>
</div>
<div class="total-held-area__united-info-area__item align-center">
<p class="total-held-area__united-info-area__item__label">Total Held Amount</p>
<p class="total-held-area__united-info-area__item__amount">$0.50</p>
</div>
<div class="total-held-area__united-info-area__item align-end">
<p class="total-held-area__united-info-area__item__label">Total Held Returned</p>
<p class="total-held-area__united-info-area__item__amount">$0.10</p>
</div>
</div>
<div class="total-held-area__info-area">
<singleinfo-stub width="100%" label="Held Amount Rate" value="0%"></singleinfo-stub>
<singleinfo-stub width="100%" label="Total Held Amount" value="$0.50"></singleinfo-stub>
<singleinfo-stub width="100%" label="Total Held Returned" value="$0.10"></singleinfo-stub>
</div>
</section>
`;

View File

@ -64,6 +64,7 @@ describe('mutations', (): void => {
expect(state.payoutModule.totalHeldAndPaid.held).toBe(50);
expect(state.payoutModule.totalHeldAndPaid.paid).toBe(100);
expect(state.payoutModule.totalHeldAndPaid.disposed).toBe(10);
expect(state.payoutModule.currentMonthEarnings).toBe(22);
});
@ -246,6 +247,7 @@ describe('actions', () => {
it('success get total', async (): Promise<void> => {
const paystub = new Paystub();
paystub.held = 100000;
paystub.disposed = 50000;
paystub.paid = 200000;
jest.spyOn(payoutApi, 'getPaystubsForPeriod').mockReturnValue(
@ -254,8 +256,9 @@ describe('actions', () => {
await store.dispatch(PAYOUT_ACTIONS.GET_TOTAL);
expect(state.payoutModule.totalHeldAndPaid.held).toBe(10);
expect(state.payoutModule.totalHeldAndPaid.held).toBe(5);
expect(state.payoutModule.totalHeldAndPaid.paid).toBe(20);
expect(state.payoutModule.totalHeldAndPaid.disposed).toBe(5);
expect(state.payoutModule.currentMonthEarnings).toBe(0);
});
@ -266,7 +269,7 @@ describe('actions', () => {
await store.dispatch(PAYOUT_ACTIONS.GET_TOTAL);
expect(true).toBe(false);
} catch (error) {
expect(state.payoutModule.totalHeldAndPaid.held).toBe(10);
expect(state.payoutModule.totalHeldAndPaid.held).toBe(5);
expect(state.payoutModule.totalHeldAndPaid.paid).toBe(20);
expect(state.payoutModule.currentMonthEarnings).toBe(0);
}