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 @@ -16,9 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
import "@testing-library/jest-dom";
import "@testing-library/jest-dom/vitest";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";

import { Wrapper } from "src/utils/Wrapper";

Expand Down Expand Up @@ -50,15 +50,22 @@ const buildRun = (overrides: Record<string, unknown>) => ({
});

let mockData: { dag_runs: Array<ReturnType<typeof buildRun>> } | undefined;
const mockIsLoading = false;
// Served from cache on a reopen: data is present without any fetch having happened since mount.
let mockFetchedAfterMount = true;

vi.mock("openapi/queries", () => ({
useDagRunServiceGetDagRuns: vi.fn(() => ({
data: mockData,
isLoading: mockIsLoading,
isFetchedAfterMount: mockFetchedAfterMount,
isFetching: false,
isSuccess: mockData !== undefined,
})),
}));

beforeEach(() => {
mockFetchedAfterMount = true;
});

// Select.Content is portalled to document.body, outside the render container.
const getItems = () => document.body.querySelectorAll(".chakra-select__item");

Expand Down Expand Up @@ -101,6 +108,30 @@ describe("RecentConfigSelect", () => {
expect(container).toBeEmptyDOMElement();
});

it("renders nothing while the runs are still loading", () => {
mockFetchedAfterMount = false;
mockData = undefined;

const { container } = render(<RecentConfigSelect dagId="test_dag" onSelectConf={vi.fn()} open />, {
wrapper: Wrapper,
});

expect(container).toBeEmptyDOMElement();
});

// Runs stay fresh for five minutes, so reopening the modal inside that window serves them from
// cache and nothing is fetched after this mount. The configs are still there to offer.
it("still offers configs when the runs came from cache", () => {
mockFetchedAfterMount = false;
mockData = {
dag_runs: [buildRun({ conf: { message: "From cache" }, dag_run_id: "run_cached" })],
};

render(<RecentConfigSelect dagId="test_dag" onSelectConf={vi.fn()} open />, { wrapper: Wrapper });

expect(getItems()).toHaveLength(1);
});

it("renders all distinct confs without capping the list", () => {
mockData = {
dag_runs: Array.from({ length: 8 }, (_unused, index) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
*/
import { useMemo, useState } from "react";

import { createListCollection, Flex, type SelectValueChangeDetails, Text } from "@chakra-ui/react";
import {
createListCollection,
Field,
Flex,
type SelectValueChangeDetails,
Stack,
Text,
} from "@chakra-ui/react";
import { useTranslation } from "react-i18next";

import { useDagRunServiceGetDagRuns } from "openapi/queries";
Expand All @@ -42,11 +49,9 @@ type RecentConfigSelectProps = {
const RecentConfigSelect = ({ dagId, onSelectConf, open }: RecentConfigSelectProps) => {
const { t: translate } = useTranslation("components");
const [selectedValue, setSelectedValue] = useState<Array<string>>([]);
const { data, isLoading } = useDagRunServiceGetDagRuns(
{ dagId, limit: 25, orderBy: ["-run_after"] },
undefined,
{ enabled: open },
);
const { data } = useDagRunServiceGetDagRuns({ dagId, limit: 25, orderBy: ["-run_after"] }, undefined, {
enabled: open,
});

const options = useMemo(() => {
const seenConfs = new Set<string>();
Expand Down Expand Up @@ -79,34 +84,48 @@ const RecentConfigSelect = ({ dagId, onSelectConf, open }: RecentConfigSelectPro
}
};

if (!isLoading && options.length === 0) {
// The options come from the runs alone, so there is nothing to show both before they arrive
// and when none of them carried a config. Asking the query how far along it is would be worse
// than useless here: the runs stay fresh for five minutes, so reopening the modal serves them
// from cache without fetching, and a readiness flag would read that as "not ready yet" and
// hide configs that are already in hand.
if (options.length === 0) {
return undefined;
}

// Laid out as a form row like Logical Date and the run parameters below it, rather than a
// label stacked over a full-width control.
return (
<Select.Root
collection={recentConfigOptions}
data-testid="recent-config-select"
disabled={isLoading || options.length === 0}
onValueChange={handleValueChange}
size="sm"
value={selectedValue}
>
<Select.Label fontSize="xs">{translate("triggerDag.recentConfig")}</Select.Label>
<Select.Trigger>
<Select.ValueText placeholder={translate("triggerDag.recentConfigPlaceholder")} />
</Select.Trigger>
<Select.Content maxH="200px" overflowY="auto">
{recentConfigOptions.items.map((option) => (
<Select.Item item={option} key={option.run.dag_run_id}>
<Flex justifyContent="space-between" width="100%">
<Text>{option.run.dag_run_id}</Text>
<Time datetime={option.run.run_after} />
</Flex>
</Select.Item>
))}
</Select.Content>
</Select.Root>
<Field.Root orientation="horizontal">
<Stack>
<Field.Label fontSize="md" style={{ flexBasis: "30%" }}>
{translate("triggerDag.recentConfig")}
</Field.Label>
</Stack>
<Stack css={{ flexBasis: "70%" }}>
<Select.Root
collection={recentConfigOptions}
data-testid="recent-config-select"
onValueChange={handleValueChange}
size="sm"
value={selectedValue}
>
<Select.Trigger>
<Select.ValueText placeholder={translate("triggerDag.recentConfigPlaceholder")} />
</Select.Trigger>
<Select.Content maxH="200px" overflowY="auto">
{recentConfigOptions.items.map((option) => (
<Select.Item item={option} key={option.run.dag_run_id}>
<Flex justifyContent="space-between" width="100%">
<Text>{option.run.dag_run_id}</Text>
<Time datetime={option.run.run_after} />
</Flex>
</Select.Item>
))}
</Select.Content>
</Select.Root>
</Stack>
</Field.Root>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ vi.mock("openapi/queries", async (importOriginal) => {
},
],
},
isLoading: false,
isFetchedAfterMount: true,
isFetching: false,
isSuccess: true,
})),
};
});
Expand Down
Loading