Skip to content

Commit

Permalink
🪟 🎉 Connection job list pagination (airbytehq#15938)
Browse files Browse the repository at this point in the history
* add load more jobs button

* add pagination to query key

* add keepPreviousData: true to prevent loading spinner when fetching new data

* show loading spinner when loading more jobs

* set page size increment back to 25

* extend input html attributes instead of adding className explicitly

* use LoadingButton instead of inserting a spinner conditionally into the button

* use suspense: true

* get rid of unnecessary undefined

* remove footer height and hide if no more jobs to load
  • Loading branch information
lmossman authored and robbinhan committed Sep 29, 2022
1 parent c7c50f0 commit 0fa284d
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 7 deletions.
1 change: 1 addition & 0 deletions airbyte-webapp/src/core/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const enum Action {
NO_MATCHING_CONNECTOR = "NoMatchingConnector",
SELECTION_OPENED = "SelectionOpened",
CHECKOUT_START = "CheckoutStart",
LOAD_MORE_JOBS = "LoadMoreJobs",
}

export type EventParams = Record<string, unknown>;
1 change: 1 addition & 0 deletions airbyte-webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@
"connection.fromTo": "{source} → {destination}",
"connection.connectionSettings": "Connection settings",
"connection.resetData": "Reset your data",
"connection.loadMoreJobs": "Load more",
"connection.updateSchema": "Refresh source schema",
"connection.updateSchema.formChanged.title": "Unsaved changes",
"connection.updateSchema.formChanged.text": "Your replication settings have unsaved changes. Those will be lost when refreshing the source schema. Save your changes before refreshing the source schema to not lose them.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,11 @@
.contentCard {
margin-bottom: 20px;
}

.footer {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useEffect, useState } from "react";
import { FormattedMessage } from "react-intl";

import { Button, ContentCard } from "components";
import { Button, ContentCard, LoadingButton } from "components";
import { Tooltip } from "components/base/Tooltip";
import EmptyResource from "components/EmptyResourceBlock";
import { RotateIcon } from "components/icons/RotateIcon";

import { getFrequencyType } from "config/utils";
import { Action, Namespace } from "core/analytics";
import { ConnectionStatus, JobWithAttemptsRead, WebBackendConnectionRead } from "core/request/AirbyteClient";
import Status from "core/statuses";
import { useAnalyticsService } from "hooks/services/Analytics";
import { useConfirmationModalService } from "hooks/services/ConfirmationModal";
import { FeatureItem, useFeature } from "hooks/services/Feature";
import { useResetConnection, useSyncConnection } from "hooks/services/useConnectionHook";
Expand All @@ -18,6 +21,8 @@ import { useCancelJob, useListJobs } from "services/job/JobService";
import JobsList from "./JobsList";
import styles from "./StatusView.module.scss";

const JOB_PAGE_SIZE_INCREMENT = 25;

enum ActionType {
RESET = "reset_connection",
SYNC = "sync",
Expand All @@ -43,12 +48,19 @@ const getJobRunningOrPending = (jobs: JobWithAttemptsRead[]) => {

const StatusView: React.FC<StatusViewProps> = ({ connection }) => {
const [activeJob, setActiveJob] = useState<ActiveJob>();
const [jobPageSize, setJobPageSize] = useState(JOB_PAGE_SIZE_INCREMENT);
const analyticsService = useAnalyticsService();

const jobs = useListJobs({
const { jobs, isPreviousData: isJobPageLoading } = useListJobs({
configId: connection.connectionId,
configTypes: ["sync", "reset_connection"],
pagination: {
pageSize: jobPageSize,
},
});

const moreJobPagesAvailable = jobs.length === jobPageSize;

useEffect(() => {
const jobRunningOrPending = getJobRunningOrPending(jobs);

Expand Down Expand Up @@ -102,6 +114,21 @@ const StatusView: React.FC<StatusViewProps> = ({ connection }) => {
return cancelJob(activeJob.id);
};

const onLoadMoreJobs = () => {
setJobPageSize((prevJobPageSize) => prevJobPageSize + JOB_PAGE_SIZE_INCREMENT);

analyticsService.track(Namespace.CONNECTION, Action.LOAD_MORE_JOBS, {
actionDescription: "Load more jobs button was clicked",
connection_id: connection.connectionId,
connector_source: connection.source?.sourceName,
connector_source_definition_id: connection.source?.sourceDefinitionId,
connector_destination: connection.destination?.destinationName,
connector_destination_definition_id: connection.destination?.destinationDefinitionId,
frequency: getFrequencyType(connection.schedule),
job_page_size: jobPageSize,
});
};

const cancelJobBtn = (
<Button className={styles.cancelButton} disabled={!activeJob?.id || activeJob.isCanceling} onClick={onCancelJob}>
<FontAwesomeIcon className={styles.iconXmark} icon={faXmark} />
Expand Down Expand Up @@ -145,6 +172,14 @@ const StatusView: React.FC<StatusViewProps> = ({ connection }) => {
>
{jobs.length ? <JobsList jobs={jobs} /> : <EmptyResource text={<FormattedMessage id="sources.noSync" />} />}
</ContentCard>

{(moreJobPagesAvailable || isJobPageLoading) && (
<footer className={styles.footer}>
<LoadingButton isLoading={isJobPageLoading} onClick={onLoadMoreJobs}>
<FormattedMessage id="connection.loadMoreJobs" />
</LoadingButton>
</footer>
)}
</div>
);
};
Expand Down
20 changes: 15 additions & 5 deletions airbyte-webapp/src/services/job/JobService.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { useMutation, useQueryClient } from "react-query";
import { useMutation, useQuery, useQueryClient } from "react-query";

import { useConfig } from "config";
import { JobsService } from "core/domain/job/JobsService";
import { useDefaultRequestMiddlewares } from "services/useDefaultRequestMiddlewares";
import { useInitService } from "services/useInitService";

import { JobDebugInfoRead, JobInfoRead, JobListRequestBody } from "../../core/request/AirbyteClient";
import {
JobDebugInfoRead,
JobInfoRead,
JobListRequestBody,
JobWithAttemptsRead,
Pagination,
} from "../../core/request/AirbyteClient";
import { useSuspenseQuery } from "../connector/useSuspenseQuery";

export const jobsKeys = {
all: ["jobs"] as const,
lists: () => [...jobsKeys.all, "list"] as const,
list: (filters: string) => [...jobsKeys.lists(), { filters }] as const,
list: (filters: string, pagination?: Pagination) => [...jobsKeys.lists(), { filters, pagination }] as const,
detail: (jobId: number) => [...jobsKeys.all, "details", jobId] as const,
getDebugInfo: (jobId: number) => [...jobsKeys.all, "getDebugInfo", jobId] as const,
cancel: (jobId: string) => [...jobsKeys.all, "cancel", jobId] as const,
Expand All @@ -25,9 +31,13 @@ function useGetJobService() {

export const useListJobs = (listParams: JobListRequestBody) => {
const service = useGetJobService();
return useSuspenseQuery(jobsKeys.list(listParams.configId), () => service.list(listParams), {
const result = useQuery(jobsKeys.list(listParams.configId, listParams.pagination), () => service.list(listParams), {
refetchInterval: 2500, // every 2,5 seconds,
}).jobs;
keepPreviousData: true,
suspense: true,
});
// cast to JobWithAttemptsRead[] because (suspense: true) means we will never get undefined
return { jobs: result.data?.jobs as JobWithAttemptsRead[], isPreviousData: result.isPreviousData };
};

export const useGetJob = (id: number, enabled = true) => {
Expand Down

0 comments on commit 0fa284d

Please sign in to comment.