diff --git a/package-lock.json b/package-lock.json
index bb5eb58..3bf5271 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,8 +18,8 @@
"@next/bundle-analyzer": "^13.1.6",
"@next/font": "^13.1.6",
"@reduxjs/toolkit": "^1.9.2",
- "@tinystacks/ops-core": "^0.3.2",
- "@tinystacks/ops-model": "^0.4.0",
+ "@tinystacks/ops-core": "^0.4.0",
+ "@tinystacks/ops-model": "^0.5.0",
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
@@ -36,6 +36,7 @@
"lodash.isempty": "^4.4.0",
"lodash.isequal": "^4.4.0",
"lodash.kebabcase": "^4.1.1",
+ "lodash.upperfirst": "^4.3.1",
"next": "13.2.4",
"next-compose-plugins": "^2.2.1",
"next-optimized-images": "^2.6.2",
@@ -60,6 +61,7 @@
"@types/lodash.isempty": "^4.4.7",
"@types/lodash.isequal": "^4.4.7",
"@types/lodash.kebabcase": "^4.1.7",
+ "@types/lodash.upperfirst": "^4.3.7",
"@types/react-redux": "^7.1.25",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.50.0",
@@ -4219,19 +4221,21 @@
}
},
"node_modules/@tinystacks/ops-core": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@tinystacks/ops-core/-/ops-core-0.3.2.tgz",
- "integrity": "sha512-YTN2KHgtQgf3srcn8yH87kmvRqr/W5DSEWsi6fKFy+IoSd7/Mpv7QEijwadgNvlqEPbE9gixqERxsDatKcSCgA==",
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@tinystacks/ops-core/-/ops-core-0.4.0.tgz",
+ "integrity": "sha512-j1QdSHzJ2YhQiRacArfKNkrvzcksWsZAXTDiliZMTBsNbtbRQVpfbkOowHb1B/iUU6hOENEh83u8LTW0TOH59A==",
"dependencies": {
- "@tinystacks/ops-model": "^0.4.0",
+ "@tinystacks/ops-model": "^0.5.0",
"@types/react": "^18.0.28",
+ "http-status-codes": "^2.2.0",
"lodash.get": "^4.4.2",
"lodash.isnil": "^4.0.0"
}
},
"node_modules/@tinystacks/ops-core/node_modules/@types/react": {
- "version": "18.2.0",
- "license": "MIT",
+ "version": "18.2.13",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.13.tgz",
+ "integrity": "sha512-vJ+zElvi/Zn9cVXB5slX2xL8PZodPCwPRDpittQdw43JR2AJ5k3vKdgJJyneV/cYgIbLQUwXa9JVDvUZXGba+Q==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -4239,9 +4243,9 @@
}
},
"node_modules/@tinystacks/ops-model": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@tinystacks/ops-model/-/ops-model-0.4.0.tgz",
- "integrity": "sha512-zh2BM/SLMdpH0ataWWmDzxgW/YGnRrBZ3f/4W/C6joFsNk4Yk7MMlY70Xfj8Z3kMLyauZ6ATK2GZbXbPP42z7w==",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@tinystacks/ops-model/-/ops-model-0.5.0.tgz",
+ "integrity": "sha512-gsAbsHo6qX5StiH9U2DZJJ77bHhOy/u1fZ+CJPunBbkAPBbBFWwtizJ2PZuTrY7RfjiWUzudglpKi68AfqptUA==",
"dependencies": {
"openapi-typescript-codegen": "^0.23.0"
}
@@ -4477,6 +4481,15 @@
"@types/lodash": "*"
}
},
+ "node_modules/@types/lodash.upperfirst": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/@types/lodash.upperfirst/-/lodash.upperfirst-4.3.7.tgz",
+ "integrity": "sha512-CrBjoB4lO6h7tXNMBUl1eh/w0KdMosiEOXOoD5DMECsA/kDWo/WQfOt1KyGKVvgwK3I6cKAY6z8LymKiMazLFg==",
+ "dev": true,
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
"node_modules/@types/memcached": {
"version": "2.2.7",
"dev": true,
@@ -9494,6 +9507,11 @@
"node": ">= 6"
}
},
+ "node_modules/http-status-codes": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz",
+ "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng=="
+ },
"node_modules/https-browserify": {
"version": "1.0.0",
"license": "MIT",
@@ -12260,7 +12278,8 @@
},
"node_modules/lodash.get": {
"version": "4.4.2",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
},
"node_modules/lodash.isempty": {
"version": "4.4.0",
@@ -12272,7 +12291,8 @@
},
"node_modules/lodash.isnil": {
"version": "4.0.0",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
+ "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="
},
"node_modules/lodash.kebabcase": {
"version": "4.1.1",
@@ -12287,6 +12307,11 @@
"version": "4.6.2",
"license": "MIT"
},
+ "node_modules/lodash.upperfirst": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
+ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg=="
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"license": "MIT",
@@ -20278,18 +20303,21 @@
"requires": {}
},
"@tinystacks/ops-core": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@tinystacks/ops-core/-/ops-core-0.3.2.tgz",
- "integrity": "sha512-YTN2KHgtQgf3srcn8yH87kmvRqr/W5DSEWsi6fKFy+IoSd7/Mpv7QEijwadgNvlqEPbE9gixqERxsDatKcSCgA==",
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@tinystacks/ops-core/-/ops-core-0.4.0.tgz",
+ "integrity": "sha512-j1QdSHzJ2YhQiRacArfKNkrvzcksWsZAXTDiliZMTBsNbtbRQVpfbkOowHb1B/iUU6hOENEh83u8LTW0TOH59A==",
"requires": {
- "@tinystacks/ops-model": "^0.4.0",
+ "@tinystacks/ops-model": "^0.5.0",
"@types/react": "^18.0.28",
+ "http-status-codes": "^2.2.0",
"lodash.get": "^4.4.2",
"lodash.isnil": "^4.0.0"
},
"dependencies": {
"@types/react": {
- "version": "18.2.0",
+ "version": "18.2.13",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.13.tgz",
+ "integrity": "sha512-vJ+zElvi/Zn9cVXB5slX2xL8PZodPCwPRDpittQdw43JR2AJ5k3vKdgJJyneV/cYgIbLQUwXa9JVDvUZXGba+Q==",
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -20299,9 +20327,9 @@
}
},
"@tinystacks/ops-model": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@tinystacks/ops-model/-/ops-model-0.4.0.tgz",
- "integrity": "sha512-zh2BM/SLMdpH0ataWWmDzxgW/YGnRrBZ3f/4W/C6joFsNk4Yk7MMlY70Xfj8Z3kMLyauZ6ATK2GZbXbPP42z7w==",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@tinystacks/ops-model/-/ops-model-0.5.0.tgz",
+ "integrity": "sha512-gsAbsHo6qX5StiH9U2DZJJ77bHhOy/u1fZ+CJPunBbkAPBbBFWwtizJ2PZuTrY7RfjiWUzudglpKi68AfqptUA==",
"requires": {
"openapi-typescript-codegen": "^0.23.0"
}
@@ -20497,6 +20525,15 @@
"@types/lodash": "*"
}
},
+ "@types/lodash.upperfirst": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/@types/lodash.upperfirst/-/lodash.upperfirst-4.3.7.tgz",
+ "integrity": "sha512-CrBjoB4lO6h7tXNMBUl1eh/w0KdMosiEOXOoD5DMECsA/kDWo/WQfOt1KyGKVvgwK3I6cKAY6z8LymKiMazLFg==",
+ "dev": true,
+ "requires": {
+ "@types/lodash": "*"
+ }
+ },
"@types/memcached": {
"version": "2.2.7",
"dev": true,
@@ -23983,6 +24020,11 @@
"debug": "4"
}
},
+ "http-status-codes": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz",
+ "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng=="
+ },
"https-browserify": {
"version": "1.0.0",
"peer": true
@@ -25677,7 +25719,9 @@
"version": "4.3.0"
},
"lodash.get": {
- "version": "4.4.2"
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
},
"lodash.isempty": {
"version": "4.4.0"
@@ -25686,7 +25730,9 @@
"version": "4.5.0"
},
"lodash.isnil": {
- "version": "4.0.0"
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
+ "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="
},
"lodash.kebabcase": {
"version": "4.1.1",
@@ -25699,6 +25745,11 @@
"lodash.mergewith": {
"version": "4.6.2"
},
+ "lodash.upperfirst": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
+ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg=="
+ },
"loose-envify": {
"version": "1.4.0",
"requires": {
diff --git a/package.json b/package.json
index b1f8841..7a2e9d4 100644
--- a/package.json
+++ b/package.json
@@ -29,8 +29,8 @@
"@next/bundle-analyzer": "^13.1.6",
"@next/font": "^13.1.6",
"@reduxjs/toolkit": "^1.9.2",
- "@tinystacks/ops-core": "^0.3.2",
- "@tinystacks/ops-model": "^0.4.0",
+ "@tinystacks/ops-core": "^0.4.0",
+ "@tinystacks/ops-model": "^0.5.0",
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
@@ -47,6 +47,7 @@
"lodash.isempty": "^4.4.0",
"lodash.isequal": "^4.4.0",
"lodash.kebabcase": "^4.1.1",
+ "lodash.upperfirst": "^4.3.1",
"next": "13.2.4",
"next-compose-plugins": "^2.2.1",
"next-optimized-images": "^2.6.2",
@@ -71,6 +72,7 @@
"@types/lodash.isempty": "^4.4.7",
"@types/lodash.isequal": "^4.4.7",
"@types/lodash.kebabcase": "^4.1.7",
+ "@types/lodash.upperfirst": "^4.3.7",
"@types/react-redux": "^7.1.25",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.50.0",
diff --git a/src/components/common/dismissable-error-banner.tsx b/src/components/common/dismissable-error-banner.tsx
index 54e76fc..984da97 100644
--- a/src/components/common/dismissable-error-banner.tsx
+++ b/src/components/common/dismissable-error-banner.tsx
@@ -1,19 +1,20 @@
import {
Alert,
+ Box,
Container,
Center,
Flex,
AlertIcon,
AlertTitle,
AlertDescription,
- CloseButton
+ CloseButton,
+ Text
} from '@chakra-ui/react';
+import upperFirst from 'lodash.upperfirst';
+import { ShowableError } from 'ops-frontend/types';
type DismissableErrorBannerProps = {
- error: {
- title: string;
- message: string;
- },
+ error: ShowableError,
dismissError: () => void;
};
@@ -21,16 +22,53 @@ export default function DismissableErrorBanner (props: DismissableErrorBannerPro
const {
error: {
title,
- message
+ message,
+ cause,
+ context,
+ fields = {}
},
dismissError
} = props;
+ let extraDetail;
+ if (cause || context) {
+ const punctuationCheck = new RegExp('.*[,.?!;:=-]');
+ let causePunctuation;
+ if (cause) {
+ causePunctuation = punctuationCheck.test(cause) ? '' :
+ (
+ context ? ':' : '.'
+ );
+ }
+ extraDetail = (
+
+
+ {`${upperFirst(cause)}${causePunctuation}`}
+
+
+ {
+ context ? context :
+ Object.entries(fields).map(([property, propError]) => {
+ return `${property} - ${propError}`
+ }).join('\n')
+ }
+
+
+ )
+ }
+
return (
-
+
-
+
+ {extraDetail}
);
}
\ No newline at end of file
diff --git a/src/components/dashboard/dashboard-list.tsx b/src/components/dashboard/dashboard-list.tsx
index 96eb633..4dd6313 100644
--- a/src/components/dashboard/dashboard-list.tsx
+++ b/src/components/dashboard/dashboard-list.tsx
@@ -16,7 +16,7 @@ import {
dismissError,
selectConsoleName,
selectDashboards,
- selectErropr,
+ selectError,
updateConsole
} from 'ops-frontend/store/consoleSlice';
import { useAppDispatch, useAppSelector } from 'ops-frontend/store/hooks';
@@ -25,15 +25,16 @@ import { FullpageLayout } from 'ops-frontend/components/layout/fullpage-layout';
import { DashboardCard } from 'ops-frontend/components/dashboard/dashboard-card';
import CreateDashboardModal from 'ops-frontend/components/dashboard/create-dashboard-modal';
import DismissableErrorBanner from 'ops-frontend/components/common/dismissable-error-banner';
+import { ShowableError } from 'ops-frontend/types';
export function DashboardList () {
const { t: hm } = useTranslation('home');
const { t: d } = useTranslation('dashboard');
const dashboards = useAppSelector(selectDashboards);
const consoleName = useAppSelector(selectConsoleName);
- const error = useAppSelector(selectErropr);
+ const error = useAppSelector(selectError);
const dispatch = useAppDispatch();
- const [consolesError, setConsolesError] = useState(undefined);
+ const [consolesError, setConsolesError] = useState(undefined);
const [retryCount, setRetryCount] = useState(0);
async function fetchData() {
try {
@@ -45,7 +46,17 @@ export function DashboardList () {
setRetryCount(0);
}
} catch (e: any) {
- setConsolesError(e.message);
+ const {
+ message,
+ cause,
+ context
+ } = e?.body?.body || {};
+ setConsolesError({
+ title: hm('consolesError'),
+ message,
+ cause,
+ context
+ });
setRetryCount(retryCount + 1);
}
}
@@ -97,7 +108,7 @@ export function DashboardList () {
errorBanner = (
setConsolesError(undefined)}
/>
);
diff --git a/src/components/dashboard/dashboard-wrapper.tsx b/src/components/dashboard/dashboard-wrapper.tsx
index 3927f58..591da13 100644
--- a/src/components/dashboard/dashboard-wrapper.tsx
+++ b/src/components/dashboard/dashboard-wrapper.tsx
@@ -1,4 +1,4 @@
-import { selectConsoleName, selectDashboards, updateConsole, updateDashboard } from 'ops-frontend/store/consoleSlice';
+import { dismissError, handleError, selectConsoleName, selectDashboards, selectError, updateConsole, updateDashboard } from 'ops-frontend/store/consoleSlice';
import React, { ReactNode, useEffect, useState } from 'react';
import { useAppDispatch, useAppSelector } from 'ops-frontend/store/hooks';
import apis from 'ops-frontend/utils/apis';
@@ -9,8 +9,9 @@ import { FullpageLayout } from 'ops-frontend/components/layout/fullpage-layout';
import { SettingsIcon } from '@chakra-ui/icons';
import { useTranslation } from 'react-i18next';
import DashboardSettings from 'ops-frontend/components/dashboard/dashboard-settings';
-import { Dashboard } from '@tinystacks/ops-model';
+import { ApiError, Dashboard } from '@tinystacks/ops-model';
import { useNavigate } from 'react-router-dom';
+import DismissableErrorBanner from 'ops-frontend/components/common/dismissable-error-banner';
export function DashboardWrapper(props: { dashboardContents: ReactNode, dashboardId: string }) {
const { dashboardContents, dashboardId } = props;
@@ -19,6 +20,7 @@ export function DashboardWrapper(props: { dashboardContents: ReactNode, dashboar
const consoleName = useAppSelector(selectConsoleName);
const [retryCount, setRetryCount] = useState(0);
const { t } = useTranslation('dashboard');
+ const error = useAppSelector(selectError);
const dispatch = useAppDispatch();
@@ -38,6 +40,13 @@ export function DashboardWrapper(props: { dashboardContents: ReactNode, dashboar
}
} catch (e) {
setRetryCount(retryCount + 1);
+ if (retryCount >= 2) {
+ const error = (e as any).body as ApiError;
+ dispatch(handleError({
+ title: 'Failed to fetch console!',
+ error: error?.body || error
+ }));
+ }
}
}
@@ -90,11 +99,23 @@ export function DashboardWrapper(props: { dashboardContents: ReactNode, dashboar
)
}
+ let errorBanner = (<>>);
+ if (error) {
+ errorBanner = (
+ dispatch(dismissError())}
+ />
+ );
+ }
+
return (
<>
{renderHeader()}
+ {errorBanner}
{content}
diff --git a/src/components/dashboard/dashboard.tsx b/src/components/dashboard/dashboard.tsx
index f9b2514..557e5ca 100644
--- a/src/components/dashboard/dashboard.tsx
+++ b/src/components/dashboard/dashboard.tsx
@@ -8,7 +8,7 @@ import { useRouter } from 'next/router'
import { DashboardWrapper } from 'ops-frontend/components/dashboard/dashboard-wrapper';
import {
selectConsoleName, selectConsoleWidgets, selectDependencies, selectHydratedWidgets, selectDashboard,
- selectDashboardIdFromRoute, selectDashboardWidgets, updateHydratedWidget
+ selectDashboardIdFromRoute, selectDashboardWidgets, updateHydratedWidget, handleError
} from 'ops-frontend/store/consoleSlice';
import { useAppSelector } from 'ops-frontend/store/hooks';
import { useTranslation } from 'react-i18next';
@@ -16,7 +16,8 @@ import { useEffect, useRef } from 'react';
import { useAppDispatch } from 'ops-frontend/store/hooks';
import { AppDispatch } from 'ops-frontend/store/store';
import { FullpageLayout } from 'ops-frontend/components/layout/fullpage-layout';
-import { Parameter, Widget, TinyStacksError } from '@tinystacks/ops-model';
+import { Parameter, Widget, TinyStacksError as TinyStacksErrorType } from '@tinystacks/ops-model';
+import { TinyStacksError } from '@tinystacks/ops-core';
import { FlatMap, Json, WidgetMap } from 'ops-frontend/types';
import ErrorWidget from 'ops-frontend/widgets/error-widget';
import LoadingWidget from 'ops-frontend/widgets/loading-widget';
@@ -73,18 +74,25 @@ function Dashboard() {
useEffect(() => {
async function importAndRenderWidgets() {
- const deepRenderedWidgets = { ...renderedWidgets };
- for (let widget of dashboardWidgets) {
- deepRenderedWidgets[widget.id || ''] = await renderWidgetAndChildren(
- widget,
- hydratedWidgets,
- dependencies,
- dashboardId,
- parameters
- );
+ try {
+ const deepRenderedWidgets = { ...renderedWidgets };
+ for (let widget of dashboardWidgets) {
+ deepRenderedWidgets[widget.id || ''] = await renderWidgetAndChildren(
+ widget,
+ hydratedWidgets,
+ dependencies,
+ dashboardId,
+ parameters
+ );
+ }
+
+ setRenderedWidgets(deepRenderedWidgets);
+ } catch (error: any) {
+ dispatch(handleError({
+ title: 'Could not render widgets!',
+ error: error?.body || error
+ }));
}
-
- setRenderedWidgets(deepRenderedWidgets);
}
// TODO: deep compare widget trees that are rendered on this dashboard instead
@@ -252,7 +260,7 @@ async function renderWidget(
{
...widget,
originalType: widget.type,
- error: (widget as TinyStacksError).message || ''
+ error: (widget as TinyStacksErrorType).message || ''
}
)
} else if (widget.type === 'LoadingWidget') {
@@ -266,6 +274,13 @@ async function renderWidget(
const moduleName = dependencies[widget.type];
const moduleNamespace = camelCase(moduleName);
const plugin = (plugins as any)[moduleNamespace] as any;
+ if (!plugin) {
+ throw TinyStacksError.fromJson({
+ message: 'Missing dependency!',
+ status: 424,
+ cause: `Cannot find module ${moduleName} for widget type ${widget.type} used in ${widget.id}.`
+ }).toJson();
+ }
hydratedWidget = plugin[widget.type].fromJson(widget);
}
diff --git a/src/i18n/en/home.ts b/src/i18n/en/home.ts
index fa2b24d..f958f84 100644
--- a/src/i18n/en/home.ts
+++ b/src/i18n/en/home.ts
@@ -4,7 +4,7 @@ const home: LocaleMessageType = {
title: 'Ops Console',
dashboards: 'All Dashboards',
addDashboard: 'Add Dashboard',
- consolesError: 'Error parsing console'
+ consolesError: 'Error fetching consoles!'
};
export default home;
diff --git a/src/store/consoleSlice.ts b/src/store/consoleSlice.ts
index 17ddb97..5cb7cae 100644
--- a/src/store/consoleSlice.ts
+++ b/src/store/consoleSlice.ts
@@ -1,5 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { ApiError, Console, Dashboard, TinyStacksError, Widget } from '@tinystacks/ops-model';
+import { TinyStacksError } from '@tinystacks/ops-core';
+import { ApiError, Console, Dashboard, Widget } from '@tinystacks/ops-model';
import { AppDispatch, RootState } from 'ops-frontend/store/store';
import { ShowableError, WidgetMap } from 'ops-frontend/types';
import apis from 'ops-frontend/utils/apis';
@@ -15,7 +16,7 @@ export const createNewDashboard = (consoleName: string, dashboard: Dashboard) =>
await dispatch(removeTempDashboard(dashboard));
return dispatch(handleError({
title: 'Failed to create dashboard!',
- message: error?.body?.message || error?.message
+ error: error?.body || error
}));
}
}
@@ -29,10 +30,10 @@ export const fetchConsoles = (consoleName?: string) => async (dispatch: AppDispa
consoles.at(0);
return dispatch(updateConsole(console || {} as Console));
} catch (e) {
- const error = e as TinyStacksError;
+ const error = (e as any).body as ApiError;
return handleError({
- title: 'Failed to create dashboard!',
- message: error.message || ''
+ title: 'Failed to fetch consoles!',
+ error: error?.body || error
});
}
}
@@ -46,7 +47,7 @@ export const updateDashboard = (consoleName: string, dashboard: Dashboard, dashb
const error = (e as any).body as ApiError;
return dispatch(handleError({
title: 'Failed to update dashboard!',
- message: error?.body?.message || error?.message
+ error: error?.body || error
}));
}
}
@@ -59,7 +60,7 @@ export const fetchDashboards = (consoleName: string) => async (dispatch: AppDisp
const error = (e as any).body as ApiError;
return dispatch(handleError({
title: 'Failed to fetch dashboards!',
- message: error?.body?.message || error?.message
+ error: error?.body
}));
}
}
@@ -144,8 +145,18 @@ export const consoleSlice = createSlice({
overrides: widgetOverrides
}
},
- handleError: function (state: ConsoleSliceState, action: PayloadAction) {
- state.error = action.payload;
+ handleError: function (state: ConsoleSliceState, action: PayloadAction<{ title: string; error: any }>) {
+ if (TinyStacksError.isTinyStacksError(action.payload.error)) {
+ state.error = {
+ title: action.payload.title,
+ ...action.payload.error
+ };
+ } else {
+ state.error = {
+ title: action.payload.title,
+ message: action.payload.error.message
+ };
+ }
},
dismissError: function (state: ConsoleSliceState) {
state.error = undefined;
@@ -242,7 +253,7 @@ export function selectWidget(widgetId: string) {
}
}
-export function selectErropr(state: RootState): ShowableError | undefined {
+export function selectError(state: RootState): ShowableError | undefined {
return state.console.error;
}
diff --git a/src/types.ts b/src/types.ts
index 8f8ee10..4f9d429 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,4 +1,4 @@
-import { Widget } from '@tinystacks/ops-model';
+import { TinyStacksError, Widget } from '@tinystacks/ops-model';
export type WidgetMap = { [id: string]: Widget };
export type FlatMap = { [id: string]: string };
@@ -10,7 +10,6 @@ export type GetWidgetArguments = {
dashboardId?: string;
parameters?: Json;
};
-export type ShowableError = {
+export type ShowableError = Omit & {
title: string;
- message: string;
}
\ No newline at end of file
diff --git a/src/utils/handle-response.ts b/src/utils/handle-response.ts
index 7d8d547..4854aa9 100644
--- a/src/utils/handle-response.ts
+++ b/src/utils/handle-response.ts
@@ -1,11 +1,12 @@
+import { TinyStacksError } from '@tinystacks/ops-core';
import type { NextApiResponse } from 'next'
-import { isTinyStacksError } from 'ops-frontend/utils/is-tinystacks-error';
+
export function handleResponse (
clientResponse: T,
res: NextApiResponse
) {
- if (isTinyStacksError(clientResponse)) {
+ if (TinyStacksError.isTinyStacksError(clientResponse)) {
throw clientResponse;
}
res.status(200).send(clientResponse)
diff --git a/src/utils/is-tinystacks-error.ts b/src/utils/is-tinystacks-error.ts
deleted file mode 100644
index a6cadbc..0000000
--- a/src/utils/is-tinystacks-error.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { TinyStacksError } from '@tinystacks/ops-model';
-
-const TinyStacksErrorName = 'TinyStacksError';
-
-export function isTinyStacksError (error: unknown): boolean {
- const e = error as any;
- const hasTinyStacksErrorName: boolean = e?.name && e?.name === TinyStacksErrorName;
- const hasTinyStacksErrorType: boolean = e?.type && Object.values(TinyStacksError.type).includes(e?.type);
- const hasMessage: boolean = e?.message && typeof e?.message === 'string';
- const hasStatus: boolean = e?.status && typeof e?.status === 'number';
- const isTsError: boolean = (
- hasTinyStacksErrorName &&
- hasTinyStacksErrorType &&
- hasMessage &&
- hasStatus
- );
- return isTsError;
-}
\ No newline at end of file