Skip to content

Commit

Permalink
feat: Display error when trying to retrieve models info
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimir-cucu committed Jan 22, 2024
1 parent 5e8ef26 commit b8ccb8f
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 38 deletions.
83 changes: 47 additions & 36 deletions src/juju/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,7 @@ export async function fetchModelStatus(
// a location to notify the user. In the new watcher model that's
// being implemented we will be able to surface this error in the
// model details page.
console.error("Unable to fetch the status.", status);
return;
throw new Error(`Unable to fetch the status. ${status}`);
}
}

Expand All @@ -284,7 +283,8 @@ export async function fetchModelStatus(
}
logout();
} catch (error) {
console.error("error connecting to model:", modelUUID, error);
console.error("Error connecting to model:", modelUUID, error);
throw error;
}
}
return status;
Expand Down Expand Up @@ -344,49 +344,60 @@ export async function fetchAllModelStatuses(
getState: () => RootState,
) {
const queue = new Limiter({ concurrency: 1 });
let modelErrorCount = 0;
modelUUIDList.forEach((modelUUID) => {
queue.push(async (done) => {
if (isLoggedIn(getState(), wsControllerURL)) {
const modelWsControllerURL = getModelByUUID(
getState(),
modelUUID,
)?.wsControllerURL;
if (modelWsControllerURL) {
await fetchAndStoreModelStatus(
try {
const modelWsControllerURL = getModelByUUID(
getState(),
modelUUID,
modelWsControllerURL,
dispatch,
getState,
);
}
const modelInfo = await fetchModelInfo(conn, modelUUID);
if (modelInfo) {
dispatch(
jujuActions.updateModelInfo({
modelInfo,
wsControllerURL,
}),
);
}
if (modelInfo?.results[0].result?.["is-controller"]) {
// If this is a controller model then update the
// controller data with this model data.
dispatch(
addControllerCloudRegion({ wsControllerURL, modelInfo }),
).catch((error) =>
console.error(
"Error when trying to add controller cloud and region data.",
error,
),
);
)?.wsControllerURL;
if (modelWsControllerURL) {
await fetchAndStoreModelStatus(
modelUUID,
modelWsControllerURL,
dispatch,
getState,
);
}
const modelInfo = await fetchModelInfo(conn, modelUUID);
if (modelInfo) {
dispatch(
jujuActions.updateModelInfo({
modelInfo,
wsControllerURL,
}),
);
}
if (modelInfo?.results[0].result?.["is-controller"]) {
// If this is a controller model then update the
// controller data with this model data.
dispatch(
addControllerCloudRegion({ wsControllerURL, modelInfo }),
).catch((error) =>
console.error(
"Error when trying to add controller cloud and region data.",
error,
),
);
}
} catch (error) {
modelErrorCount++;
}
}
done();
});
});
return new Promise<void>((resolve) => {
// If errors appear in more than a certain number of models, a rejected
// promise should be returned and further handled in modelPollerMiddleware().
const shouldDisplayError =
modelErrorCount >= Math.max(5, 0.1 * modelUUIDList.length);
return new Promise<void>((resolve, reject) => {
queue.onDone(() => {
resolve();
shouldDisplayError
? reject(new Error("Unable to update some model data."))
: resolve();
});
});
}
Expand Down
17 changes: 16 additions & 1 deletion src/pages/ModelsIndex/ModelsIndex.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { SearchAndFilter } from "@canonical/react-components";
import {
Button,
Notification,
SearchAndFilter,
} from "@canonical/react-components";
import type { SearchAndFilterChip } from "@canonical/react-components/dist/components/SearchAndFilter/types";
import type { ReactNode } from "react";
import { useSelector } from "react-redux";
Expand All @@ -16,6 +20,7 @@ import {
getGroupedModelStatusCounts,
getModelData,
getModelListLoaded,
getModelsError,
hasModels,
} from "store/juju/selectors";
import { pluralize } from "store/juju/utils/models";
Expand Down Expand Up @@ -55,6 +60,7 @@ export default function Models() {
custom: queryParams.custom,
};

const modelsError = useAppSelector(getModelsError);
const modelsLoaded = useAppSelector(getModelListLoaded);
const hasSomeModels = useSelector(hasModels);
// loop model data and pull out filter panel data
Expand Down Expand Up @@ -196,6 +202,15 @@ export default function Models() {
titleComponent="div"
titleClassName="u-no-max-width u-full-width"
>
{modelsError ? (
<Notification severity="negative" title="Error">
{modelsError} Try{" "}
<Button appearance="link" onClick={() => window.location.reload()}>
refreshing
</Button>{" "}
the page.
</Notification>
) : null}
{content}
</BaseLayout>
);
Expand Down
5 changes: 5 additions & 0 deletions src/store/juju/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ export const getModelData = createSelector(
(sliceState) => sliceState.modelData ?? null,
);

export const getModelsError = createSelector(
[slice],
(sliceState) => sliceState.modelsError,
);

/**
Fetches the controller data from state.
@param state The application state.
Expand Down
12 changes: 12 additions & 0 deletions src/store/juju/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const slice = createSlice({
},
controllers: null,
models: {},
modelsError: null,
modelsLoaded: false,
modelData: {},
modelWatcherData: {},
Expand Down Expand Up @@ -128,9 +129,20 @@ const slice = createSlice({
state.modelData[modelInfo.uuid].info = modelInfo;
}
},
updateModelsError: (
state,
action: PayloadAction<
{
modelsError: string | null;
} & WsControllerURLParam
>,
) => {
state.modelsError = action.payload.modelsError;
},
clearModelData: (state) => {
state.modelData = {};
state.models = {};
state.modelsError = null;
state.modelsLoaded = false;
},
clearControllerData: (state) => {
Expand Down
1 change: 1 addition & 0 deletions src/store/juju/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export type JujuState = {
crossModelQuery: CrossModelQueryState;
controllers: Controllers | null;
models: ModelsList;
modelsError: string | null;
modelsLoaded: boolean;
modelData: ModelDataList;
modelWatcherData?: ModelWatcherData;
Expand Down
26 changes: 25 additions & 1 deletion src/store/middleware/model-poller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export const modelPollerMiddleware: Middleware<
}

let pollCount = 0;
let isFirstTimePolling = true;
do {
const identity = conn?.info?.user?.identity;
if (identity) {
Expand All @@ -207,11 +208,34 @@ export const modelPollerMiddleware: Middleware<
reduxStore.dispatch,
reduxStore.getState,
);
// If the code execution arrives here, then the model statuses
// have been succesfully updated. Models error should be removed.
if (reduxStore.getState().juju.modelsError) {
reduxStore.dispatch(
jujuActions.updateModelsError({
modelsError: null,
wsControllerURL,
}),
);
}
} catch (e) {
console.log(e);
console.log("something");
const errorMessage = isFirstTimePolling
? "Unable to fetch the models."
: "Unable to update the models.";
console.error(errorMessage, e);
reduxStore.dispatch(
jujuActions.updateModelsError({
modelsError: errorMessage,
wsControllerURL,
}),
);
}
}

if (isFirstTimePolling) {
isFirstTimePolling = false;
}
// Allow the polling to run a certain number of times in tests.
if (process.env.NODE_ENV === "test") {
if (pollCount === action.payload.poll) {
Expand Down
1 change: 1 addition & 0 deletions src/testing/factories/juju/juju.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ export const jujuStateFactory = Factory.define<JujuState>(() => ({
controllers: null,
models: {},
modelsLoaded: false,
modelsError: null,
modelData: {},
modelWatcherData: {},
charms: [],
Expand Down

0 comments on commit b8ccb8f

Please sign in to comment.