web/satellite: buckets api and store refactored (#2830)
This commit is contained in:
parent
1e099839dd
commit
87ef5e3398
59
web/satellite/src/api/buckets.ts
Normal file
59
web/satellite/src/api/buckets.ts
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { BaseGql } from '@/api/baseGql';
|
||||
import { BucketCursor, BucketPage, BucketsApi } from '@/types/buckets';
|
||||
|
||||
/**
|
||||
* BucketsApiGql is a graphql implementation of Buckets API.
|
||||
* Exposes all bucket-related functionality
|
||||
*/
|
||||
export class BucketsApiGql extends BaseGql implements BucketsApi {
|
||||
/**
|
||||
* Fetch buckets
|
||||
*
|
||||
* @returns BucketPage
|
||||
* @throws Error
|
||||
*/
|
||||
public async get(projectId: string, before: Date, cursor: BucketCursor): Promise<BucketPage> {
|
||||
const query =
|
||||
`query($projectId: String!, $before: DateTime!, $limit: Int!, $search: String!, $page: Int!) {
|
||||
project(id: $projectId) {
|
||||
bucketUsages(before: $before, cursor: {
|
||||
limit: $limit, search: $search, page: $page
|
||||
}) {
|
||||
bucketUsages {
|
||||
bucketName,
|
||||
storage,
|
||||
egress,
|
||||
objectCount,
|
||||
since,
|
||||
before
|
||||
},
|
||||
search,
|
||||
limit,
|
||||
offset,
|
||||
pageCount,
|
||||
currentPage,
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const variables = {
|
||||
projectId,
|
||||
before: before.toISOString(),
|
||||
limit: cursor.limit,
|
||||
search: cursor.search,
|
||||
page: cursor.page,
|
||||
};
|
||||
|
||||
const response = await this.query(query, variables);
|
||||
|
||||
return this.fromJson(response.data.project.bucketUsages);
|
||||
}
|
||||
|
||||
private fromJson(bucketPage): BucketPage {
|
||||
return new BucketPage(bucketPage.bucketUsages, bucketPage.search, bucketPage.limit, bucketPage.offset, bucketPage.pageCount, bucketPage.currentPage, bucketPage.totalCount);
|
||||
}
|
||||
}
|
@ -43,55 +43,3 @@ export async function fetchProjectUsage(projectId: string, since: Date, before:
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// fetchBucketUsages retrieves bucket usage totals for a particular project
|
||||
export async function fetchBucketUsages(projectId: string, before: Date, cursor: BucketUsageCursor): Promise<RequestResponse<BucketUsagePage>> {
|
||||
let result: RequestResponse<BucketUsagePage> = new RequestResponse<BucketUsagePage>();
|
||||
|
||||
let response: any = await apollo.query(
|
||||
{
|
||||
query: gql(`
|
||||
query($projectId: String!, $before: DateTime!, $limit: Int!, $search: String!, $page: Int!) {
|
||||
project(id: $projectId) {
|
||||
bucketUsages(before: $before, cursor: {
|
||||
limit: $limit, search: $search, page: $page
|
||||
}) {
|
||||
bucketUsages{
|
||||
bucketName,
|
||||
storage,
|
||||
egress,
|
||||
objectCount,
|
||||
since,
|
||||
before
|
||||
},
|
||||
search,
|
||||
limit,
|
||||
offset,
|
||||
pageCount,
|
||||
currentPage,
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}`
|
||||
),
|
||||
variables: {
|
||||
projectId: projectId,
|
||||
before: before.toISOString(),
|
||||
limit: cursor.limit,
|
||||
search: cursor.search,
|
||||
page: cursor.page
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
|
||||
if (response.errors) {
|
||||
result.errorMessage = response.errors[0].message;
|
||||
} else {
|
||||
result.isSuccess = true;
|
||||
result.data = response.data.project.bucketUsages;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -3,15 +3,15 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NoBucketArea v-if="!totalCountOfBuckets && !search" />
|
||||
<NoBucketArea v-if="!totalCount && !search" />
|
||||
<div class="buckets-overflow" v-else>
|
||||
<div class="buckets-header">
|
||||
<p>Buckets</p>
|
||||
<HeaderComponent class="buckets-header-component" placeholder="Buckets" :search="fetch"/>
|
||||
<HeaderComponent class="buckets-header-component" placeHolder="Buckets" :search="fetch"/>
|
||||
</div>
|
||||
<div v-if="buckets.length" class="buckets-container">
|
||||
<SortingHeader />
|
||||
<List :dataSet="buckets" :itemComponent="itemComponent"/>
|
||||
<List :dataSet="buckets" :itemComponent="itemComponent" :onItemClick="doNothing"/>
|
||||
<Pagination :totalPageCount="totalPageCount" :onPageClickCallback="onPageClick" />
|
||||
</div>
|
||||
<EmptyState
|
||||
@ -33,7 +33,14 @@
|
||||
import Pagination from '@/components/common/Pagination.vue';
|
||||
import List from '@/components/common/List.vue';
|
||||
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
|
||||
import { BUCKET_USAGE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { Bucket } from '@/types/buckets';
|
||||
|
||||
const {
|
||||
FETCH,
|
||||
SET_SEARCH,
|
||||
} = BUCKET_ACTIONS;
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@ -50,44 +57,50 @@
|
||||
public emptyImage: string = EMPTY_STATE_IMAGES.API_KEY;
|
||||
|
||||
public mounted(): void {
|
||||
this.$store.dispatch(BUCKET_USAGE_ACTIONS.FETCH, 1);
|
||||
this.$store.dispatch(FETCH, 1);
|
||||
}
|
||||
|
||||
public doNothing(): void {
|
||||
// this method is used to mock prop function of common List
|
||||
}
|
||||
|
||||
public get totalPageCount(): number {
|
||||
return this.$store.state.bucketUsageModule.page.pageCount;
|
||||
return this.$store.getters.page.pageCount;
|
||||
}
|
||||
|
||||
public get totalCountOfBuckets(): number {
|
||||
return this.$store.state.bucketUsageModule.totalCount;
|
||||
public get totalCount(): number {
|
||||
return this.$store.getters.page.totalCount;
|
||||
}
|
||||
|
||||
public get itemComponent() {
|
||||
return BucketItem;
|
||||
}
|
||||
|
||||
public get buckets(): BucketUsage[] {
|
||||
return this.$store.state.bucketUsageModule.page.bucketUsages;
|
||||
public get buckets(): Bucket[] {
|
||||
return this.$store.getters.page.buckets;
|
||||
}
|
||||
|
||||
public get search(): string {
|
||||
return this.$store.state.bucketUsageModule.cursor.search;
|
||||
return this.$store.getters.cursor.search;
|
||||
}
|
||||
|
||||
public async fetch(searchQuery: string): Promise<void> {
|
||||
await this.$store.dispatch(BUCKET_USAGE_ACTIONS.SET_SEARCH, searchQuery);
|
||||
const bucketsResponse = await this.$store.dispatch(BUCKET_USAGE_ACTIONS.FETCH, 1);
|
||||
await this.$store.dispatch(SET_SEARCH, searchQuery);
|
||||
|
||||
if (!bucketsResponse.isSuccess) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + bucketsResponse.errorMessage);
|
||||
try {
|
||||
await this.$store.dispatch(FETCH, 1);
|
||||
} catch (error) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch buckets: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async onPageClick(page: number): Promise<void> {
|
||||
const response = await this.$store.dispatch(BUCKET_USAGE_ACTIONS.FETCH, page);
|
||||
if (!response.isSuccess) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + response.errorMessage);
|
||||
try {
|
||||
await this.$store.dispatch(FETCH, page);
|
||||
} catch (error) {
|
||||
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch buckets: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -12,12 +12,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from 'vue-property-decorator';
|
||||
import { Bucket } from '@/types/buckets';
|
||||
|
||||
// TODO: should it be functional?
|
||||
@Component
|
||||
export default class BucketItem extends Vue {
|
||||
@Prop()
|
||||
private readonly itemData: BucketUsage;
|
||||
private readonly itemData: Bucket;
|
||||
|
||||
public get storage(): string {
|
||||
return this.itemData.storage.toFixed(4);
|
||||
|
@ -22,11 +22,7 @@
|
||||
export default class List extends Vue {
|
||||
@Prop({default: ''})
|
||||
private readonly itemComponent: string;
|
||||
@Prop({
|
||||
default: () => {
|
||||
console.error('onItemClick is not reinitialized');
|
||||
}
|
||||
})
|
||||
@Prop({default: () => new Promise(() => false)})
|
||||
private readonly onItemClick: listItemClickCallback;
|
||||
@Prop({default: Array()})
|
||||
private readonly dataSet: any[];
|
||||
|
@ -36,9 +36,9 @@
|
||||
PM_ACTIONS,
|
||||
API_KEYS_ACTIONS,
|
||||
NOTIFICATION_ACTIONS,
|
||||
BUCKET_USAGE_ACTIONS,
|
||||
} from '@/utils/constants/actionNames';
|
||||
import { USER_ACTIONS } from '@/store/modules/users';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
|
||||
@Component
|
||||
export default class ProjectSelectionDropdown extends Vue {
|
||||
@ -60,7 +60,7 @@
|
||||
this.$store.dispatch(USER_ACTIONS.CLEAR);
|
||||
this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.CLEAR);
|
||||
this.$store.dispatch(BUCKET_USAGE_ACTIONS.CLEAR);
|
||||
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -22,15 +22,15 @@
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import {
|
||||
API_KEYS_ACTIONS,
|
||||
APP_STATE_ACTIONS,
|
||||
BUCKET_USAGE_ACTIONS,
|
||||
NOTIFICATION_ACTIONS,
|
||||
PM_ACTIONS,
|
||||
PROJECT_PAYMENT_METHODS_ACTIONS,
|
||||
PROJECT_USAGE_ACTIONS,
|
||||
PROJETS_ACTIONS
|
||||
APP_STATE_ACTIONS,
|
||||
PROJETS_ACTIONS,
|
||||
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';
|
||||
|
||||
@Component
|
||||
@ -43,9 +43,7 @@
|
||||
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
|
||||
|
||||
// TODO: add types
|
||||
const keysResponse = await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
|
||||
const usageResponse = await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
|
||||
const bucketsResponse = await this.$store.dispatch(BUCKET_USAGE_ACTIONS.FETCH, 1);
|
||||
const paymentMethodsResponse = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
|
||||
|
||||
try {
|
||||
@ -54,7 +52,9 @@
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${err.message}`);
|
||||
}
|
||||
|
||||
if (!keysResponse.isSuccess) {
|
||||
try {
|
||||
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
|
||||
} catch {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch api keys');
|
||||
}
|
||||
|
||||
@ -62,8 +62,10 @@
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project usage');
|
||||
}
|
||||
|
||||
if (!bucketsResponse.isSuccess) {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + bucketsResponse.errorMessage);
|
||||
try {
|
||||
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, 1);
|
||||
} catch (error) {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + error.message);
|
||||
}
|
||||
|
||||
if (!paymentMethodsResponse.isSuccess) {
|
||||
|
@ -51,13 +51,13 @@
|
||||
NOTIFICATION_ACTIONS,
|
||||
PROJECT_USAGE_ACTIONS,
|
||||
PROJETS_ACTIONS,
|
||||
BUCKET_USAGE_ACTIONS,
|
||||
PM_ACTIONS
|
||||
PM_ACTIONS,
|
||||
} from '@/utils/constants/actionNames';
|
||||
import Button from '@/components/common/Button.vue';
|
||||
import Checkbox from '@/components/common/Checkbox.vue';
|
||||
import { CreateProjectModel, Project } from '@/types/projects';
|
||||
import HeaderedInput from '@/components/common/HeaderedInput.vue';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { CreateProjectModel, Project } from '@/types/projects';
|
||||
import { RequestResponse } from '@/types/response';
|
||||
|
||||
@Component({
|
||||
@ -185,8 +185,8 @@
|
||||
}
|
||||
|
||||
private clearBucketUsage(): void {
|
||||
this.$store.dispatch(BUCKET_USAGE_ACTIONS.SET_SEARCH, '');
|
||||
this.$store.dispatch(BUCKET_USAGE_ACTIONS.CLEAR);
|
||||
this.$store.dispatch(BUCKET_ACTIONS.SET_SEARCH, '');
|
||||
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
|
||||
}
|
||||
|
||||
private notifyError(message: string): void {
|
||||
|
@ -6,16 +6,18 @@ import Vuex from 'vuex';
|
||||
|
||||
import { makeNotificationsModule } from '@/store/modules/notifications';
|
||||
import { ApiKeysApiGql } from '@/api/apiKeys';
|
||||
import { BucketsApiGql } from '@/api/buckets';
|
||||
import { CreditsApiGql } from '@/api/credits';
|
||||
import { ProjectMembersApiGql } from '@/api/projectMembers';
|
||||
import { UsersApiGql } from '@/api/users';
|
||||
import { appStateModule } from '@/store/modules/appState';
|
||||
import { makeApiKeysModule } from '@/store/modules/apiKeys';
|
||||
import { makeCreditsModule } from '@/store/modules/credits';
|
||||
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 { bucketUsageModule, usageModule } from '@/store/modules/usage';
|
||||
import { usageModule } from '@/store/modules/usage';
|
||||
import { makeUsersModule } from '@/store/modules/users';
|
||||
|
||||
Vue.use(Vuex);
|
||||
@ -31,6 +33,7 @@ export class StoreModule<S> {
|
||||
const usersApi = new UsersApiGql();
|
||||
const apiKeysApi = new ApiKeysApiGql();
|
||||
const creditsApi = new CreditsApiGql();
|
||||
const bucketsApi = new BucketsApiGql();
|
||||
const projectMembersApi = new ProjectMembersApiGql();
|
||||
|
||||
// Satellite store (vuex)
|
||||
@ -39,13 +42,13 @@ const store = new Vuex.Store({
|
||||
notificationsModule: makeNotificationsModule(),
|
||||
apiKeysModule: makeApiKeysModule(apiKeysApi),
|
||||
appStateModule,
|
||||
bucketUsageModule,
|
||||
creditsModule: makeCreditsModule(creditsApi),
|
||||
projectMembersModule: makeProjectMembersModule(projectMembersApi),
|
||||
projectPaymentsMethodsModule,
|
||||
usersModule: makeUsersModule(usersApi),
|
||||
projectsModule: makeProjectsModule(),
|
||||
usageModule,
|
||||
usersModule: makeUsersModule(usersApi),
|
||||
bucketUsageModule: makeBucketsModule(bucketsApi),
|
||||
}
|
||||
});
|
||||
|
||||
|
87
web/satellite/src/store/modules/buckets.ts
Normal file
87
web/satellite/src/store/modules/buckets.ts
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { Bucket, BucketCursor, BucketPage, BucketsApi } from '@/types/buckets';
|
||||
import { StoreModule } from '@/store';
|
||||
|
||||
export const BUCKET_ACTIONS = {
|
||||
FETCH: 'setBuckets',
|
||||
SET_SEARCH: 'setBucketSearch',
|
||||
CLEAR: 'clearBuckets'
|
||||
};
|
||||
|
||||
export const BUCKET_MUTATIONS = {
|
||||
SET: 'setBuckets',
|
||||
SET_SEARCH: 'setBucketSearch',
|
||||
SET_PAGE: 'setBucketPage',
|
||||
CLEAR: 'clearBuckets',
|
||||
};
|
||||
|
||||
const {
|
||||
FETCH
|
||||
} = BUCKET_ACTIONS;
|
||||
const {
|
||||
SET,
|
||||
SET_PAGE,
|
||||
SET_SEARCH,
|
||||
CLEAR,
|
||||
} = BUCKET_MUTATIONS;
|
||||
const bucketPageLimit = 8;
|
||||
const firstPage = 1;
|
||||
|
||||
class BucketsState {
|
||||
public cursor: BucketCursor = { limit: bucketPageLimit, search: '', page: firstPage };
|
||||
public page: BucketPage = { buckets: new Array<Bucket>(), currentPage: 1, pageCount: 1, offset: 0, limit: bucketPageLimit, search: '', totalCount: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* creates buckets module with all dependencies
|
||||
*
|
||||
* @param api - buckets api
|
||||
*/
|
||||
export function makeBucketsModule(api: BucketsApi): StoreModule<BucketsState> {
|
||||
return {
|
||||
state: new BucketsState(),
|
||||
|
||||
mutations: {
|
||||
[SET](state: BucketsState, page: BucketPage) {
|
||||
state.page = page;
|
||||
},
|
||||
[SET_PAGE](state: BucketsState, page: number) {
|
||||
state.cursor.page = page;
|
||||
},
|
||||
[SET_SEARCH](state: BucketsState, search: string) {
|
||||
state.cursor.search = search;
|
||||
},
|
||||
[CLEAR](state: BucketsState) {
|
||||
state.cursor = new BucketCursor('', bucketPageLimit, firstPage);
|
||||
state.page = new BucketPage([], '', bucketPageLimit, 0, 1, 1, 0);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
[FETCH]: async function({commit, rootGetters, state}: any, page: number): Promise<BucketPage> {
|
||||
const projectID = rootGetters.selectedProject.id;
|
||||
const before = new Date();
|
||||
state.cursor.page = page;
|
||||
|
||||
commit(SET_PAGE, page);
|
||||
|
||||
let result = await api.get(projectID, before, state.cursor);
|
||||
|
||||
commit(SET, result);
|
||||
|
||||
return result;
|
||||
},
|
||||
[BUCKET_ACTIONS.SET_SEARCH]: function({commit}, search: string) {
|
||||
commit(SET_SEARCH, search);
|
||||
},
|
||||
[BUCKET_ACTIONS.CLEAR]: function({commit}) {
|
||||
commit(CLEAR);
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
page: (state: BucketsState): BucketPage => state.page,
|
||||
cursor: (state: BucketsState): BucketCursor => state.cursor,
|
||||
}
|
||||
};
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { BUCKET_USAGE_MUTATIONS, PROJECT_USAGE_MUTATIONS } from '@/store/mutationConstants';
|
||||
import { BUCKET_USAGE_ACTIONS, PROJECT_USAGE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { fetchBucketUsages, fetchProjectUsage } from '@/api/usage';
|
||||
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';
|
||||
|
||||
export const usageModule = {
|
||||
@ -76,56 +76,3 @@ export const usageModule = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const bucketPageLimit = 8;
|
||||
const firstPage = 1;
|
||||
|
||||
export const bucketUsageModule = {
|
||||
state: {
|
||||
cursor: { limit: bucketPageLimit, search: '', page: firstPage } as BucketUsageCursor,
|
||||
page: { bucketUsages: [] as BucketUsage[] } as BucketUsagePage,
|
||||
totalCount: 0,
|
||||
},
|
||||
mutations: {
|
||||
[BUCKET_USAGE_MUTATIONS.FETCH](state: any, page: BucketUsagePage) {
|
||||
state.page = page;
|
||||
|
||||
if (page.totalCount > 0) {
|
||||
state.totalCount = page.totalCount;
|
||||
}
|
||||
},
|
||||
[BUCKET_USAGE_MUTATIONS.SET_PAGE](state: any, page: number) {
|
||||
state.cursor.page = page;
|
||||
},
|
||||
[BUCKET_USAGE_MUTATIONS.SET_SEARCH](state: any, search: string) {
|
||||
state.cursor.search = search;
|
||||
},
|
||||
[BUCKET_USAGE_MUTATIONS.CLEAR](state: any) {
|
||||
state.cursor = { limit: bucketPageLimit, search: '', page: firstPage } as BucketUsageCursor;
|
||||
state.page = { bucketUsages: [] as BucketUsage[] } as BucketUsagePage;
|
||||
state.totalCount = 0;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
[BUCKET_USAGE_ACTIONS.FETCH]: async function({commit, rootGetters, state}: any, page: number): Promise<RequestResponse<BucketUsagePage>> {
|
||||
const projectID = rootGetters.selectedProject.id;
|
||||
const before = new Date();
|
||||
state.cursor.page = page;
|
||||
|
||||
commit(BUCKET_USAGE_MUTATIONS.SET_PAGE, page);
|
||||
|
||||
let result = await fetchBucketUsages(projectID, before, state.cursor);
|
||||
if (result.isSuccess) {
|
||||
commit(BUCKET_USAGE_MUTATIONS.FETCH, result.data);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
[BUCKET_USAGE_ACTIONS.SET_SEARCH]: function({commit}, search: string) {
|
||||
commit(BUCKET_USAGE_MUTATIONS.SET_SEARCH, search);
|
||||
},
|
||||
[BUCKET_USAGE_ACTIONS.CLEAR]: function({commit}) {
|
||||
commit(BUCKET_USAGE_MUTATIONS.CLEAR);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -25,13 +25,6 @@ export const PROJECT_USAGE_MUTATIONS = {
|
||||
CLEAR: 'CLEAR_PROJECT_USAGE'
|
||||
};
|
||||
|
||||
export const BUCKET_USAGE_MUTATIONS = {
|
||||
FETCH: 'FETCH_BUCKET_USAGES',
|
||||
SET_SEARCH: 'SET_SEARCH_BUCKET_USAGE',
|
||||
SET_PAGE: 'SET_PAGE_BUCKET_USAGE',
|
||||
CLEAR: 'CLEAR_BUCKET_USAGES'
|
||||
};
|
||||
|
||||
export const NOTIFICATION_MUTATIONS = {
|
||||
ADD: 'ADD_NOTIFICATION',
|
||||
DELETE: 'DELETE_NOTIFICATION',
|
||||
|
74
web/satellite/src/types/buckets.ts
Normal file
74
web/satellite/src/types/buckets.ts
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
/**
|
||||
* Exposes all bucket-related functionality
|
||||
*/
|
||||
export interface BucketsApi {
|
||||
/**
|
||||
* Fetch buckets
|
||||
*
|
||||
* @returns BucketPage
|
||||
* @throws Error
|
||||
*/
|
||||
get(projectId: string, before: Date, cursor: BucketCursor): Promise<BucketPage>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bucket class holds info for Bucket entity.
|
||||
*/
|
||||
export class Bucket {
|
||||
public bucketName: string;
|
||||
public storage: number;
|
||||
public egress: number;
|
||||
public objectCount: number;
|
||||
public since: Date;
|
||||
public before: Date;
|
||||
|
||||
constructor(bucketName: string = '', storage: number = 0, egress: number = 0, objectCount: number = 0, since: Date = new Date(), before: Date = new Date()) {
|
||||
this.bucketName = bucketName;
|
||||
this.storage = storage;
|
||||
this.egress = egress;
|
||||
this.objectCount = objectCount;
|
||||
this.since = since;
|
||||
this.before = before;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BucketPage class holds bucket total usages and flag whether more usages available.
|
||||
*/
|
||||
export class BucketPage {
|
||||
buckets: Bucket[];
|
||||
search: string;
|
||||
limit: number;
|
||||
offset: number;
|
||||
pageCount: number;
|
||||
currentPage: number;
|
||||
totalCount: number;
|
||||
|
||||
constructor(buckets: Bucket[] = [], search: string = '', limit: number = 0, offset: number = 0, pageCount: number = 0, currentPage: number = 0, totalCount: number = 0) {
|
||||
this.buckets = buckets;
|
||||
this.search = search;
|
||||
this.limit = limit;
|
||||
this.offset = offset;
|
||||
this.pageCount = pageCount;
|
||||
this.currentPage = currentPage;
|
||||
this.totalCount = totalCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BucketCursor class holds cursor for bucket name and limit.
|
||||
*/
|
||||
export class BucketCursor {
|
||||
search: string;
|
||||
limit: number;
|
||||
page: number;
|
||||
|
||||
constructor(search: string = '', limit: number = 0, page: number = 0) {
|
||||
this.search = search;
|
||||
this.limit = limit;
|
||||
this.page = page;
|
||||
}
|
||||
}
|
29
web/satellite/src/types/usage.d.ts
vendored
29
web/satellite/src/types/usage.d.ts
vendored
@ -9,32 +9,3 @@ declare type ProjectUsage = {
|
||||
since: Date,
|
||||
before: Date,
|
||||
};
|
||||
|
||||
// BucketUSage total usage of a bucket for given period
|
||||
declare type BucketUsage = {
|
||||
bucketName: string,
|
||||
storage: number,
|
||||
egress: number,
|
||||
objectCount: number,
|
||||
since: Date,
|
||||
before: Date,
|
||||
};
|
||||
|
||||
// BucketUsagePage holds bucket total usages and flag
|
||||
// wether more usages available
|
||||
declare type BucketUsagePage = {
|
||||
bucketUsages: BucketUsage[],
|
||||
search: string,
|
||||
limit: number,
|
||||
offset: number,
|
||||
pageCount: number,
|
||||
currentPage: number,
|
||||
totalCount: number,
|
||||
};
|
||||
|
||||
// BucketUsageCursor holds cursor for bucket name and limit
|
||||
declare type BucketUsageCursor = {
|
||||
search: string,
|
||||
limit: number,
|
||||
page: number,
|
||||
};
|
||||
|
@ -70,12 +70,6 @@ export const PROJECT_USAGE_ACTIONS = {
|
||||
CLEAR: 'clearProjectUsage',
|
||||
};
|
||||
|
||||
export const BUCKET_USAGE_ACTIONS = {
|
||||
FETCH: 'fetchBucketUsages',
|
||||
SET_SEARCH: 'setSearchBucketUsage',
|
||||
CLEAR: 'clearBucketUsages'
|
||||
};
|
||||
|
||||
export const PROJECT_PAYMENT_METHODS_ACTIONS = {
|
||||
ADD: 'addProjectPaymentMethod',
|
||||
FETCH: 'fetchProjectPaymentMethods',
|
||||
|
@ -21,7 +21,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { AppState } from '@/utils/constants/appStateEnum';
|
||||
import DashboardHeader from '@/components/header/Header.vue';
|
||||
import NavigationArea from '@/components/navigation/NavigationArea.vue';
|
||||
import ProjectCreationSuccessPopup from '@/components/project/ProjectCreationSuccessPopup.vue';
|
||||
import {
|
||||
API_KEYS_ACTIONS,
|
||||
APP_STATE_ACTIONS,
|
||||
@ -29,16 +31,15 @@
|
||||
PM_ACTIONS,
|
||||
PROJETS_ACTIONS,
|
||||
PROJECT_USAGE_ACTIONS,
|
||||
BUCKET_USAGE_ACTIONS, PROJECT_PAYMENT_METHODS_ACTIONS
|
||||
PROJECT_PAYMENT_METHODS_ACTIONS,
|
||||
} from '@/utils/constants/actionNames';
|
||||
import { USER_ACTIONS } from '@/store/modules/users';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { AppState } from '@/utils/constants/appStateEnum';
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
import DashboardHeader from '@/components/header/Header.vue';
|
||||
import NavigationArea from '@/components/navigation/NavigationArea.vue';
|
||||
import { Project } from '@/types/projects';
|
||||
import ProjectCreationSuccessPopup from '@/components/project/ProjectCreationSuccessPopup.vue';
|
||||
import { RequestResponse } from '@/types/response';
|
||||
import ROUTES from '@/utils/constants/routerConstants';
|
||||
import { Project } from '@/types/projects';
|
||||
import { RequestResponse } from '@/types/response';
|
||||
import { User } from '@/types/users';
|
||||
|
||||
@Component({
|
||||
@ -84,9 +85,10 @@
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project usage');
|
||||
}
|
||||
|
||||
const bucketsResponse = await this.$store.dispatch(BUCKET_USAGE_ACTIONS.FETCH, 1);
|
||||
if (!bucketsResponse.isSuccess) {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + bucketsResponse.errorMessage);
|
||||
try {
|
||||
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, 1);
|
||||
} catch (error) {
|
||||
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + error.message);
|
||||
}
|
||||
|
||||
const paymentMethodsResponse = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
|
||||
|
92
web/satellite/tests/unit/store/buckets.spec.ts
Normal file
92
web/satellite/tests/unit/store/buckets.spec.ts
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import Vuex from 'vuex';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
import { BucketsApiGql } from '@/api/buckets';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { makeBucketsModule } from '@/store/modules/buckets';
|
||||
import { makeProjectsModule } from '@/store/modules/projects';
|
||||
import { Bucket, BucketCursor, BucketPage } from '@/types/buckets';
|
||||
import { Project } from '@/types/projects';
|
||||
|
||||
const Vue = createLocalVue();
|
||||
const bucketsApi = new BucketsApiGql();
|
||||
const bucketsModule = makeBucketsModule(bucketsApi);
|
||||
const { FETCH, SET_SEARCH, CLEAR } = BUCKET_ACTIONS;
|
||||
|
||||
const projectsModule = makeProjectsModule();
|
||||
const selectedProject = new Project();
|
||||
selectedProject.id = '1';
|
||||
projectsModule.state.selectedProject = selectedProject;
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({modules: { projectsModule, bucketsModule } });
|
||||
const state = (store.state as any).bucketsModule;
|
||||
const bucket = new Bucket('test', 10, 10, 1, new Date(), new Date());
|
||||
const page: BucketPage = { buckets: [bucket], currentPage: 1, pageCount: 1, offset: 0, limit: 8, search: 'test', totalCount: 1 };
|
||||
|
||||
describe('actions', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('success fetch buckets', async () => {
|
||||
jest.spyOn(bucketsApi, 'get').mockReturnValue(
|
||||
Promise.resolve(page)
|
||||
);
|
||||
|
||||
await store.dispatch(FETCH, 1);
|
||||
|
||||
expect(state.page).toEqual(page);
|
||||
expect(state.cursor.page).toEqual(1);
|
||||
});
|
||||
|
||||
it('fetch throws an error when api call fails', async () => {
|
||||
jest.spyOn(bucketsApi, 'get').mockImplementation(() => { throw new Error(); });
|
||||
|
||||
try {
|
||||
await store.dispatch(FETCH , 1);
|
||||
} catch (error) {
|
||||
expect(state.page).toEqual(page);
|
||||
}
|
||||
});
|
||||
|
||||
it('success set search buckets', () => {
|
||||
store.dispatch(SET_SEARCH, 'test');
|
||||
|
||||
expect(state.cursor.search).toMatch('test');
|
||||
});
|
||||
|
||||
it('success clear', () => {
|
||||
store.dispatch(CLEAR);
|
||||
|
||||
expect(state.cursor).toEqual(new BucketCursor('', 8, 1));
|
||||
expect(state.page).toEqual(new BucketPage([], '', 8, 0, 1, 1, 0));
|
||||
});
|
||||
});
|
||||
|
||||
describe('getters', () => {
|
||||
const page: BucketPage = { buckets: [bucket], currentPage: 1, pageCount: 1, offset: 0, limit: 8, search: 'test', totalCount: 1 };
|
||||
|
||||
it('page of buckets', async () => {
|
||||
jest.spyOn(bucketsApi, 'get').mockReturnValue(
|
||||
Promise.resolve(page)
|
||||
);
|
||||
|
||||
await store.dispatch(FETCH, 1);
|
||||
|
||||
const storePage = store.getters.page;
|
||||
|
||||
expect(storePage).toEqual(page);
|
||||
});
|
||||
|
||||
it('cursor of buckets', () => {
|
||||
store.dispatch(CLEAR);
|
||||
|
||||
const cursor = store.getters.cursor;
|
||||
|
||||
expect(cursor).toEqual(new BucketCursor('', 8, 1));
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user