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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { Optional } from "@databiosphere/findable-ui/lib/components/Stepper/comp
import { getGeneModelLabel } from "./utils";
import { BUTTON_PROPS } from "@databiosphere/findable-ui/lib/components/common/Button/constants";
import { STEP } from "./step";
import { StepError } from "../components/StepError/stepError";
import { getStepActiveState, getButtonDisabledState } from "../utils/stepUtils";

export const GTFStep = ({
active,
Expand All @@ -36,7 +38,7 @@ export const GTFStep = ({
onContinue,
onEdit,
}: StepProps): JSX.Element => {
const { geneModelUrls } = useUCSCFiles(genome);
const { error, geneModelUrls, isLoading } = useUCSCFiles(genome);
const { controls, onChange, onValueChange, value } =
useRadioGroup(geneModelUrls);

Expand All @@ -46,16 +48,13 @@ export const GTFStep = ({

return (
<Step
active={active && !!geneModelUrls}
active={getStepActiveState(active, isLoading)}
completed={completed}
index={index}
>
{/* Step component `children` should be subcomponents such as `StepLabel`, `StepContent`. */}
{/* We ignore this; the loading UI is in the DOM while `geneModelUrls` is `undefined` and the Step is not `active`. */}
<Loading
loading={geneModelUrls === undefined}
panelStyle={LOADING_PANEL_STYLE.INHERIT}
/>
<Loading loading={isLoading} panelStyle={LOADING_PANEL_STYLE.INHERIT} />
<StepLabel
optional={
completed && (
Expand All @@ -77,7 +76,8 @@ export const GTFStep = ({
>
Genes and Gene Predictions
</Typography>
{controls.length > 0 ? (
<StepError error={error} />
{!error && controls.length > 0 ? (
<RadioGroup onChange={onChange} value={value}>
{controls.map(({ label, value }, i) => (
<FormControlLabel
Expand All @@ -90,13 +90,15 @@ export const GTFStep = ({
))}
</RadioGroup>
) : (
<Typography variant={TYPOGRAPHY_PROPS.VARIANT.TEXT_BODY_400}>
No gene models found.
</Typography>
!error && (
<Typography variant={TYPOGRAPHY_PROPS.VARIANT.TEXT_BODY_400}>
No gene models found.
</Typography>
)
)}
<Button
{...BUTTON_PROPS.PRIMARY_CONTAINED}
disabled={!value}
disabled={getButtonDisabledState(!value, false, !!error)}
Copy link
Preview

Copilot AI Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The !!error conversion is redundant since getButtonDisabledState already handles the truthy conversion with !!hasError internally.

Suggested change
disabled={getButtonDisabledState(!value, false, !!error)}
disabled={getButtonDisabledState(!value, false, error)}

Copilot uses AI. Check for mistakes.

onClick={() => onContinue()}
>
Continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,39 @@ import { UseUCSCFiles } from "./types";
export const useUCSCFiles = (genome: BRCDataCatalogGenome): UseUCSCFiles => {
const assemblyId = genome.accession;
const [geneModelUrls, setGeneModelUrls] = useState<string[] | undefined>();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (!assemblyId) {
setError("Assembly ID is required");
return;
}

setIsLoading(true);
setError(null);
setGeneModelUrls(undefined);

fetch(`${UCSC_FILES_ENDPOINT}?genome=${assemblyId}`)
.then((res) => res.json())
.then(async (res) => {
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return res.json();
})
.then(parseUCSCFilesResult)
.then((urls) => setGeneModelUrls(urls))
.then((urls) => {
setGeneModelUrls(urls);
setIsLoading(false);
})
.catch((e) => {
throw new Error("Failed to fetch UCSC files", { cause: e });
const errorMessage =
e instanceof Error ? e.message : "Failed to fetch UCSC files";
setError(errorMessage);
setIsLoading(false);
setGeneModelUrls(undefined);
});
}, [assemblyId]);

return { geneModelUrls };
return { error, geneModelUrls, isLoading };
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ export interface UrlList {
}

export interface UseUCSCFiles {
error: string | null;
geneModelUrls: string[] | undefined;
isLoading: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { StepLabel } from "@databiosphere/findable-ui/lib/components/Stepper/com
import { StepProps } from "../types";
import { Button } from "@mui/material";
import { BUTTON_PROPS } from "@databiosphere/findable-ui/lib/components/common/Button/constants";
import { StepError } from "../components/StepError/stepError";
import { getStepActiveState, getButtonDisabledState } from "../utils/stepUtils";

export const LaunchStep = ({
active,
Expand All @@ -14,12 +16,21 @@ export const LaunchStep = ({
status,
}: StepProps): JSX.Element => {
return (
<Step active={active} completed={completed} index={index}>
<Step
active={getStepActiveState(active, status.loading)}
completed={completed}
index={index}
>
<StepLabel>{entryLabel}</StepLabel>
<StepContent>
<StepError error={status.error} />
<Button
{...BUTTON_PROPS.PRIMARY_CONTAINED}
disabled={status.disabled || status.loading}
disabled={getButtonDisabledState(
status.disabled,
status.loading,
!!status.error
Copy link
Preview

Copilot AI Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The !!status.error conversion is redundant since getButtonDisabledState already handles the truthy conversion with !!hasError internally.

Suggested change
!!status.error
status.error

Copilot uses AI. Check for mistakes.

)}
onClick={onLaunchGalaxy}
>
Launch In Galaxy
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Typography } from "@mui/material";
import { TYPOGRAPHY_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/typography";

export interface StepErrorProps {
error: string | null;
}

export const StepError = ({ error }: StepErrorProps): JSX.Element | null => {
if (!error) return null;

return (
<Typography variant={TYPOGRAPHY_PROPS.VARIANT.TEXT_BODY_400} color="error">
Error: {error}
</Typography>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface ConfiguredValue {

export interface Status {
disabled: boolean;
error: string | null;
loading: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ export const useLaunchGalaxy = ({
configuredInput,
workflow,
}: Props): UseLaunchGalaxy => {
const { data: landingUrl, isLoading: loading, run } = useAsync<string>();
const {
data: landingUrl,
error,
isLoading: loading,
run,
} = useAsync<string>();
const configuredValue = getConfiguredValues(configuredInput, workflow);
const disabled = !configuredValue;

Expand Down Expand Up @@ -40,5 +45,10 @@ export const useLaunchGalaxy = ({
);
}, [landingUrl]);

return { onLaunchGalaxy, status: { disabled, loading } };
let errorMessage: string | null = null;
if (error) {
errorMessage = (error as Error).message || "Failed to launch Galaxy";
}

return { onLaunchGalaxy, status: { disabled, error: errorMessage, loading } };
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export interface StepConfig {

export interface StepProps
extends Pick<StepConfig, "description" | "disabled">,
Pick<MStepProps, "active" | "completed" | "last">,
Required<Pick<MStepProps, "index">> {
Pick<MStepProps, "completed" | "last">,
Required<Pick<MStepProps, "index" | "active">> {
entryLabel: string;
genome: BRCDataCatalogGenome;
onConfigure: OnConfigure;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Determines if a step should be active based on loading and error states
* @param active - Whether the step should be active
* @param isLoading - Whether the step is currently loading
* @returns Whether the step should be active
*/
export const getStepActiveState = (
active: boolean,
isLoading: boolean
): boolean => {
return active && !isLoading;
};

/**
* Determines if a button should be disabled based on various states
* @param baseDisabled - Base disabled state
* @param isLoading - Whether currently loading
* @param hasError - Whether there is an error
* @returns Whether the button should be disabled
*/
export const getButtonDisabledState = (
baseDisabled: boolean,
isLoading: boolean,
hasError?: boolean
): boolean => {
return baseDisabled || isLoading || !!hasError;
};
Loading