web/satellite: usage api refactored (#2864)
This commit is contained in:
parent
8fbb25f3b5
commit
8c24399438
@ -3,43 +3,41 @@
|
||||
|
||||
import apollo from '@/utils/apollo';
|
||||
import gql from 'graphql-tag';
|
||||
import { RequestResponse } from '@/types/response';
|
||||
import { ProjectUsage, UsageApi } from '@/types/usage';
|
||||
import { BaseGql } from '@/api/baseGql';
|
||||
|
||||
// fetchProjectUsage retrieves total project usage for a given period
|
||||
export async function fetchProjectUsage(projectId: string, since: Date, before: Date): Promise<RequestResponse<ProjectUsage>> {
|
||||
let result: RequestResponse<ProjectUsage> = new RequestResponse<ProjectUsage>();
|
||||
|
||||
let response: any = await apollo.query(
|
||||
{
|
||||
query: gql(`
|
||||
query($projectId: String!, $since: DateTime!, $before: DateTime!) {
|
||||
project(id: $projectId) {
|
||||
usage(since: $since, before: $before) {
|
||||
storage,
|
||||
egress,
|
||||
objectCount,
|
||||
since,
|
||||
before
|
||||
}
|
||||
/**
|
||||
* Exposes all project-usage-related functionality
|
||||
*/
|
||||
export class ProjectUsageApiGql extends BaseGql implements UsageApi {
|
||||
/**
|
||||
* Fetch usage
|
||||
*
|
||||
* @returns ProjectUsage
|
||||
* @throws Error
|
||||
*/
|
||||
public async get(projectId: string, since: Date, before: Date): Promise<ProjectUsage> {
|
||||
const query = `
|
||||
query($projectId: String!, $since: DateTime!, $before: DateTime!) {
|
||||
project(id: $projectId) {
|
||||
usage(since: $since, before: $before) {
|
||||
storage,
|
||||
egress,
|
||||
objectCount,
|
||||
since,
|
||||
before
|
||||
}
|
||||
}`
|
||||
),
|
||||
variables: {
|
||||
projectId: projectId,
|
||||
since: since.toISOString(),
|
||||
before: before.toISOString()
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
}
|
||||
}`;
|
||||
|
||||
if (response.errors) {
|
||||
result.errorMessage = response.errors[0].message;
|
||||
} else {
|
||||
result.isSuccess = true;
|
||||
result.data = response.data.project.usage;
|
||||
const variables = {
|
||||
projectId,
|
||||
since,
|
||||
before
|
||||
};
|
||||
|
||||
const response = await this.query(query, variables);
|
||||
|
||||
return response.data.project.usage;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -26,12 +26,12 @@
|
||||
NOTIFICATION_ACTIONS,
|
||||
PM_ACTIONS,
|
||||
API_KEYS_ACTIONS,
|
||||
PROJECT_USAGE_ACTIONS,
|
||||
PROJECT_PAYMENT_METHODS_ACTIONS
|
||||
} from '@/utils/constants/actionNames';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { Project } from '@/types/projects';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
|
||||
|
||||
@Component
|
||||
export default class ProjectSelectionDropdown extends Vue {
|
||||
@ -42,8 +42,12 @@
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_PROJECTS);
|
||||
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
|
||||
|
||||
// TODO: add types
|
||||
const usageResponse = await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
try {
|
||||
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
} catch (err) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${err.message}`);
|
||||
}
|
||||
|
||||
const paymentMethodsResponse = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
|
||||
|
||||
try {
|
||||
@ -58,10 +62,6 @@
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch api keys');
|
||||
}
|
||||
|
||||
if (!usageResponse.isSuccess) {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project usage');
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, 1);
|
||||
} catch (error) {
|
||||
|
@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<router-link class="navigation-area__item-container" v-bind:class="{disabled: isProjectNotSelected && navItem.path !== '/project-details'}" v-for="navItem in navigation" v-bind:key="navItem.name" :to="navItem.path">
|
||||
<div class="navigation-area__item-container__link-container" >
|
||||
<div v-html="navItem.svg"></div>
|
||||
<div v-html="navItem.icon"></div>
|
||||
<h1>{{navItem.name}}</h1>
|
||||
<div class="navigation-area__item-container__link-container__add-button" id="addTeamMemberPopupButtonSVG" v-if="navItem.name === 'Team'">
|
||||
<div :click="togglePopup">
|
||||
|
@ -49,7 +49,6 @@
|
||||
API_KEYS_ACTIONS,
|
||||
APP_STATE_ACTIONS,
|
||||
NOTIFICATION_ACTIONS,
|
||||
PROJECT_USAGE_ACTIONS,
|
||||
PM_ACTIONS,
|
||||
} from '@/utils/constants/actionNames';
|
||||
import Button from '@/components/common/Button.vue';
|
||||
@ -58,6 +57,7 @@
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { CreateProjectModel, Project } from '@/types/projects';
|
||||
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
@ -60,8 +60,10 @@
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { RouteConfig } from '@/router';
|
||||
import Datepicker from '@/components/project/DatePicker.vue';
|
||||
import { NOTIFICATION_ACTIONS, PROJECT_USAGE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { toUnixTimestamp } from '@/utils/time';
|
||||
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
|
||||
import { DateRange } from '@/types/usage';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@ -107,12 +109,20 @@
|
||||
return this.$store.state.usageModule.projectUsage.objectCount.toPrecision(5);
|
||||
}
|
||||
|
||||
public mounted(): void {
|
||||
this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
public async mounted(): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
} catch (e) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
public beforeRouteLeave(to, from, next): void {
|
||||
this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP, this.dateRange);
|
||||
public async beforeRouteLeave(to, from, next): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP, this.dateRange);
|
||||
} catch (e) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
|
||||
}
|
||||
|
||||
const buttons = [...(document as any).querySelectorAll('.usage-report-container__options-area__option')];
|
||||
buttons.forEach(option => {
|
||||
@ -130,18 +140,20 @@
|
||||
public async onCurrentRollupClick(event: any): Promise<void> {
|
||||
this.onButtonClickAction(event);
|
||||
|
||||
const response = await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
if (!response.isSuccess) {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project usage');
|
||||
try {
|
||||
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
} catch (e) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async onPreviousRollupClick(event: any): Promise<void> {
|
||||
this.onButtonClickAction(event);
|
||||
|
||||
const response = await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_PREVIOUS_ROLLUP);
|
||||
if (!response.isSuccess) {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project usage');
|
||||
try {
|
||||
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_PREVIOUS_ROLLUP);
|
||||
} catch (e) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,15 +187,19 @@
|
||||
let endDate = isInverted ? firstDate : secondDate;
|
||||
|
||||
endDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate(), 23, 59, 59));
|
||||
|
||||
if (now.getUTCFullYear() === endDate.getUTCFullYear() &&
|
||||
now.getUTCMonth() === endDate.getUTCMonth() &&
|
||||
now.getUTCDate() === endDate.getUTCDate()) {
|
||||
endDate = now;
|
||||
}
|
||||
|
||||
const response = await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH, {startDate, endDate});
|
||||
if (!response.isSuccess) {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project usage');
|
||||
const dateRange: DateRange = new DateRange(startDate, endDate);
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH, dateRange);
|
||||
} catch (e) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,10 @@ import { makeBucketsModule } from '@/store/modules/buckets';
|
||||
import { projectPaymentsMethodsModule } from '@/store/modules/paymentMethods';
|
||||
import { makeProjectMembersModule } from '@/store/modules/projectMembers';
|
||||
import { makeProjectsModule } from '@/store/modules/projects';
|
||||
import { usageModule } from '@/store/modules/usage';
|
||||
import { makeUsageModule } from '@/store/modules/usage';
|
||||
import { makeUsersModule } from '@/store/modules/users';
|
||||
import { ProjectsApiGql } from '@/api/projects';
|
||||
import { ProjectUsageApiGql } from '@/api/usage';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
@ -37,6 +38,7 @@ const creditsApi = new CreditsApiGql();
|
||||
const bucketsApi = new BucketsApiGql();
|
||||
const projectMembersApi = new ProjectMembersApiGql();
|
||||
const projectsApi = new ProjectsApiGql();
|
||||
const projectUsageApi = new ProjectUsageApiGql();
|
||||
|
||||
// Satellite store (vuex)
|
||||
const store = new Vuex.Store({
|
||||
@ -49,7 +51,7 @@ const store = new Vuex.Store({
|
||||
projectPaymentsMethodsModule,
|
||||
usersModule: makeUsersModule(usersApi),
|
||||
projectsModule: makeProjectsModule(projectsApi),
|
||||
usageModule,
|
||||
usageModule: makeUsageModule(projectUsageApi),
|
||||
bucketUsageModule: makeBucketsModule(bucketsApi),
|
||||
}
|
||||
});
|
||||
|
@ -1,78 +1,91 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { PROJECT_USAGE_MUTATIONS } from '@/store/mutationConstants';
|
||||
import { PROJECT_USAGE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { fetchProjectUsage } from '@/api/usage';
|
||||
import { RequestResponse } from '@/types/response';
|
||||
import { ProjectUsageApiGql } from '@/api/usage';
|
||||
import { StoreModule } from '@/store';
|
||||
import { ProjectUsage, DateRange } from '@/types/usage';
|
||||
|
||||
export const usageModule = {
|
||||
state: {
|
||||
projectUsage: {storage: 0, egress: 0, objectCount: 0} as ProjectUsage,
|
||||
startDate: new Date(),
|
||||
endDate: new Date()
|
||||
},
|
||||
mutations: {
|
||||
[PROJECT_USAGE_MUTATIONS.FETCH](state: any, projectUsage: ProjectUsage) {
|
||||
state.projectUsage = projectUsage;
|
||||
},
|
||||
// TODO: create type here instead of {startDate, endDate}
|
||||
[PROJECT_USAGE_MUTATIONS.SET_DATE](state: any, {startDate, endDate}: any) {
|
||||
state.startDate = startDate as Date;
|
||||
state.endDate = endDate as Date;
|
||||
},
|
||||
[PROJECT_USAGE_MUTATIONS.CLEAR](state:any) {
|
||||
state.projectUsage = {storage: 0, egress: 0, objectCount: 0} as ProjectUsage;
|
||||
state.startDate = new Date();
|
||||
state.endData = new Date();
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
[PROJECT_USAGE_ACTIONS.FETCH]: async function({commit, rootGetters}: any, {startDate, endDate}: any): Promise<RequestResponse<ProjectUsage>> {
|
||||
const projectID = rootGetters.selectedProject.id;
|
||||
|
||||
let result: RequestResponse<ProjectUsage> = await fetchProjectUsage(projectID, startDate, endDate);
|
||||
|
||||
if (result.isSuccess) {
|
||||
commit(PROJECT_USAGE_MUTATIONS.SET_DATE, {startDate, endDate});
|
||||
commit(PROJECT_USAGE_MUTATIONS.FETCH, result.data);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
[PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP]: async function({commit, rootGetters}: any): Promise<RequestResponse<ProjectUsage>> {
|
||||
const projectID: string = rootGetters.selectedProject.id;
|
||||
|
||||
const endDate = new Date();
|
||||
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), 1));
|
||||
|
||||
let result: RequestResponse<ProjectUsage> = await fetchProjectUsage(projectID, startDate, endDate);
|
||||
|
||||
if (result.isSuccess) {
|
||||
commit(PROJECT_USAGE_MUTATIONS.SET_DATE, {startDate, endDate});
|
||||
commit(PROJECT_USAGE_MUTATIONS.FETCH, result.data);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
[PROJECT_USAGE_ACTIONS.FETCH_PREVIOUS_ROLLUP]: async function({commit, rootGetters}: any): Promise<RequestResponse<ProjectUsage>> {
|
||||
const projectID = rootGetters.selectedProject.id;
|
||||
|
||||
const date = new Date();
|
||||
const startDate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() - 1, 1));
|
||||
const endDate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 0, 23, 59, 59));
|
||||
|
||||
let result = await fetchProjectUsage(projectID, startDate, endDate);
|
||||
|
||||
if (result.isSuccess) {
|
||||
commit(PROJECT_USAGE_MUTATIONS.SET_DATE, {startDate, endDate});
|
||||
commit(PROJECT_USAGE_MUTATIONS.FETCH, result.data);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
[PROJECT_USAGE_ACTIONS.CLEAR]: function({commit}): void {
|
||||
commit(PROJECT_USAGE_MUTATIONS.CLEAR);
|
||||
}
|
||||
}
|
||||
export const PROJECT_USAGE_ACTIONS = {
|
||||
FETCH: 'fetchProjectUsage',
|
||||
FETCH_CURRENT_ROLLUP: 'fetchCurrentProjectUsage',
|
||||
FETCH_PREVIOUS_ROLLUP: 'fetchPreviousProjectUsage',
|
||||
CLEAR: 'clearProjectUsage',
|
||||
};
|
||||
|
||||
export const PROJECT_USAGE_MUTATIONS = {
|
||||
SET_PROJECT_USAGE: 'SET_PROJECT_USAGE',
|
||||
SET_DATE: 'SET_DATE_PROJECT_USAGE',
|
||||
CLEAR: 'CLEAR_PROJECT_USAGE'
|
||||
};
|
||||
|
||||
const defaultState = new ProjectUsage(0, 0, 0, new Date(), new Date());
|
||||
|
||||
class UsageState {
|
||||
public projectUsage: ProjectUsage = defaultState;
|
||||
public startDate: Date = new Date();
|
||||
public endDate: Date = new Date();
|
||||
}
|
||||
|
||||
export function makeUsageModule(api: ProjectUsageApiGql): StoreModule<UsageState> {
|
||||
return {
|
||||
state: new UsageState(),
|
||||
mutations: {
|
||||
[PROJECT_USAGE_MUTATIONS.SET_PROJECT_USAGE](state: UsageState, projectUsage: ProjectUsage) {
|
||||
state.projectUsage = projectUsage;
|
||||
},
|
||||
[PROJECT_USAGE_MUTATIONS.SET_DATE](state: UsageState, dateRange: DateRange) {
|
||||
state.startDate = dateRange.startDate;
|
||||
state.endDate = dateRange.endDate;
|
||||
},
|
||||
[PROJECT_USAGE_MUTATIONS.CLEAR](state: UsageState) {
|
||||
state.projectUsage = defaultState;
|
||||
state.startDate = new Date();
|
||||
state.endDate = new Date();
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
[PROJECT_USAGE_ACTIONS.FETCH]: async function({commit, rootGetters}: any, dateRange: DateRange): Promise<ProjectUsage> {
|
||||
const projectID = rootGetters.selectedProject.id;
|
||||
|
||||
let usage: ProjectUsage = await api.get(projectID, dateRange.startDate, dateRange.endDate);
|
||||
|
||||
commit(PROJECT_USAGE_MUTATIONS.SET_DATE, dateRange);
|
||||
commit(PROJECT_USAGE_MUTATIONS.SET_PROJECT_USAGE, usage);
|
||||
|
||||
return usage;
|
||||
},
|
||||
[PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP]: async function({commit, rootGetters}: any): Promise<ProjectUsage> {
|
||||
const projectID: string = rootGetters.selectedProject.id;
|
||||
|
||||
const endDate = new Date();
|
||||
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), 1));
|
||||
const dateRange = new DateRange(startDate, endDate);
|
||||
|
||||
let usage: ProjectUsage = await api.get(projectID, dateRange.startDate, dateRange.endDate);
|
||||
|
||||
commit(PROJECT_USAGE_MUTATIONS.SET_DATE, dateRange);
|
||||
commit(PROJECT_USAGE_MUTATIONS.SET_PROJECT_USAGE, usage);
|
||||
|
||||
return usage;
|
||||
},
|
||||
[PROJECT_USAGE_ACTIONS.FETCH_PREVIOUS_ROLLUP]: async function({commit, rootGetters}: any): Promise<ProjectUsage> {
|
||||
const projectID = rootGetters.selectedProject.id;
|
||||
|
||||
const date = new Date();
|
||||
const startDate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() - 1, 1));
|
||||
const endDate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 0, 23, 59, 59));
|
||||
const dateRange = new DateRange(startDate, endDate);
|
||||
|
||||
let usage: ProjectUsage = await api.get(projectID, dateRange.startDate, dateRange.endDate);
|
||||
|
||||
commit(PROJECT_USAGE_MUTATIONS.SET_DATE, dateRange);
|
||||
commit(PROJECT_USAGE_MUTATIONS.SET_PROJECT_USAGE, usage);
|
||||
|
||||
return usage;
|
||||
},
|
||||
[PROJECT_USAGE_ACTIONS.CLEAR]: function({commit}): void {
|
||||
commit(PROJECT_USAGE_MUTATIONS.CLEAR);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -10,12 +10,6 @@ export const API_KEYS_MUTATIONS = {
|
||||
CLEAR: 'clear',
|
||||
};
|
||||
|
||||
export const PROJECT_USAGE_MUTATIONS = {
|
||||
FETCH: 'FETCH_PROJECT_USAGE',
|
||||
SET_DATE: 'SET_DATE_PROJECT_USAGE',
|
||||
CLEAR: 'CLEAR_PROJECT_USAGE'
|
||||
};
|
||||
|
||||
export const NOTIFICATION_MUTATIONS = {
|
||||
ADD: 'ADD_NOTIFICATION',
|
||||
DELETE: 'DELETE_NOTIFICATION',
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
/**
|
||||
* Exposes all user-related functionality
|
||||
* Exposes all project-related functionality
|
||||
*/
|
||||
export interface ProjectsApi {
|
||||
/**
|
||||
|
11
web/satellite/src/types/usage.d.ts
vendored
11
web/satellite/src/types/usage.d.ts
vendored
@ -1,11 +0,0 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
// ProjectUsage sums usage for given period
|
||||
declare type ProjectUsage = {
|
||||
storage: number,
|
||||
egress: number,
|
||||
objectCount: number,
|
||||
since: Date,
|
||||
before: Date,
|
||||
};
|
42
web/satellite/src/types/usage.ts
Normal file
42
web/satellite/src/types/usage.ts
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
// ProjectUsage sums usage for given period
|
||||
export class ProjectUsage {
|
||||
public storage: number;
|
||||
public egress: number;
|
||||
public objectCount: number;
|
||||
public since: Date;
|
||||
public before: Date;
|
||||
|
||||
public constructor(storage: number, egress: number, objectCount: number, since: Date, before: Date) {
|
||||
this.storage = storage;
|
||||
this.egress = egress;
|
||||
this.objectCount = objectCount;
|
||||
this.since = since;
|
||||
this.before = before;
|
||||
}
|
||||
}
|
||||
|
||||
export class DateRange {
|
||||
public startDate: Date = new Date();
|
||||
public endDate: Date = new Date();
|
||||
|
||||
public constructor(startDate: Date, endDate: Date) {
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes all project-usage-related functionality
|
||||
*/
|
||||
export interface UsageApi {
|
||||
/**
|
||||
* Fetch usage
|
||||
*
|
||||
* @returns ProjectUsage
|
||||
* @throws Error
|
||||
*/
|
||||
get(projectId: string, since: Date, before: Date): Promise<ProjectUsage>;
|
||||
}
|
@ -54,13 +54,6 @@ export const API_KEYS_ACTIONS = {
|
||||
CLEAR_SELECTION: 'clearAPIKeySelection'
|
||||
};
|
||||
|
||||
export const PROJECT_USAGE_ACTIONS = {
|
||||
FETCH: 'fetchProjectUsage',
|
||||
FETCH_CURRENT_ROLLUP: 'fetchCurrentProjectUsage',
|
||||
FETCH_PREVIOUS_ROLLUP: 'fetchPreviousProjectUsage',
|
||||
CLEAR: 'clearProjectUsage',
|
||||
};
|
||||
|
||||
export const PROJECT_PAYMENT_METHODS_ACTIONS = {
|
||||
ADD: 'addProjectPaymentMethod',
|
||||
FETCH: 'fetchProjectPaymentMethods',
|
||||
|
@ -29,7 +29,6 @@
|
||||
APP_STATE_ACTIONS,
|
||||
NOTIFICATION_ACTIONS,
|
||||
PM_ACTIONS,
|
||||
PROJECT_USAGE_ACTIONS,
|
||||
PROJECT_PAYMENT_METHODS_ACTIONS,
|
||||
} from '@/utils/constants/actionNames';
|
||||
import { USER_ACTIONS } from '@/store/modules/users';
|
||||
@ -37,18 +36,16 @@
|
||||
import { AppState } from '@/utils/constants/appStateEnum';
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
import { Project } from '@/types/projects';
|
||||
import { RequestResponse } from '@/types/response';
|
||||
import router, { RouteConfig } from '@/router';
|
||||
import { RouteConfig } from '@/router';
|
||||
import { User } from '@/types/users';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
|
||||
|
||||
@Component({
|
||||
mounted: async function() {
|
||||
setTimeout(async () => {
|
||||
let user: User;
|
||||
|
||||
try {
|
||||
user = await this.$store.dispatch(USER_ACTIONS.GET);
|
||||
await this.$store.dispatch(USER_ACTIONS.GET);
|
||||
} catch (error) {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.ERROR);
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
|
||||
@ -89,9 +86,10 @@
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch api keys');
|
||||
}
|
||||
|
||||
const usageResponse = await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
if (!usageResponse.isSuccess) {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project usage');
|
||||
try {
|
||||
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
} catch (e) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
|
154
web/satellite/tests/unit/store/usage.spec.ts
Normal file
154
web/satellite/tests/unit/store/usage.spec.ts
Normal file
@ -0,0 +1,154 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import Vuex from 'vuex';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
import { ProjectUsageApiGql } from '@/api/usage';
|
||||
import { makeUsageModule, PROJECT_USAGE_ACTIONS, PROJECT_USAGE_MUTATIONS } from '@/store/modules/usage';
|
||||
import { DateRange, ProjectUsage } from '@/types/usage';
|
||||
import { ProjectsApiGql } from '@/api/projects';
|
||||
import { makeProjectsModule } from '@/store/modules/projects';
|
||||
import { Project } from '@/types/projects';
|
||||
|
||||
const Vue = createLocalVue();
|
||||
const projectUsageApi = new ProjectUsageApiGql();
|
||||
const usageModule = makeUsageModule(projectUsageApi);
|
||||
|
||||
const projectsApi = new ProjectsApiGql();
|
||||
const projectsModule = makeProjectsModule(projectsApi);
|
||||
const selectedProject = new Project('', '', '', '');
|
||||
selectedProject.id = '1';
|
||||
projectsModule.state.selectedProject = selectedProject;
|
||||
|
||||
let testDate1 = new Date();
|
||||
testDate1.setDate(1);
|
||||
let testDate2 = new Date();
|
||||
testDate2.setDate(2);
|
||||
const testUsage = new ProjectUsage(2, 3, 4, testDate1, testDate2);
|
||||
const now = new Date();
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({ modules: { usageModule, projectsModule } });
|
||||
|
||||
const state = (store.state as any).usageModule;
|
||||
|
||||
describe('mutations', () => {
|
||||
beforeEach(() => {
|
||||
createLocalVue().use(Vuex);
|
||||
});
|
||||
|
||||
it('fetch project usage', () => {
|
||||
store.commit(PROJECT_USAGE_MUTATIONS.SET_PROJECT_USAGE, testUsage);
|
||||
|
||||
expect(state.projectUsage.storage).toBe(2);
|
||||
expect(state.projectUsage.egress).toBe(3);
|
||||
expect(state.projectUsage.objectCount).toBe(4);
|
||||
expect(state.startDate.toDateString()).toBe(now.toDateString());
|
||||
expect(state.endDate.toDateString()).toBe(now.toDateString());
|
||||
});
|
||||
|
||||
it('set dates', () => {
|
||||
const dateRange: DateRange = new DateRange(testDate1, testDate2);
|
||||
store.commit(PROJECT_USAGE_MUTATIONS.SET_DATE, dateRange);
|
||||
|
||||
expect(state.startDate.toDateString()).toBe(testDate1.toDateString());
|
||||
expect(state.endDate.toDateString()).toBe(testDate2.toDateString());
|
||||
});
|
||||
|
||||
it('clear usage', () => {
|
||||
store.commit(PROJECT_USAGE_MUTATIONS.CLEAR);
|
||||
|
||||
expect(state.projectUsage.storage).toBe(0);
|
||||
expect(state.projectUsage.egress).toBe(0);
|
||||
expect(state.projectUsage.objectCount).toBe(0);
|
||||
expect(state.startDate.toDateString()).toBe(now.toDateString());
|
||||
expect(state.endDate.toDateString()).toBe(now.toDateString());
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
createLocalVue().use(Vuex);
|
||||
});
|
||||
|
||||
it('success fetch project usage', async () => {
|
||||
jest.spyOn(projectUsageApi, 'get').mockReturnValue(
|
||||
Promise.resolve(testUsage)
|
||||
);
|
||||
const dateRange: DateRange = new DateRange(testDate1, testDate2);
|
||||
|
||||
await store.dispatch(PROJECT_USAGE_ACTIONS.FETCH, dateRange);
|
||||
|
||||
expect(state.projectUsage.storage).toBe(2);
|
||||
expect(state.projectUsage.egress).toBe(3);
|
||||
expect(state.projectUsage.objectCount).toBe(4);
|
||||
expect(state.startDate.toDateString()).toBe(testDate1.toDateString());
|
||||
expect(state.endDate.toDateString()).toBe(testDate2.toDateString());
|
||||
});
|
||||
|
||||
it('success fetch current project usage', async () => {
|
||||
jest.spyOn(projectUsageApi, 'get').mockReturnValue(
|
||||
Promise.resolve(testUsage)
|
||||
);
|
||||
|
||||
const firstDate = new Date();
|
||||
firstDate.setDate(1);
|
||||
|
||||
await store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
|
||||
expect(state.projectUsage.storage).toBe(2);
|
||||
expect(state.projectUsage.egress).toBe(3);
|
||||
expect(state.projectUsage.objectCount).toBe(4);
|
||||
expect(state.startDate.toDateString()).toBe(firstDate.toDateString());
|
||||
expect(state.endDate.toDateString()).toBe(now.toDateString());
|
||||
});
|
||||
|
||||
it('success fetch previous project usage', async () => {
|
||||
jest.spyOn(projectUsageApi, 'get').mockReturnValue(
|
||||
Promise.resolve(testUsage)
|
||||
);
|
||||
|
||||
const firstDate = new Date();
|
||||
firstDate.setMonth(firstDate.getMonth() - 1);
|
||||
firstDate.setDate(1);
|
||||
|
||||
const secondDate = new Date();
|
||||
secondDate.setDate(1);
|
||||
|
||||
await store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_PREVIOUS_ROLLUP);
|
||||
|
||||
expect(state.projectUsage.storage).toBe(2);
|
||||
expect(state.projectUsage.egress).toBe(3);
|
||||
expect(state.projectUsage.objectCount).toBe(4);
|
||||
expect(state.startDate.toDateString()).toBe(firstDate.toDateString());
|
||||
expect(state.endDate.toDateString()).toBe(secondDate.toDateString());
|
||||
});
|
||||
|
||||
it('success clear usage', async () => {
|
||||
await store.dispatch(PROJECT_USAGE_ACTIONS.CLEAR);
|
||||
|
||||
expect(state.projectUsage.storage).toBe(0);
|
||||
expect(state.projectUsage.egress).toBe(0);
|
||||
expect(state.projectUsage.objectCount).toBe(0);
|
||||
expect(state.startDate.toDateString()).toBe(now.toDateString());
|
||||
expect(state.endDate.toDateString()).toBe(now.toDateString());
|
||||
});
|
||||
|
||||
it('create throws an error when create api call fails', async () => {
|
||||
state.projects = [];
|
||||
jest.spyOn(projectUsageApi, 'get').mockImplementation(() => { throw new Error(); });
|
||||
|
||||
try {
|
||||
await store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_PREVIOUS_ROLLUP);
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(state.projectUsage.storage).toBe(0);
|
||||
expect(state.projectUsage.egress).toBe(0);
|
||||
expect(state.projectUsage.objectCount).toBe(0);
|
||||
expect(state.startDate.toDateString()).toBe(now.toDateString());
|
||||
expect(state.endDate.toDateString()).toBe(now.toDateString());
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user