diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 8985d0686..b60c0dbb7 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -95,7 +95,16 @@ export default class APIClient { ) { edp = `${ edp }/${ this.siteId }` } - return fetch(edp + path, this.init); + return fetch(edp + path, this.init) + .then(response => { + if (response.ok) { + return response + } else { + throw new Error( + `! ${this.init.method} error on ${path}; ${response.status}` + ) + } + }) } get(path, params, options) { diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx index a616d8853..b80214bd8 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx @@ -145,17 +145,17 @@ function FilterAutoComplete(props: Props) { new APIClient() [method?.toLocaleLowerCase()](endpoint, { ...params, q: inputValue }) .then((response: any) => { - if (response.ok) { return response.json(); - } - throw new Error(response.statusText); }) .then(({ data }: any) => { const _options = data.map((i: any) => ({ value: i.value, label: i.value })) || []; setOptions(_options); callback(_options); setLoading(false); - }); + }) + .catch((e) => { + throw new Error(e); + }) }; const debouncedLoadOptions = React.useCallback(debounce(loadOptions, 1000), [params]); diff --git a/frontend/app/duck/index.js b/frontend/app/duck/index.ts similarity index 93% rename from frontend/app/duck/index.js rename to frontend/app/duck/index.ts index 051ec6933..fd4b0a655 100644 --- a/frontend/app/duck/index.js +++ b/frontend/app/duck/index.ts @@ -3,7 +3,6 @@ import { combineReducers } from 'redux-immutable'; import jwt from './jwt'; import user from './user'; import sessions from './sessions'; -import issues from './issues'; import assignments from './assignments'; import target from './target'; import targetCustom from './targetCustom'; @@ -34,11 +33,10 @@ import customMetrics from './customMetrics'; import search from './search'; import liveSearch from './liveSearch'; -export default combineReducers({ +const rootReducer = combineReducers({ jwt, user, sessions, - issues, assignments, target, targetCustom, @@ -70,3 +68,7 @@ export default combineReducers({ ...integrations, ...sources, }); + +export type RootStore = ReturnType + +export default rootReducer \ No newline at end of file diff --git a/frontend/app/duck/user.js b/frontend/app/duck/user.js index 1420cae45..5b39d34db 100644 --- a/frontend/app/duck/user.js +++ b/frontend/app/duck/user.js @@ -21,7 +21,6 @@ const PUSH_NEW_SITE = 'user/PUSH_NEW_SITE'; const SET_ONBOARDING = 'user/SET_ONBOARDING'; const initialState = Map({ - // client: Client(), account: Account(), siteId: null, passwordRequestError: false, diff --git a/frontend/app/services/ErrorService.ts b/frontend/app/services/ErrorService.ts index 5b3520b5c..9e9402b45 100644 --- a/frontend/app/services/ErrorService.ts +++ b/frontend/app/services/ErrorService.ts @@ -1,16 +1,17 @@ import BaseService from './BaseService'; -import { fetchErrorCheck } from 'App/utils' export default class ErrorService extends BaseService { all(params: any = {}): Promise { return this.client.post('/errors/search', params) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || []); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || []) + .catch(e => Promise.reject(e)) } one(id: string): Promise { return this.client.get(`/errors/${id}`) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } } \ No newline at end of file diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index a5734aff0..966b3668f 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -1,6 +1,5 @@ import Widget from "App/mstore/types/widget"; import APIClient from 'App/api_client'; -import { fetchErrorCheck } from "App/utils"; export default class MetricService { private client: APIClient; @@ -30,8 +29,9 @@ export default class MetricService { */ getMetric(metricId: string): Promise { return this.client.get('/metrics/' + metricId) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } /** @@ -45,8 +45,9 @@ export default class MetricService { const method = isCreating ? 'post' : 'put'; const url = isCreating ? '/metrics' : '/metrics/' + data[Widget.ID_KEY]; return this.client[method](url, data) - .then(fetchErrorCheck) + .then(r => r.json()) .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } /** @@ -74,8 +75,9 @@ export default class MetricService { getMetricChartData(metric: Widget, data: any, isWidget: boolean = false): Promise { const path = isWidget ? `/metrics/${metric.metricId}/chart` : `/metrics/try`; return this.client.post(path, data) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } /** diff --git a/frontend/app/services/NotesService.ts b/frontend/app/services/NotesService.ts index 97534973e..cdecb7c3f 100644 --- a/frontend/app/services/NotesService.ts +++ b/frontend/app/services/NotesService.ts @@ -45,89 +45,61 @@ export interface NotesFilter { } export default class NotesService { - private client: APIClient; + private client: APIClient; - constructor(client?: APIClient) { - this.client = client ? client : new APIClient(); - } + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } - initClient(client?: APIClient) { - this.client = client || new APIClient(); - } + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } - fetchNotes(filter: NotesFilter): Promise { - return this.client.post('/notes', filter).then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error getting notes: ' + r.status) - } - }) - } + fetchNotes(filter: NotesFilter): Promise { + return this.client.post('/notes', filter).then(r => { + return r.json().then(r => r.data) + }) + } - getNotesBySessionId(sessionID: string): Promise { - return this.client.get(`/sessions/${sessionID}/notes`) + getNotesBySessionId(sessionID: string): Promise { + return this.client.get(`/sessions/${sessionID}/notes`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error getting notes for ' +sessionID + ' cuz: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - addNote(sessionID: string, note: WriteNote): Promise { - return this.client.post(`/sessions/${sessionID}/notes`, note) + addNote(sessionID: string, note: WriteNote): Promise { + return this.client.post(`/sessions/${sessionID}/notes`, note) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error adding note: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - updateNote(noteID: string, note: WriteNote): Promise { - return this.client.post(`/notes/${noteID}`, note) + updateNote(noteID: string, note: WriteNote): Promise { + return this.client.post(`/notes/${noteID}`, note) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error updating note: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - deleteNote(noteID: number) { - return this.client.delete(`/notes/${noteID}`) + deleteNote(noteID: number) { + return this.client.delete(`/notes/${noteID}`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error deleting note: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - sendSlackNotification(noteId: string, webhook: string) { - return this.client.get(`/notes/${noteId}/slack/${webhook}`) + sendSlackNotification(noteId: string, webhook: string) { + return this.client.get(`/notes/${noteId}/slack/${webhook}`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error sending slack notif: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - sendMsTeamsNotification(noteId: string, webhook: string) { - return this.client.get(`/notes/${noteId}/msteams/${webhook}`) + sendMsTeamsNotification(noteId: string, webhook: string) { + return this.client.get(`/notes/${noteId}/msteams/${webhook}`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error sending slack notif: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } } diff --git a/frontend/app/services/RecordingsService.ts b/frontend/app/services/RecordingsService.ts index f7885a36b..6dd3845c5 100644 --- a/frontend/app/services/RecordingsService.ts +++ b/frontend/app/services/RecordingsService.ts @@ -37,11 +37,7 @@ export default class RecordingsService { reserveUrl(siteId: string, recordingData: RecordingData): Promise<{ URL: string; key: string }> { return this.client.put(`/${siteId}/assist/save`, recordingData).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't reserve space for recording: " + r.status); - } }); } @@ -51,61 +47,37 @@ export default class RecordingsService { headers: { 'Content-Type': 'video/webm' }, body: file, }).then((r) => { - if (r.ok) { return true; - } else { - throw new Error("Can't upload file: " + r.status); - } }); } confirmFile(siteId: string, recordingData: RecordingData, key: string): Promise { return this.client.put(`/${siteId}/assist/save/done`, { ...recordingData, key }).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't confirm file saving: " + r.status); - } }); } fetchRecordings(filters: FetchFilter): Promise { return this.client.post(`/assist/records`, filters).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't get recordings: " + r.status); - } }); } fetchRecording(id: number): Promise { return this.client.get(`/assist/records/${id}`).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't get recordings: " + r.status); - } }); } updateRecordingName(id: number, name: string): Promise { return this.client.post(`/assist/records/${id}`, { name }).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't get recordings: " + r.status); - } }); } deleteRecording(id: number): Promise { return this.client.delete(`/assist/records/${id}`).then((r) => { - if (r.ok) { return r.json().then((j) => j.data); - } else { - throw new Error("Can't get recordings: " + r.status); - } }); } } diff --git a/frontend/app/services/SessionService.ts b/frontend/app/services/SessionService.ts index 0ea0468b2..6f59b3805 100644 --- a/frontend/app/services/SessionService.ts +++ b/frontend/app/services/SessionService.ts @@ -1,5 +1,4 @@ import APIClient from 'App/api_client'; -import { fetchErrorCheck } from 'App/utils'; export default class SettingsService { private client: APIClient; @@ -26,8 +25,9 @@ export default class SettingsService { getSessions(filter: any) { return this.client .post('/sessions/search', filter) - .then(fetchErrorCheck) - .then((response) => response.data || []); + .then(r => r.json()) + .then((response) => response.data || []) + .catch(e => Promise.reject(e)) } getSessionInfo(sessionId: string, isLive?: boolean): Promise> { @@ -41,7 +41,8 @@ export default class SettingsService { getLiveSessions(filter: any) { return this.client .post('/assist/sessions', filter) - .then(fetchErrorCheck) - .then((response) => response.data || []); + .then(r => r.json()) + .then((response) => response.data || []) + .catch(e => Promise.reject(e)) } } diff --git a/frontend/app/services/UserService.ts b/frontend/app/services/UserService.ts index 7a515d910..705040eb2 100644 --- a/frontend/app/services/UserService.ts +++ b/frontend/app/services/UserService.ts @@ -1,6 +1,5 @@ import APIClient from 'App/api_client'; import { IUser } from 'App/mstore/types/user' -import { fetchErrorCheck } from 'App/utils' export default class UserService { private client: APIClient; @@ -29,12 +28,14 @@ export default class UserService { const data = user.toSave(); if (user.userId) { return this.client.put('/client/members/' + user.userId, data) - .then(fetchErrorCheck) + .then(r => r.json()) .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } else { return this.client.post('/client/members', data) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } } @@ -46,8 +47,9 @@ export default class UserService { delete(userId: string) { return this.client.delete('/client/members/' + userId) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } getRoles() { diff --git a/frontend/app/types/account/account.js b/frontend/app/types/account/account.ts similarity index 57% rename from frontend/app/types/account/account.js rename to frontend/app/types/account/account.ts index bb1c058cd..00f4e30d4 100644 --- a/frontend/app/types/account/account.js +++ b/frontend/app/types/account/account.ts @@ -1,7 +1,27 @@ -import Member from 'Types/member'; -import Limit from './limit'; +import Member, { IMember } from 'Types/member'; +import Limit, { ILimits } from './limit'; import { DateTime } from 'luxon'; +// TODO types for mobx and all +export interface IAccount extends IMember { + changePassword?: any + limits: ILimits + banner: string + email: string + verifiedEmail: string + id: string + smtp: boolean + license: string + expirationDate?: DateTime + permissions: string[] + iceServers: string + hasPassword: boolean + apiKey: string + tenantKey: string + edition: string + optOut: string +} + export default Member.extend({ changePassword: undefined, limits: Limit(), diff --git a/frontend/app/types/account/limit.js b/frontend/app/types/account/limit.ts similarity index 61% rename from frontend/app/types/account/limit.js rename to frontend/app/types/account/limit.ts index f5274f7b0..7ded53b51 100644 --- a/frontend/app/types/account/limit.js +++ b/frontend/app/types/account/limit.ts @@ -1,6 +1,16 @@ import Record from 'Types/Record'; import { Map } from 'immutable'; +interface ILimitValue { + limit: number + remaining: number +} + +export interface ILimits { + teamMember: ILimitValue + sites: ILimitValue +} + const defaultValues = Map({ limit: 0, remaining: 0 }); const Limit = Record({ teamMember: defaultValues, diff --git a/frontend/app/types/member.js b/frontend/app/types/member.ts similarity index 75% rename from frontend/app/types/member.js rename to frontend/app/types/member.ts index 8e15397f6..d3914eac9 100644 --- a/frontend/app/types/member.js +++ b/frontend/app/types/member.ts @@ -2,6 +2,20 @@ import Record from 'Types/Record'; import { DateTime } from 'luxon'; import { validateEmail, validateName } from 'App/validate'; +export interface IMember { + id: string + name: string + email: string + createdAt: DateTime + admin: boolean + superAdmin: boolean + joined: boolean + expiredInvitation: boolean + roleId: string + roleName: string + invitationLink: string +} + export default Record({ id: undefined, name: '', diff --git a/frontend/app/utils/index.ts b/frontend/app/utils/index.ts index 741e91e8a..b9ab9f8bf 100644 --- a/frontend/app/utils/index.ts +++ b/frontend/app/utils/index.ts @@ -346,13 +346,6 @@ export const exportCSVFile = (headers, items, fileTitle) => { } }; -export const fetchErrorCheck = async (response: any) => { - if (!response.ok) { - return Promise.reject(response); - } - return response.json(); -}; - export const cleanSessionFilters = (data: any) => { const { filters, ...rest } = data; const _fitlers = filters.filter((f: any) => {