web/satellite: buckets api and store refactored (#2830)

This commit is contained in:
Vitalii Shpital 2019-08-21 17:07:49 +03:00 committed by GitHub
parent 1e099839dd
commit 87ef5e3398
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 390 additions and 208 deletions

View 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);
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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);

View File

@ -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[];

View File

@ -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>

View File

@ -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) {

View File

@ -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 {

View File

@ -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),
}
});

View 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,
}
};
}

View File

@ -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);
}
}
};

View File

@ -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',

View 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;
}
}

View File

@ -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,
};

View File

@ -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',

View File

@ -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);

View 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));
});
});