satellite/{console,web}: update error handling

This change updates how API errors are handled and sent.

Change-Id: Ia4c71eeb6f2d009a47b59ce77a23f70b8b10f6dc
This commit is contained in:
Wilfred Asomani 2023-07-19 20:19:34 +00:00 committed by Storj Robot
parent 2c56599ca0
commit c934974652
72 changed files with 462 additions and 166 deletions

View File

@ -6,7 +6,6 @@ package web
import (
"context"
"encoding/json"
"fmt"
"net/http"
"go.uber.org/zap"
@ -29,7 +28,6 @@ func ServeCustomJSONError(ctx context.Context, log *zap.Logger, w http.ResponseW
if requestID := requestid.FromContext(ctx); requestID != "" {
fields = append(fields, zap.String("requestID", requestID))
msg += fmt.Sprintf(" (request id: %s)", requestID)
}
switch status {

View File

@ -926,13 +926,14 @@ func (server *Server) graphqlHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(code)
var jsonError struct {
Error string `json:"error"`
Error string `json:"error"`
RequestID string `json:"requestID"`
}
jsonError.Error = err.Error()
if requestID := requestid.FromContext(ctx); requestID != "" {
jsonError.Error += fmt.Sprintf(" (request id: %s)", requestID)
jsonError.RequestID = requestID
}
if err := json.NewEncoder(w).Encode(jsonError); err != nil {
@ -992,7 +993,8 @@ func (server *Server) graphqlHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(code)
var jsonError struct {
Errors []string `json:"errors"`
Errors []string `json:"errors"`
RequestID string `json:"requestID"`
}
for _, err := range errors {
@ -1000,7 +1002,7 @@ func (server *Server) graphqlHandler(w http.ResponseWriter, r *http.Request) {
}
if requestID := requestid.FromContext(ctx); requestID != "" {
jsonError.Errors = append(jsonError.Errors, fmt.Sprintf("request id: %s", requestID))
jsonError.RequestID = requestID
}
if err := json.NewEncoder(w).Encode(jsonError); err != nil {

View File

@ -72,7 +72,7 @@ onMounted(async (): Promise<void> => {
await configStore.getConfig();
} catch (error) {
appStore.setErrorPage(500, true);
notify.error(error.message, null);
notify.notifyError(error, null);
}
fixViewportHeight();

View File

@ -10,6 +10,7 @@ import {
EdgeCredentials,
} from '@/types/accessGrants';
import { HttpClient } from '@/utils/httpClient';
import { APIError } from '@/utils/error';
/**
* AccessGrantsApiGql is a graphql implementation of Access Grants API.
@ -139,7 +140,11 @@ export class AccessGrantsApiGql extends BaseGql implements AccessGrantsApi {
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('Can not get access grant names');
throw new APIError({
status: response.status,
message: 'Can not get access grant names',
requestID: response.headers.get('x-request-id'),
});
}
const result = await response.json();
@ -162,7 +167,11 @@ export class AccessGrantsApiGql extends BaseGql implements AccessGrantsApi {
return;
}
throw new Error('can not delete access grant');
throw new APIError({
status: response.status,
message: 'Can not delete access grant',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -181,7 +190,11 @@ export class AccessGrantsApiGql extends BaseGql implements AccessGrantsApi {
};
const response = await this.client.post(path, JSON.stringify(body));
if (!response.ok) {
throw new Error('Cannot get gateway credentials');
throw new APIError({
status: response.status,
message: 'Can not get gateway credentials',
requestID: response.headers.get('x-request-id'),
});
}
const result = await response.json();

View File

@ -15,6 +15,7 @@ import {
} from '@/types/users';
import { HttpClient } from '@/utils/httpClient';
import { ErrorTokenExpired } from '@/api/errors/ErrorTokenExpired';
import { APIError } from '@/utils/error';
/**
* AuthHttpApi is a console Auth API.
@ -43,7 +44,11 @@ export class AuthHttpApi implements UsersApi {
case 429:
throw new ErrorTooManyRequests(errMsg);
default:
throw new Error(errMsg);
throw new APIError({
status: response.status,
message: errMsg,
requestID: response.headers.get('x-request-id'),
});
}
}
@ -85,7 +90,11 @@ export class AuthHttpApi implements UsersApi {
case 429:
throw new ErrorTooManyRequests(errMsg);
default:
throw new Error(errMsg);
throw new APIError({
status: response.status,
message: errMsg,
requestID: response.headers.get('x-request-id'),
});
}
}
@ -102,7 +111,11 @@ export class AuthHttpApi implements UsersApi {
return;
}
throw new Error('Can not logout. Please try again later');
throw new APIError({
status: response.status,
message: 'Can not logout. Please try again later',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -129,7 +142,11 @@ export class AuthHttpApi implements UsersApi {
case 429:
throw new ErrorTooManyRequests(errMsg);
default:
throw new Error(errMsg);
throw new APIError({
status: response.status,
message: errMsg,
requestID: response.headers.get('x-request-id'),
});
}
}
@ -150,7 +167,11 @@ export class AuthHttpApi implements UsersApi {
return;
}
throw new Error('can not update user data');
throw new APIError({
status: response.status,
message: 'Can not update user data',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -187,7 +208,11 @@ export class AuthHttpApi implements UsersApi {
);
}
throw new Error('can not get user data');
throw new APIError({
status: response.status,
message: 'Can not get user data',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -209,7 +234,11 @@ export class AuthHttpApi implements UsersApi {
}
const result = await response.json();
throw new Error(result.error);
throw new APIError({
status: response.status,
message: result.error,
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -228,7 +257,11 @@ export class AuthHttpApi implements UsersApi {
return;
}
throw new Error('can not delete user');
throw new APIError({
status: response.status,
message: 'Can not delete user',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -248,7 +281,11 @@ export class AuthHttpApi implements UsersApi {
);
}
throw new Error('can not get user frozen status');
throw new APIError({
status: response.status,
message: 'Can not get user frozen status',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -271,7 +308,11 @@ export class AuthHttpApi implements UsersApi {
);
}
throw new Error('can not get user settings');
throw new APIError({
status: response.status,
message: 'Can not get user settings',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -296,7 +337,11 @@ export class AuthHttpApi implements UsersApi {
);
}
throw new Error('can not get user settings');
throw new APIError({
status: response.status,
message: 'Can not update user settings',
requestID: response.headers.get('x-request-id'),
});
}
// TODO: remove secret after Vanguard release
@ -338,7 +383,11 @@ export class AuthHttpApi implements UsersApi {
case 429:
throw new ErrorTooManyRequests(errMsg);
default:
throw new Error(errMsg);
throw new APIError({
status: response.status,
message: errMsg,
requestID: response.headers.get('x-request-id'),
});
}
}
}
@ -356,7 +405,11 @@ export class AuthHttpApi implements UsersApi {
return await response.json();
}
throw new Error('Can not generate MFA secret. Please try again later');
throw new APIError({
status: response.status,
message: 'Can not generate MFA secret. Please try again later',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -376,7 +429,11 @@ export class AuthHttpApi implements UsersApi {
return;
}
throw new Error('Can not enable MFA. Please try again later');
throw new APIError({
status: response.status,
message: 'Can not enable MFA. Please try again later',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -399,7 +456,11 @@ export class AuthHttpApi implements UsersApi {
const result = await response.json();
const errMsg = result.error || 'Cannot disable MFA. Please try again later';
throw new Error(errMsg);
throw new APIError({
status: response.status,
message: errMsg,
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -415,7 +476,11 @@ export class AuthHttpApi implements UsersApi {
return await response.json();
}
throw new Error('Can not generate MFA recovery codes. Please try again later');
throw new APIError({
status: response.status,
message: 'Can not generate MFA recovery codes. Please try again later',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -462,7 +527,11 @@ export class AuthHttpApi implements UsersApi {
case 400:
throw new ErrorBadRequest(errMsg);
default:
throw new Error(errMsg);
throw new APIError({
status: response.status,
message: errMsg,
requestID: response.headers.get('x-request-id'),
});
}
}
@ -480,6 +549,10 @@ export class AuthHttpApi implements UsersApi {
return new Date(await response.json());
}
throw new Error('Unable to refresh session.');
throw new APIError({
status: response.status,
message: 'Unable to refresh session.',
requestID: response.headers.get('x-request-id'),
});
}
}

View File

@ -4,6 +4,7 @@
import { BaseGql } from '@/api/baseGql';
import { Bucket, BucketCursor, BucketPage, BucketsApi } from '@/types/buckets';
import { HttpClient } from '@/utils/httpClient';
import { APIError } from '@/utils/error';
/**
* BucketsApiGql is a graphql implementation of Buckets API.
@ -69,7 +70,11 @@ export class BucketsApiGql extends BaseGql implements BucketsApi {
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('Can not get bucket names');
throw new APIError({
status: response.status,
message: 'Can not get bucket names',
requestID: response.headers.get('x-request-id'),
});
}
const result = await response.json();

View File

@ -3,6 +3,7 @@
import { HttpClient } from '@/utils/httpClient';
import { FrontendConfig, FrontendConfigApi } from '@/types/config';
import { APIError } from '@/utils/error';
/**
* FrontendConfigHttpApi is an HTTP implementation of the frontend config API.
@ -19,7 +20,11 @@ export class FrontendConfigHttpApi implements FrontendConfigApi {
public async get(): Promise<FrontendConfig> {
const response = await this.http.get(this.ROOT_PATH);
if (!response.ok) {
throw new Error('Cannot get frontend config');
throw new APIError({
status: response.status,
message: 'Cannot get frontend config',
requestID: response.headers.get('x-request-id'),
});
}
return await response.json() as FrontendConfig;
}

View File

@ -18,6 +18,7 @@ import {
} from '@/types/payments';
import { HttpClient } from '@/utils/httpClient';
import { Time } from '@/utils/time';
import { APIError } from '@/utils/error';
/**
* PaymentsHttpApi is a http implementation of Payments API.
@ -38,7 +39,11 @@ export class PaymentsHttpApi implements PaymentsApi {
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('Can not get account balance');
throw new APIError({
status: response.status,
message: 'Can not get account balance',
requestID: response.headers.get('x-request-id'),
});
}
const balance = await response.json();
@ -63,7 +68,11 @@ export class PaymentsHttpApi implements PaymentsApi {
return couponType;
}
throw new Error('can not setup account');
throw new APIError({
status: response.status,
message: 'Can not setup account',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -76,7 +85,11 @@ export class PaymentsHttpApi implements PaymentsApi {
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('can not get projects charges');
throw new APIError({
status: response.status,
message: 'Can not get projects charges',
requestID: response.headers.get('x-request-id'),
});
}
return ProjectCharges.fromJSON(await response.json());
@ -90,7 +103,11 @@ export class PaymentsHttpApi implements PaymentsApi {
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('cannot get project usage price model');
throw new APIError({
status: response.status,
message: 'Can not get project usage price model',
requestID: response.headers.get('x-request-id'),
});
}
const model = await response.json();
@ -115,7 +132,11 @@ export class PaymentsHttpApi implements PaymentsApi {
return;
}
throw new Error('can not add credit card');
throw new APIError({
status: response.status,
message: 'Can not add credit card',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -132,7 +153,11 @@ export class PaymentsHttpApi implements PaymentsApi {
return;
}
throw new Error('can not remove credit card');
throw new APIError({
status: response.status,
message: 'Can not remove credit card',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -146,7 +171,11 @@ export class PaymentsHttpApi implements PaymentsApi {
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('can not list credit cards');
throw new APIError({
status: response.status,
message: 'can not list credit cards',
requestID: response.headers.get('x-request-id'),
});
}
const creditCards = await response.json();
@ -172,7 +201,11 @@ export class PaymentsHttpApi implements PaymentsApi {
return;
}
throw new Error('can not make credit card default');
throw new APIError({
status: response.status,
message: 'Can not make credit card default',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -186,7 +219,11 @@ export class PaymentsHttpApi implements PaymentsApi {
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('can not list billing history');
throw new APIError({
status: response.status,
message: 'Can not list billing history',
requestID: response.headers.get('x-request-id'),
});
}
const paymentsHistoryItems = await response.json();
@ -221,7 +258,11 @@ export class PaymentsHttpApi implements PaymentsApi {
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('Can not list token payment history');
throw new APIError({
status: response.status,
message: 'Can not list token payment history',
requestID: response.headers.get('x-request-id'),
});
}
const json = await response.json();
@ -269,7 +310,11 @@ export class PaymentsHttpApi implements PaymentsApi {
const coupon = await response.json();
if (!coupon) {
throw new Error(errMsg);
throw new APIError({
status: response.status,
message: errMsg,
requestID: response.headers.get('x-request-id'),
});
}
return new Coupon(
@ -294,7 +339,11 @@ export class PaymentsHttpApi implements PaymentsApi {
const path = `${this.ROOT_PATH}/coupon`;
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('cannot retrieve coupon');
throw new APIError({
status: response.status,
message: 'Can not retrieve coupon',
requestID: response.headers.get('x-request-id'),
});
}
const coupon = await response.json();
@ -331,7 +380,11 @@ export class PaymentsHttpApi implements PaymentsApi {
case 404:
return new Wallet();
default:
throw new Error('Can not get wallet');
throw new APIError({
status: response.status,
message: 'Can not get wallet',
requestID: response.headers.get('x-request-id'),
});
}
}
@ -354,7 +407,11 @@ export class PaymentsHttpApi implements PaymentsApi {
const response = await this.client.post(path, null);
if (!response.ok) {
throw new Error('Can not claim wallet');
throw new APIError({
status: response.status,
message: 'Can not claim wallet',
requestID: response.headers.get('x-request-id'),
});
}
const wallet = await response.json();
@ -379,7 +436,11 @@ export class PaymentsHttpApi implements PaymentsApi {
return;
}
throw new Error('Could not purchase pricing package');
throw new APIError({
status: response.status,
message: 'Can not purchase pricing package',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -395,6 +456,10 @@ export class PaymentsHttpApi implements PaymentsApi {
return await response.json();
}
throw new Error('Could not check pricing package availability');
throw new APIError({
status: response.status,
message: 'Could not check pricing package availability',
requestID: response.headers.get('x-request-id'),
});
}
}

View File

@ -4,6 +4,7 @@
import { BaseGql } from '@/api/baseGql';
import { ProjectInvitationItemModel, ProjectMember, ProjectMemberCursor, ProjectMembersApi, ProjectMembersPage } from '@/types/projectMembers';
import { HttpClient } from '@/utils/httpClient';
import { APIError } from '@/utils/error';
export class ProjectMembersApiGql extends BaseGql implements ProjectMembersApi {
private readonly http: HttpClient = new HttpClient();
@ -104,7 +105,11 @@ export class ProjectMembersApiGql extends BaseGql implements ProjectMembersApi {
if (httpResponse.ok) return;
const result = await httpResponse.json();
throw new Error(result.error || 'Failed to send project invitations');
throw new APIError({
status: httpResponse.status,
message: result.error || 'Failed to send project invitations',
requestID: httpResponse.headers.get('x-request-id'),
});
}
/**
@ -115,12 +120,16 @@ export class ProjectMembersApiGql extends BaseGql implements ProjectMembersApi {
public async getInviteLink(projectID: string, email: string): Promise<string> {
const path = `${this.ROOT_PATH}/${projectID}/invite-link?email=${encodeURIComponent(email)}`;
const httpResponse = await this.http.get(path);
const result = await httpResponse.json();
if (httpResponse.ok) {
return await httpResponse.json();
return result;
}
throw new Error('Can not get invite link');
throw new APIError({
status: httpResponse.status,
message: result.error || 'Can not get invite link',
requestID: httpResponse.headers.get('x-request-id'),
});
}
/**

View File

@ -16,6 +16,7 @@ import {
} from '@/types/projects';
import { HttpClient } from '@/utils/httpClient';
import { Time } from '@/utils/time';
import { APIError } from '@/utils/error';
export class ProjectsApiGql extends BaseGql implements ProjectsApi {
private readonly http: HttpClient = new HttpClient();
@ -149,7 +150,11 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
const response = await this.http.get(path);
if (!response.ok) {
throw new Error('can not get usage limits');
throw new APIError({
status: response.status,
message: 'Can not get usage limits',
requestID: response.headers.get('x-request-id'),
});
}
const limits = await response.json();
@ -177,8 +182,11 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
const response = await this.http.get(path);
if (!response.ok) {
throw new Error('can not get total usage limits');
throw new APIError({
status: response.status,
message: 'Can not get total usage limits',
requestID: response.headers.get('x-request-id'),
});
}
const limits = await response.json();
@ -206,7 +214,11 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
const response = await this.http.get(path);
if (!response.ok) {
throw new Error('Can not get project daily usage');
throw new APIError({
status: response.status,
message: 'Can not get project daily usage',
requestID: response.headers.get('x-request-id'),
});
}
const usage = await response.json();
@ -237,7 +249,11 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
return await response.json();
}
throw new Error('Can not get project salt');
throw new APIError({
status: response.status,
message: 'Can not get project salt',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -296,7 +312,11 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
));
}
throw new Error(result.error || 'Failed to get project invitations');
throw new APIError({
status: response.status,
message: result.error || 'Failed to get project invitations',
requestID: response.headers.get('x-request-id'),
});
}
/**
@ -312,7 +332,11 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
if (httpResponse.ok) return;
const result = await httpResponse.json();
throw new Error(result.error || 'Failed to respond to project invitation');
throw new APIError({
status: httpResponse.status,
message: result.error || 'Failed to respond to project invitation',
requestID: httpResponse.headers.get('x-request-id'),
});
}
/**

View File

@ -244,7 +244,8 @@ async function onPageClick(index: number, limit: number): Promise<void> {
try {
await agStore.getAccessGrants(index, projectsStore.state.selectedProject.id, limit);
} catch (error) {
notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
error.message = `Unable to fetch Access Grants. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
}
}
@ -280,7 +281,8 @@ async function fetch(searchQuery: string): Promise<void> {
try {
await agStore.getAccessGrants(FIRST_PAGE, projectsStore.state.selectedProject.id);
} catch (error) {
notify.error(`Unable to fetch accesses: ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
error.message = `Unable to fetch accesses: ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
}
}
@ -332,7 +334,8 @@ onMounted(async () => {
await agStore.getAccessGrants(FIRST_PAGE, projectsStore.state.selectedProject.id);
areGrantsFetching.value = false;
} catch (error) {
notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
error.message = `Unable to fetch Access Grants. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
}
});

View File

@ -626,7 +626,7 @@ async function setLastStep(): Promise<void> {
bucketsStore.setPromptForPassphrase(false);
}
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.CREATE_AG_MODAL);
}
isLoading.value = false;
@ -648,7 +648,7 @@ onMounted(async () => {
bucketsStore.getAllBucketsNames(projectID),
]);
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.CREATE_AG_MODAL);
}
isLoading.value = false;

View File

@ -184,7 +184,7 @@ async function enableMFA(): Promise<void> {
await usersStore.generateUserMFASecret();
toggleEnableMFAModal();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.ACCOUNT_SETTINGS_AREA);
notify.notifyError(error, AnalyticsErrorEventSource.ACCOUNT_SETTINGS_AREA);
}
});
}
@ -198,7 +198,7 @@ async function generateNewMFARecoveryCodes(): Promise<void> {
await usersStore.generateUserMFARecoveryCodes();
toggleMFACodesModal();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.ACCOUNT_SETTINGS_AREA);
notify.notifyError(error, AnalyticsErrorEventSource.ACCOUNT_SETTINGS_AREA);
}
});
}

View File

@ -151,7 +151,7 @@ onMounted(async (): Promise<void> => {
try {
await billingStore.getBalance();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.BILLING_AREA);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_AREA);
}
});
</script>

View File

@ -47,7 +47,7 @@ async function fetchHistory(): Promise<void> {
try {
await billingStore.getPaymentsHistory();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_HISTORY_TAB);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_HISTORY_TAB);
}
}

View File

@ -124,7 +124,7 @@ onMounted(async () => {
try {
await billingStore.getCoupon();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_COUPONS_TAB);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_COUPONS_TAB);
}
isCouponFetching.value = false;

View File

@ -176,7 +176,7 @@ onMounted(async () => {
try {
await projectsStore.getProjects();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_OVERVIEW_TAB);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_OVERVIEW_TAB);
isDataFetching.value = false;
return;
}
@ -185,7 +185,7 @@ onMounted(async () => {
await billingStore.getProjectUsageAndChargesCurrentRollup();
await billingStore.getProjectUsagePriceModel();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_OVERVIEW_TAB);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_OVERVIEW_TAB);
}
isDataFetching.value = false;

View File

@ -304,7 +304,7 @@ async function fetchHistory(): Promise<void> {
transactionCount.value = nativePaymentHistoryItems.value.length;
displayedHistory.value = nativePaymentHistoryItems.value.slice(0, DEFAULT_PAGE_LIMIT);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
} finally {
nativePayIsLoading.value = false;
}
@ -336,7 +336,7 @@ async function updatePaymentMethod(): Promise<void> {
await notify.success('Default payment card updated');
isChangeDefaultPaymentModalOpen.value = false;
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
}
}
@ -351,7 +351,7 @@ async function removePaymentMethod(): Promise<void> {
analytics.eventTriggered(AnalyticsEvent.CREDIT_CARD_REMOVED);
await notify.success('Credit card removed');
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
}
isRemovePaymentMethodsModalOpen.value = false;
@ -376,7 +376,7 @@ async function addCard(token: string): Promise<void> {
// We fetch User one more time to update their Paid Tier status.
await usersStore.getUser();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
emit('toggleIsLoading');
@ -387,7 +387,7 @@ async function addCard(token: string): Promise<void> {
try {
await billingStore.getCreditCards();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
emit('toggleIsLoading');
}

View File

@ -160,7 +160,7 @@ async function claimWalletClick(): Promise<void> {
// wallet claimed; open token modal
onAddTokensClick();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_STORJ_TOKEN_CONTAINER);
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_STORJ_TOKEN_CONTAINER);
}
isLoading.value = false;

View File

@ -271,7 +271,8 @@ async function fetchPreviewAndMapUrl(): Promise<void> {
try {
url = await generateObjectPreviewAndMapURL(filePath.value);
} catch (error) {
notify.error(`Unable to get file preview and map URL. ${error.message}`, AnalyticsErrorEventSource.GALLERY_VIEW);
error.message = `Unable to get file preview and map URL. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.GALLERY_VIEW);
}
if (!url) {

View File

@ -122,7 +122,7 @@ async function onResendEmailButtonClick(): Promise<void> {
try {
await auth.resendEmail(email);
} catch (error) {
await notify.error(error.message, null);
notify.notifyError(error, null);
}
startResendEmailCountdown();

View File

@ -197,7 +197,8 @@ async function onAddUsersClick(): Promise<void> {
try {
await pmStore.inviteMembers(emailArray, projectsStore.state.selectedProject.id);
} catch (error) {
await notify.error(`Error adding project members. ${error.message}`, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
error.message = `Error adding project members. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
isLoading.value = false;
return;
@ -210,7 +211,8 @@ async function onAddUsersClick(): Promise<void> {
try {
await pmStore.getProjectMembers(FIRST_PAGE, projectsStore.state.selectedProject.id);
} catch (error) {
await notify.error(`Unable to fetch project members. ${error.message}`, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
error.message = `Unable to fetch project members. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
}
closeModal();

View File

@ -175,7 +175,7 @@ async function onUpdateClick(): Promise<void> {
try {
await auth.changePassword(oldPassword.value, newPassword.value);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.CHANGE_PASSWORD_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.CHANGE_PASSWORD_MODAL);
return;
}
@ -187,7 +187,7 @@ async function onUpdateClick(): Promise<void> {
router.push(RouteConfig.Login.path);
}, DELAY_BEFORE_REDIRECT);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.CHANGE_PASSWORD_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.CHANGE_PASSWORD_MODAL);
}
analytics.eventTriggered(AnalyticsEvent.PASSWORD_CHANGED);

View File

@ -264,7 +264,7 @@ async function onCreate(): Promise<void> {
LocalData.setBucketWasCreatedStatus();
}
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BUCKET_CREATION_FLOW);
notify.notifyError(error, AnalyticsErrorEventSource.BUCKET_CREATION_FLOW);
} finally {
isLoading.value = false;
}
@ -319,7 +319,7 @@ onMounted(async (): Promise<void> => {
await bucketsStore.getAllBucketsNames(projectsStore.state.selectedProject.id);
bucketName.value = allBucketNames.value.length > 0 ? '' : 'demo-bucket';
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BUCKET_CREATION_NAME_STEP);
notify.notifyError(error, AnalyticsErrorEventSource.BUCKET_CREATION_NAME_STEP);
} finally {
bucketNamesLoading.value = false;
}

View File

@ -140,7 +140,7 @@ async function onDelete(): Promise<void> {
analytics.eventTriggered(AnalyticsEvent.BUCKET_DELETED);
await fetchBuckets();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.DELETE_BUCKET_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.DELETE_BUCKET_MODAL);
return;
} finally {
isLoading.value = false;

View File

@ -108,7 +108,7 @@ async function disable(): Promise<void> {
closeModal();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.DISABLE_MFA_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.DISABLE_MFA_MODAL);
isError.value = true;
}

View File

@ -90,7 +90,7 @@ async function onUpdateClick(): Promise<void> {
try {
await userStore.updateUser(userInfo);
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.EDIT_PROFILE_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.EDIT_PROFILE_MODAL);
return;
}

View File

@ -117,7 +117,7 @@ async function save() {
notify.success(`Session timeout changed successfully. Your session timeout is ${sessionDuration.value?.shortString}.`);
onClose();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.EDIT_TIMEOUT_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.EDIT_TIMEOUT_MODAL);
} finally {
isLoading.value = false;
}

View File

@ -153,7 +153,7 @@ async function showCodes(): Promise<void> {
isEnable.value = false;
isCodes.value = true;
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.ENABLE_MFA_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.ENABLE_MFA_MODAL);
}
}
@ -181,7 +181,7 @@ async function enable(): Promise<void> {
analytics.eventTriggered(AnalyticsEvent.MFA_ENABLED);
await notify.success('MFA was enabled successfully');
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.ENABLE_MFA_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.ENABLE_MFA_MODAL);
isError.value = true;
}

View File

@ -155,7 +155,7 @@ async function onContinue(): Promise<void> {
bucketsStore.setPromptForPassphrase(false);
isLoading.value = false;
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.OPEN_BUCKET_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.OPEN_BUCKET_MODAL);
isLoading.value = false;
return;
}

View File

@ -90,14 +90,16 @@ async function respondToInvitation(response: ProjectInvitationResponse): Promise
success = true;
} catch (error) {
const action = accepted ? 'accept' : 'decline';
notify.error(`Failed to ${action} project invitation. ${error.message}`, AnalyticsErrorEventSource.JOIN_PROJECT_MODAL);
error.message = `Failed to ${action} project invitation. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.JOIN_PROJECT_MODAL);
}
try {
await projectsStore.getUserInvitations();
await projectsStore.getProjects();
} catch (error) {
notify.error(`Failed to reload projects and invitations list. ${error.message}`, AnalyticsErrorEventSource.JOIN_PROJECT_MODAL);
error.message = `Failed to reload projects and invitations list. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.JOIN_PROJECT_MODAL);
}
if (!success) {

View File

@ -242,7 +242,8 @@ async function fetchPreviewAndMapUrl(): Promise<void> {
try {
url = await generateObjectPreviewAndMapURL(filePath.value);
} catch (error) {
notify.error(`Unable to get file preview and map URL. ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
error.message = `Unable to get file preview and map URL. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
}
if (!url) {
@ -306,7 +307,8 @@ async function getSharedLink(): Promise<void> {
try {
objectLink.value = await generateFileOrFolderShareURL(filePath.value);
} catch (error) {
notify.error(`Unable to get sharing URL. ${error.message}`, AnalyticsErrorEventSource.OBJECT_DETAILS_MODAL);
error.message = `Unable to get sharing URL. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.OBJECT_DETAILS_MODAL);
}
}

View File

@ -187,7 +187,7 @@ async function onCardAdded(token: string): Promise<void> {
// Fetch cards to hide paid tier banner
await billingStore.getCreditCards();
} catch (error) {
await notify.error(error.message, null);
notify.notifyError(error, null);
}
isLoading.value = false;

View File

@ -115,13 +115,15 @@ async function onRemove(): Promise<void> {
notify.success('Members were successfully removed from the project');
pmStore.setSearchQuery('');
} catch (error) {
notify.error(`Error removing project members. ${error.message}`, AnalyticsErrorEventSource.PROJECT_MEMBERS_HEADER);
error.message = `Error removing project members. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_MEMBERS_HEADER);
}
try {
await setProjectState();
} catch (error) {
notify.error(`Unable to fetch project members. ${error.message}`, AnalyticsErrorEventSource.PROJECT_MEMBERS_HEADER);
error.message = `Unable to fetch project members. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_MEMBERS_HEADER);
}
closeModal();

View File

@ -126,7 +126,8 @@ onMounted(async (): Promise<void> => {
link.value = await generateFileOrFolderShareURL(filePath.value, shareType.value === ShareType.Folder);
}
} catch (error) {
notify.error(`Unable to get sharing URL. ${error.message}`, AnalyticsErrorEventSource.SHARE_MODAL);
error.message = `Unable to get sharing URL. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.SHARE_MODAL);
}
loading.value = false;

View File

@ -58,7 +58,7 @@ async function rememberSkip() {
await usersStore.updateSettings({ passphrasePrompt: false });
appStore.removeActiveModal();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.SKIP_PASSPHRASE_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.SKIP_PASSPHRASE_MODAL);
}
}

View File

@ -76,7 +76,7 @@ async function onSaveCardClick(): Promise<void> {
try {
await stripeCardInput.value.onSubmit();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
loading.value = false;
}
}
@ -106,7 +106,7 @@ async function addCardToDB(token: string): Promise<void> {
loading.value = false;
props.setSuccess();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
loading.value = false;
}
}

View File

@ -88,7 +88,7 @@ async function onAddTokens(): Promise<void> {
setStep(UpgradeAccountStep.AddTokens);
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
notify.notifyError(error, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
loading.value = false;
@ -123,7 +123,7 @@ async function setSecondStep() {
try {
pkgAvailable = await payments.pricingPackageAvailable();
} catch (error) {
notify.error(error.message, null);
notify.notifyError(error, null);
setStep(UpgradeAccountStep.Options);
loading.value = false;
return;

View File

@ -191,7 +191,7 @@ async function onLogout(): Promise<void> {
await analytics.eventTriggered(AnalyticsEvent.LOGOUT_CLICKED);
await auth.logout();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.NAVIGATION_ACCOUNT_AREA);
notify.notifyError(error, AnalyticsErrorEventSource.NAVIGATION_ACCOUNT_AREA);
}
}

View File

@ -459,7 +459,7 @@ async function onProjectClick(): Promise<void> {
await projectsStore.getProjects();
await projectsStore.getProjectLimits(selectedProject.value.id);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.MOBILE_NAVIGATION);
notify.notifyError(error, AnalyticsErrorEventSource.MOBILE_NAVIGATION);
} finally {
isLoading.value = false;
}
@ -492,7 +492,8 @@ async function onProjectSelected(projectID: string): Promise<void> {
projectsStore.getProjectLimits(projectID),
]);
} catch (error) {
notify.error(`Unable to select project. ${error.message}`, AnalyticsErrorEventSource.MOBILE_NAVIGATION);
error.message = `Unable to select project. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.MOBILE_NAVIGATION);
}
}
@ -584,7 +585,7 @@ async function onLogout(): Promise<void> {
analytics.eventTriggered(AnalyticsEvent.LOGOUT_CLICKED);
await auth.logout();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.MOBILE_NAVIGATION);
notify.notifyError(error, AnalyticsErrorEventSource.MOBILE_NAVIGATION);
}
}
</script>

View File

@ -251,7 +251,7 @@ async function toggleSelection(): Promise<void> {
await projectsStore.getProjects();
await projectsStore.getProjectLimits(selectedProject.value.id);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.NAVIGATION_PROJECT_SELECTION);
notify.notifyError(error, AnalyticsErrorEventSource.NAVIGATION_PROJECT_SELECTION);
} finally {
isLoading.value = false;
}
@ -301,7 +301,7 @@ async function onProjectSelected(projectID: string): Promise<void> {
pmStore.getProjectMembers(FIRST_PAGE, projectID),
]);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.NAVIGATION_PROJECT_SELECTION);
notify.notifyError(error, AnalyticsErrorEventSource.NAVIGATION_PROJECT_SELECTION);
}
return;

View File

@ -226,4 +226,10 @@ onMounted((): void => {
.message-info {
font-family: 'font_regular', sans-serif;
}
.message-footer {
font-family: 'font_regular', sans-serif;
font-size: 12px;
line-height: 20px;
}
</style>

View File

@ -89,14 +89,16 @@ async function onDeclineClicked(): Promise<void> {
await projectsStore.respondToInvitation(invite.value.projectID, ProjectInvitationResponse.Decline);
analytics.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
} catch (error) {
notify.error(`Failed to decline project invitation. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
error.message = `Failed to decline project invitation. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
}
try {
await projectsStore.getUserInvitations();
await projectsStore.getProjects();
} catch (error) {
notify.error(`Failed to reload projects and invitations list. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
error.message = `Failed to reload projects and invitations list. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
}
isLoading.value = false;

View File

@ -118,7 +118,7 @@ async function openBucket(): Promise<void> {
await bucketsStore.setS3Client(projectsStore.state.selectedProject.id);
isLoading.value = false;
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.BUCKET_DETAILS_PAGE);
notify.notifyError(error, AnalyticsErrorEventSource.BUCKET_DETAILS_PAGE);
isLoading.value = false;
return;
}

View File

@ -214,7 +214,7 @@ async function openBucket(bucketName: string): Promise<void> {
await bucketsStore.setS3Client(projectsStore.state.selectedProject.id);
overallLoading.value = false;
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.BUCKET_TABLE);
notify.notifyError(error, AnalyticsErrorEventSource.BUCKET_TABLE);
overallLoading.value = false;
return;
}

View File

@ -95,7 +95,7 @@ watch(passphrase, async () => {
try {
await bucketsStore.setS3Client(projectID);
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPLOAD_FILE_VIEW);
notify.notifyError(error, AnalyticsErrorEventSource.UPLOAD_FILE_VIEW);
return;
}

View File

@ -84,7 +84,7 @@ async function endOnboarding(): Promise<void> {
try {
await usersStore.updateSettings({ onboardingEnd: true });
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.ONBOARDING_OVERVIEW_STEP);
notify.notifyError(error, AnalyticsErrorEventSource.ONBOARDING_OVERVIEW_STEP);
}
}
@ -98,7 +98,7 @@ onMounted(async (): Promise<void> => {
await usersStore.updateSettings({ onboardingStart: true });
}
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.ONBOARDING_OVERVIEW_STEP);
notify.notifyError(error, AnalyticsErrorEventSource.ONBOARDING_OVERVIEW_STEP);
}
const config = configStore.state.config;

View File

@ -89,7 +89,7 @@ onBeforeMount(async () => {
try {
pkgAvailable = await payments.pricingPackageAvailable();
} catch (error) {
notify.error(error.message, null);
notify.notifyError(error, null);
}
if (!pkgAvailable) {
router.push(nextPath);
@ -118,7 +118,7 @@ onBeforeMount(async () => {
try {
await usersStore.updateSettings({ onboardingStart: true });
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.PRICING_PLAN_STEP);
notify.notifyError(error, AnalyticsErrorEventSource.PRICING_PLAN_STEP);
}
}

View File

@ -242,7 +242,8 @@ onMounted(async (): Promise<void> => {
areBucketNamesFetching.value = false;
} catch (error) {
await notify.error(`Unable to fetch all bucket names. ${error.message}`, AnalyticsErrorEventSource.ONBOARDING_PERMISSIONS_STEP);
error.message = `Unable to fetch all bucket names. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ONBOARDING_PERMISSIONS_STEP);
}
isLoading.value = false;

View File

@ -69,7 +69,7 @@ async function endOnboarding(): Promise<void> {
try {
await usersStore.updateSettings({ onboardingEnd: true });
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.ONBOARDING_OVERVIEW_STEP);
notify.notifyError(error, AnalyticsErrorEventSource.ONBOARDING_OVERVIEW_STEP);
}
}
</script>

View File

@ -659,7 +659,7 @@ onMounted(async (): Promise<void> => {
try {
await projectsStore.getProjectLimits(projectID);
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.EDIT_PROJECT_DETAILS);
notify.notifyError(error, AnalyticsErrorEventSource.EDIT_PROJECT_DETAILS);
}
});
</script>

View File

@ -447,7 +447,7 @@ async function onChartsDateRangePick(dateRange: Date[]): Promise<void> {
try {
await projectsStore.getDailyProjectData({ since, before });
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.PROJECT_DASHBOARD_PAGE);
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_DASHBOARD_PAGE);
}
}
@ -516,7 +516,7 @@ onMounted(async (): Promise<void> => {
agStore.getAccessGrants(FIRST_PAGE, projectID),
]);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.PROJECT_DASHBOARD_PAGE);
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_DASHBOARD_PAGE);
} finally {
isDataFetching.value = false;
}

View File

@ -151,7 +151,8 @@ async function onProjectSelected(project: Project): Promise<void> {
analytics.pageVisit(RouteConfig.EditProjectDetails.path);
await router.push(RouteConfig.EditProjectDetails.path);
} catch (error) {
await notify.error(`Unable to select project. ${error.message}`, AnalyticsErrorEventSource.PROJECTS_LIST);
error.message = `Unable to select project. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECTS_LIST);
}
isLoading.value = false;

View File

@ -171,7 +171,8 @@ async function resendInvites(): Promise<void> {
await pmStore.inviteMembers(pmStore.state.selectedProjectMembersEmails, projectsStore.state.selectedProject.id);
notify.success('Invites re-sent!');
} catch (error) {
notify.error(`Unable to resend project invitations. ${error.message}`, AnalyticsErrorEventSource.PROJECT_MEMBERS_HEADER);
error.message = `Unable to resend project invitations. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_MEMBERS_HEADER);
}
try {

View File

@ -145,12 +145,12 @@ function copyLinkClicked() {
withLoading(async () => {
try {
inviteLink.value = await pmStore.getInviteLink(props.model.getEmail(), projectsStore.state.selectedProject.id);
} catch (_) {
notify.error(`Error getting invite link.`, AnalyticsErrorEventSource.PROJECT_MEMBERS_PAGE);
navigator.clipboard.writeText(inviteLink.value);
notify.notify('Invite copied!');
} catch (error) {
error.message = `Error getting invite link. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_MEMBERS_PAGE);
}
navigator.clipboard.writeText(inviteLink.value);
notify.notify('Invite copied!');
});
}

View File

@ -154,13 +154,14 @@ function onResendClick(member: ProjectMemberItemModel) {
analytics.eventTriggered(AnalyticsEvent.RESEND_INVITE_CLICKED);
try {
await pmStore.inviteMembers([member.getEmail()], projectsStore.state.selectedProject.id);
} catch (_) {
await notify.error(`Error resending invite.`, AnalyticsErrorEventSource.PROJECT_MEMBERS_PAGE);
notify.notify('Invite resent!');
pmStore.setSearchQuery('');
} catch (error) {
error.message = `Error resending invite. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_MEMBERS_PAGE);
return;
}
await notify.notify('Invite resent!');
pmStore.setSearchQuery('');
try {
await pmStore.getProjectMembers(FIRST_PAGE, projectsStore.state.selectedProject.id);
} catch (error) {

View File

@ -82,7 +82,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
addNotification(notification);
}
function notifyError(payload: ErrorPayload): void {
function notifyError(payload: ErrorPayload, messageNode?: string): void {
if (payload.source) {
state.analytics.errorEventTriggered(payload.source);
}
@ -91,6 +91,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
() => deleteNotification(notification.id),
NOTIFICATION_TYPES.ERROR,
payload.message,
messageNode,
);
addNotification(notification);

View File

@ -31,11 +31,22 @@ const authLink = setContext((_, { headers }) => {
/**
* Handling unauthorized error.
*/
const errorLink = onError(({ graphQLErrors, networkError }) => {
const errorLink = onError(({ graphQLErrors, networkError, response }) => {
const notificationsStore = useNotificationsStore();
if (graphQLErrors?.length) {
notificationsStore.notifyError({ message: graphQLErrors.join('\n'), source: AnalyticsErrorEventSource.OVERALL_GRAPHQL_ERROR });
const message = graphQLErrors.join('<br>');
let template = `
<p class="message-title">${message}</p>
`;
if (response && response['requestID']) {
template = `
${template}
<p class="message-footer">Request ID: ${response['requestID']}</p>
`;
}
notificationsStore.notifyError({ message: '', source: AnalyticsErrorEventSource.OVERALL_GRAPHQL_ERROR }, template);
}
if (networkError) {
@ -47,8 +58,13 @@ const errorLink = onError(({ graphQLErrors, networkError }) => {
window.location.href = window.location.origin + '/login';
}, 2000);
} else {
const error = typeof nError.result === 'string' ? nError.result : nError.result.error;
nError.result && notificationsStore.notifyError({ message: error, source: AnalyticsErrorEventSource.OVERALL_GRAPHQL_ERROR });
const message = typeof nError.result === 'string' ? nError.result : nError.result.error;
let template = `<p class="message-title">${message}</p>`;
if (typeof nError.result !== 'string' && nError.result.requestID) {
template = `${template} <p class="message-footer">Request ID: ${nError.result.requestID}</p>`;
}
notificationsStore.notifyError({ message: '', source: AnalyticsErrorEventSource.OVERALL_GRAPHQL_ERROR }, template);
}
}

View File

@ -0,0 +1,27 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
/**
* A custom error class with status and requestID properties.
* */
export class APIError extends Error {
public status: number;
public requestID: string | null;
constructor(data: {status: number, message: string, requestID: string | null}) {
super(data.message);
this.message = data.message;
this.requestID = data.requestID;
}
/**
* Returns a new APIError with the same status and requestID but with a different message.
* */
public withMessage(message: string): APIError {
return new APIError({
status: this.status,
message,
requestID: this.requestID,
});
}
}

View File

@ -3,6 +3,7 @@
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useNotificationsStore } from '@/store/modules/notificationsStore';
import { APIError } from '@/utils/error';
/**
* Exposes UI notifications functionality.
@ -15,6 +16,25 @@ export class Notificator {
notificationsStore.notifySuccess(message, messageNode);
}
public notifyError(error: Error, source: AnalyticsErrorEventSource | null): void {
const notificationsStore = useNotificationsStore();
if (error instanceof APIError) {
let template = `
<p class="message-title">${error.message}</p>
`;
if (error.requestID) {
template = `
${template}
<p class="message-footer">Request ID: ${error.requestID}</p>
`;
}
notificationsStore.notifyError({ message: '', source }, template);
return;
}
notificationsStore.notifyError({ message: error.message, source });
}
public error(message: string, source: AnalyticsErrorEventSource | null): void {
const notificationsStore = useNotificationsStore();
notificationsStore.notifyError({ message, source });

View File

@ -105,7 +105,7 @@ async function onActivateClick(): Promise<void> {
await auth.resendEmail(email.value);
isResendSuccessShown.value = true;
} catch (error) {
notify.error(error.message, null);
notify.notifyError(error, null);
}
}

View File

@ -194,7 +194,7 @@ async function ensureLogin(): Promise<void> {
} catch (error) {
if (!(error instanceof ErrorUnauthorized)) {
appStore.changeState(FetchState.ERROR);
await notify.error(error.message, null);
notify.notifyError(error, null);
}
const query = new URLSearchParams(oauthData.value).toString();

View File

@ -548,7 +548,8 @@ async function refreshSession(manual = false): Promise<void> {
try {
LocalData.setSessionExpirationDate(await auth.refreshSession());
} catch (error) {
notify.error((error instanceof ErrorUnauthorized) ? 'Your session was timed out.' : error.message, AnalyticsErrorEventSource.OVERALL_SESSION_EXPIRED_ERROR);
error.message = (error instanceof ErrorUnauthorized) ? 'Your session was timed out.' : error.message;
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_SESSION_EXPIRED_ERROR);
await handleInactive();
isSessionRefreshing.value = false;
return;
@ -612,7 +613,7 @@ async function handleInactive(): Promise<void> {
} catch (error) {
if (error instanceof ErrorUnauthorized) return;
notify.error(error.message, AnalyticsErrorEventSource.OVERALL_SESSION_EXPIRED_ERROR);
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_SESSION_EXPIRED_ERROR);
}
}
@ -639,7 +640,7 @@ async function generateNewMFARecoveryCodes(): Promise<void> {
await usersStore.generateUserMFARecoveryCodes();
toggleMFARecoveryModal();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
}
@ -711,7 +712,7 @@ onMounted(async () => {
} catch (error) {
if (!(error instanceof ErrorUnauthorized)) {
appStore.changeState(FetchState.ERROR);
notify.error(error.message, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
setTimeout(async () => await router.push(RouteConfig.Login.path), 1000);
@ -736,19 +737,22 @@ onMounted(async () => {
notify.success(`The coupon code was added successfully`);
}
} catch (error) {
notify.error(`Unable to setup account. ${error.message}`, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
error.message = `Unable to setup account. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
try {
await billingStore.getCreditCards();
} catch (error) {
notify.error(`Unable to get credit cards. ${error.message}`, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
error.message = `Unable to get credit cards. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
try {
await projectsStore.getUserInvitations();
} catch (error) {
notify.error(`Unable to get project invitations. ${error.message}`, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
error.message = `Unable to get project invitations. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
let projects: Project[] = [];

View File

@ -214,7 +214,7 @@ async function onSendConfigurations(): Promise<void> {
await auth.forgotPassword(email.value, captchaResponseToken.value);
await notify.success('Please look for instructions in your email');
} catch (error) {
await notify.error(error.message, null);
notify.notifyError(error, null);
}
captcha.value?.reset();

View File

@ -437,7 +437,7 @@ async function login(): Promise<void> {
return;
}
await notify.error(error.message, null);
notify.notifyError(error, null);
isLoading.value = false;
return;
}

View File

@ -161,7 +161,7 @@ async function onResetClick(): Promise<void> {
return;
}
await notify.error(error.message, null);
notify.notifyError(error, null);
}
isLoading.value = false;

View File

@ -310,7 +310,7 @@ async function handleInactive(): Promise<void> {
} catch (error) {
if (error instanceof ErrorUnauthorized) return;
notify.error(error.message, AnalyticsErrorEventSource.OVERALL_SESSION_EXPIRED_ERROR);
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_SESSION_EXPIRED_ERROR);
}
}
@ -322,7 +322,7 @@ async function generateNewMFARecoveryCodes(): Promise<void> {
await usersStore.generateUserMFARecoveryCodes();
toggleMFARecoveryModal();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
}
}
@ -437,7 +437,8 @@ async function refreshSession(manual = false): Promise<void> {
try {
LocalData.setSessionExpirationDate(await auth.refreshSession());
} catch (error) {
notify.error((error instanceof ErrorUnauthorized) ? 'Your session was timed out.' : error.message, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
error.message = (error instanceof ErrorUnauthorized) ? 'Your session was timed out.' : error.message;
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
await handleInactive();
isSessionRefreshing.value = false;
return;
@ -492,7 +493,7 @@ onMounted(async () => {
} catch (error) {
if (!(error instanceof ErrorUnauthorized)) {
appStore.changeState(FetchState.ERROR);
await notify.error(error.message, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
}
setTimeout(async () => await router.push(RouteConfig.Login.path), 1000);
@ -517,19 +518,22 @@ onMounted(async () => {
notify.success(`The coupon code was added successfully`);
}
} catch (error) {
notify.error(`Unable to setup account. ${error.message}`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
error.message = `Unable to setup account. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
}
try {
await billingStore.getCreditCards();
} catch (error) {
notify.error(`Unable to get credit cards. ${error.message}`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
error.message = `Unable to get credit cards. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
}
try {
await projectsStore.getUserInvitations();
} catch (error) {
notify.error(`Unable to get project invitations. ${error.message}`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
error.message = `Unable to get project invitations. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
}
try {

View File

@ -282,7 +282,7 @@ async function onLogout(): Promise<void> {
analytics.eventTriggered(AnalyticsEvent.LOGOUT_CLICKED);
await auth.logout();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.NAVIGATION_ACCOUNT_AREA);
notify.notifyError(error, AnalyticsErrorEventSource.NAVIGATION_ACCOUNT_AREA);
}
}

View File

@ -179,7 +179,7 @@ async function onLogout(): Promise<void> {
analytics.eventTriggered(AnalyticsEvent.LOGOUT_CLICKED);
await auth.logout();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.NAVIGATION_ACCOUNT_AREA);
notify.notifyError(error, AnalyticsErrorEventSource.NAVIGATION_ACCOUNT_AREA);
}
}
</script>

View File

@ -83,14 +83,16 @@ async function onDeclineClicked(): Promise<void> {
await projectsStore.respondToInvitation(props.invitation.projectID, ProjectInvitationResponse.Decline);
analytics.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
} catch (error) {
notify.error(`Failed to decline project invitation. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
error.message = `Failed to decline project invitation. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
}
try {
await projectsStore.getUserInvitations();
await projectsStore.getProjects();
} catch (error) {
notify.error(`Failed to reload projects and invitations list. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
error.message = `Failed to reload projects and invitations list. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
}
isLoading.value = false;

View File

@ -131,14 +131,16 @@ function onDeclineClicked(): void {
await projectsStore.respondToInvitation(props.invitation.projectID, ProjectInvitationResponse.Decline);
analytics.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
} catch (error) {
notify.error(`Failed to decline project invitation. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
error.message = `Failed to decline project invitation. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
}
try {
await projectsStore.getUserInvitations();
await projectsStore.getProjects();
} catch (error) {
notify.error(`Failed to reload projects and invitations list. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
error.message = `Failed to reload projects and invitations list. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
}
});
}

View File

@ -800,7 +800,7 @@ async function createUser(): Promise<void> {
await detectBraveBrowser() ? await router.push(braveSuccessPath) : window.location.href = nonBraveSuccessPath;
} catch (error) {
notify.error(error.message, null);
notify.notifyError(error, null);
}
captcha.value?.reset();