Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions dashboard/src/api/apiMethods/fetchApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,40 @@ import { globalSessionData } from "../../utils/Enum";
import { toast } from "react-toastify";
import { serverErrorHandler } from "@utils/Utils";

/** Keep 403 toasts readable (Atlas authorization message). */
const FORBIDDEN_ERROR_TOAST_MS = 5_000;
/**
* Callers often `toast.dismiss(ref.current)` in catch when `ref.current` is null;
* react-toastify then clears all toasts and removes the 403 toast we just showed.
* Queue the toast as a macrotask so it runs after that dismiss.
*/
const FETCH_API_FORBIDDEN_TOAST_ID = "fetch-api-http-403";

const showForbiddenToastLater = (
responseData: unknown,
defaultMessage: string
) => {
setTimeout(() => {
let message = defaultMessage;
if (responseData && typeof responseData === "object") {
const d = responseData as {
errorMessage?: unknown;
message?: unknown;
error?: unknown;
};
message =
(d.errorMessage as string | undefined) ||
(d.message as string | undefined) ||
(d.error as string | undefined) ||
message;
}
toast.error(message, {
toastId: FETCH_API_FORBIDDEN_TOAST_ID,
autoClose: FORBIDDEN_ERROR_TOAST_MS
});
}, 0);
};

let prevNetworkErrorTime = 0;

function errorHandelingForAbortAndStatus0() {
Expand Down Expand Up @@ -61,11 +95,14 @@ const fetchApi = async (url: string, config: AxiosRequestConfig) => {
window.location.replace("login.jsp");
break;
case 403:
serverErrorHandler(
{ responseJSON: error.response?.data },
// Match classic UI: toast only, no redirect. Defer toast so callers
// that dismiss all toasts in catch (e.g. toast.dismiss(null ref)) do
// not remove this notification before it is shown — see
// showForbiddenToastLater.
showForbiddenToastLater(
error.response?.data,
"You are not authorized"
);
window.location.replace("login.jsp");
break;
case 404:
serverErrorHandler(
Expand Down Expand Up @@ -99,7 +136,13 @@ const fetchApi = async (url: string, config: AxiosRequestConfig) => {
break;
}
}
if (error.response?.statusText != "abort") {
// Only treat as offline / connection failure when there is no HTTP
// response (or status 0). Do not run this for 403/404/5xx — those are
// handled above and would wrongly show a network toast.
const res = error.response;
const isAbort =
error.code === "ERR_CANCELED" || res?.statusText === "abort";
if (!isAbort && (!res || Number(res.status) === 0)) {
errorHandelingForAbortAndStatus0();
}
}
Expand Down
4 changes: 4 additions & 0 deletions dashboard/src/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ const getEntityIconPath = (options: any) => {
};

const serverError = (error: any, toastId: any) => {
// fetchApi already surfaces 403 via serverErrorHandler (toast); avoid duplicate.
if (error?.response?.status === 403) {
return;
}
if (
error.response !== undefined &&
error.response.data.errorMessage !== undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
FormControlLabel,
Checkbox,
Autocomplete,
Chip,
Tooltip,
tooltipClasses,
TooltipProps,
Expand Down Expand Up @@ -235,6 +236,18 @@ const BusinessMetadataAttributeForm = ({
: [];

let enumTypeOptions = [...selectedEnumValues];
const isBmAttributeEdit = !isEmpty(editbmAttribute);
const nonRemovableApplicableTypes =
isBmAttributeEdit &&
Array.isArray(field?.options?.applicableEntityTypes)
? new Set(
field.options.applicableEntityTypes.filter(
(t: unknown): t is string =>
typeof t === "string" && t.length > 0
)
)
: null;

return (
<>
<fieldset
Expand Down Expand Up @@ -832,6 +845,29 @@ const BusinessMetadataAttributeForm = ({
: []
}
className="advanced-search-autocomplete"
renderTags={
nonRemovableApplicableTypes
? (tagValue, getTagProps) =>
tagValue.map((option: string, tagIndex) => {
const tagProps = getTagProps({
index: tagIndex
});
const stripDelete =
nonRemovableApplicableTypes.has(option);
return (
<Chip
{...tagProps}
label={option}
onDelete={
stripDelete
? undefined
: tagProps.onDelete
}
/>
);
})
: undefined
}
renderInput={(params) => (
<TextField
{...params}
Expand Down
28 changes: 16 additions & 12 deletions dashboard/src/views/SideBar/Import/ImportLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useEffect, useRef, useState } from "react";
import { useDropzone } from "react-dropzone";
import { Typography, LinearProgress, Stack } from "@mui/material";
import { toast } from "react-toastify";
import { CustomButton } from "@components/muiComponents";
import { CustomButton, LightTooltip } from "@components/muiComponents";

const thumb = {
position: "relative",
Expand Down Expand Up @@ -140,17 +140,21 @@ const ImportLayout = ({
color="inherit"
sx={progressCss}
/>
<Typography
sx={{
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
lineHeight: "2",
width: "100%"
}}
>
{file.name}
</Typography>
<LightTooltip title={file.name} placement="top" arrow>
<Typography
component="span"
sx={{
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
lineHeight: "2",
width: "100%",
display: "block"
}}
>
{file.name}
</Typography>
</LightTooltip>
</Stack>
</Stack>
<CustomButton
Expand Down