Skip to content

Commit

Permalink
🪟 🎨 Job History tab improvements (#6172)
Browse files Browse the repository at this point in the history
Co-authored-by: Vladimir <dizel852@gmail.com>
  • Loading branch information
josephkmh and dizel852 committed May 9, 2023
1 parent 76fb142 commit fd1c287
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 230 deletions.
25 changes: 17 additions & 8 deletions airbyte-webapp/src/components/JobItem/JobItem.module.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
@use "../../scss/colors";
@use "scss/colors";
@use "scss/variables";

.logsLoadingContainer {
display: flex;
align-items: center;
justify-content: center;
background: colors.$white;
padding: 6px 0;
min-height: 58px;
.jobItem {
border-bottom: variables.$border-thin solid colors.$grey-100;

&:hover {
background-color: colors.$grey-50;
}

&__loading {
display: flex;
align-items: center;
justify-content: center;
background: colors.$white;
padding: 6px 0;
min-height: 58px;
}
}
21 changes: 5 additions & 16 deletions airbyte-webapp/src/components/JobItem/JobItem.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
import React, { Suspense, useCallback, useRef, useState } from "react";
import styled from "styled-components";

import { Spinner } from "components/ui/Spinner";

import { SynchronousJobRead } from "core/request/AirbyteClient";

import { useAttemptLink } from "./attemptLinkUtils";
import ContentWrapper from "./components/ContentWrapper";
import MainInfo from "./components/MainInfo";
import { JobSummary } from "./components/JobSummary";
import styles from "./JobItem.module.scss";
import { JobsWithJobs } from "./types";
import { didJobSucceed, getJobAttempts, getJobId } from "./utils";

const ErrorDetails = React.lazy(() => import("./components/ErrorDetails"));
const JobLogs = React.lazy(() => import("./components/JobLogs"));

const Item = styled.div<{ isFailed: boolean }>`
border-bottom: 1px solid ${({ theme }) => theme.greyColor20};
font-size: 15px;
line-height: 18px;
&:hover {
background: ${({ theme, isFailed }) => (isFailed ? theme.dangerTransparentColor : theme.greyColor0)};
}
`;

interface JobItemProps {
job: SynchronousJobRead | JobsWithJobs;
}
Expand Down Expand Up @@ -52,13 +41,13 @@ export const JobItem: React.FC<JobItemProps> = ({ job }) => {
}, [job, linkedJobId]);

return (
<Item isFailed={!didSucceed} ref={scrollAnchor}>
<MainInfo isOpen={isOpen} isFailed={!didSucceed} onExpand={onExpand} job={job} attempts={getJobAttempts(job)} />
<div className={styles.jobItem} ref={scrollAnchor}>
<JobSummary isOpen={isOpen} isFailed={!didSucceed} onExpand={onExpand} job={job} attempts={getJobAttempts(job)} />
<ContentWrapper isOpen={isOpen} onToggled={onDetailsToggled}>
<div>
<Suspense
fallback={
<div className={styles.logsLoadingContainer}>
<div className={styles.jobItem__loading}>
<Spinner size="sm" />
</div>
}
Expand All @@ -72,6 +61,6 @@ export const JobItem: React.FC<JobItemProps> = ({ job }) => {
</Suspense>
</div>
</ContentWrapper>
</Item>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
@use "scss/colors";
@use "scss/variables";

.container {
font-size: variables.$font-size-sm;
line-height: 1.2;
color: colors.$grey;
}

.details > *:not(:last-child, .lastAttempt)::after {
content: "|";
padding: 0 variables.$spacing-sm;
}

.failedMessage {
color: colors.$red-300;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.lastAttempt {
margin-right: variables.$spacing-sm;

&.failed {
color: colors.$red;
}
}
54 changes: 37 additions & 17 deletions airbyte-webapp/src/components/JobItem/components/AttemptDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import classNames from "classnames";
import dayjs from "dayjs";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";

import { FlexContainer } from "components/ui/Flex";
import { Text } from "components/ui/Text";

import { AttemptRead, AttemptStatus } from "core/request/AirbyteClient";
import { formatBytes } from "utils/numberHelper";

Expand All @@ -13,9 +15,10 @@ interface AttemptDetailsProps {
className?: string;
attempt: AttemptRead;
hasMultipleAttempts?: boolean;
jobId: string;
}

export const AttemptDetails: React.FC<AttemptDetailsProps> = ({ attempt, className, hasMultipleAttempts }) => {
export const AttemptDetails: React.FC<AttemptDetailsProps> = ({ attempt, hasMultipleAttempts, jobId }) => {
const { formatMessage } = useIntl();

if (attempt.status !== AttemptStatus.succeeded && attempt.status !== AttemptStatus.failed) {
Expand Down Expand Up @@ -49,36 +52,53 @@ export const AttemptDetails: React.FC<AttemptDetailsProps> = ({ attempt, classNa
const isFailed = attempt.status === AttemptStatus.failed && !isCancelled;

return (
<div className={classNames(styles.container, className)}>
<>
{!isCancelled && (
<div className={styles.details}>
<FlexContainer gap="xs">
{hasMultipleAttempts && (
<strong className={classNames(styles.lastAttempt, { [styles.failed]: isFailed })}>
<Text color={isFailed ? "red" : "darkBlue"} bold as="span" size="sm">
<FormattedMessage id="sources.lastAttempt" />
</strong>
</Text>
)}
<span>{formatBytes(attempt?.totalStats?.bytesEmitted)}</span>
<span>
<Text as="span" color="grey" size="sm">
{formatBytes(attempt?.totalStats?.bytesEmitted)}
</Text>
<Text as="span" color="grey" size="sm">
|
</Text>
<Text as="span" color="grey" size="sm">
<FormattedMessage
id="sources.countEmittedRecords"
values={{ count: attempt.totalStats?.recordsEmitted || 0 }}
/>
</span>
<span>
</Text>
<Text as="span" color="grey" size="sm">
|
</Text>
<Text as="span" color="grey" size="sm">
<FormattedMessage
id="sources.countCommittedRecords"
values={{ count: attempt.totalStats?.recordsCommitted || 0 }}
/>
</span>
<span>
</Text>
<Text as="span" color="grey" size="sm">
|
</Text>
<Text as="span" color="grey" size="sm">
<FormattedMessage id="jobs.jobId" values={{ id: jobId }} />
</Text>
<Text as="span" color="grey" size="sm">
|
</Text>
<Text as="span" color="grey" size="sm">
{hours ? <FormattedMessage id="sources.hour" values={{ hour: hours }} /> : null}
{hours || minutes ? <FormattedMessage id="sources.minute" values={{ minute: minutes }} /> : null}
<FormattedMessage id="sources.second" values={{ second: seconds }} />
</span>
</div>
</Text>
</FlexContainer>
)}
{isFailed && (
<div className={styles.failedMessage}>
<Text color="red" size="sm" className={styles.failedMessage}>
{formatMessage(
{
id: "ui.keyValuePairV3",
Expand All @@ -88,8 +108,8 @@ export const AttemptDetails: React.FC<AttemptDetailsProps> = ({ attempt, classNa
value: getExternalFailureMessage(attempt),
}
)}
</div>
</Text>
)}
</div>
</>
);
};
22 changes: 14 additions & 8 deletions airbyte-webapp/src/components/JobItem/components/JobLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ import { FormattedMessage } from "react-intl";
import { useLocation } from "react-router-dom";

import Logs from "components/Logs";
import { Box } from "components/ui/Box";
import { StatusIcon } from "components/ui/StatusIcon";
import { StatusIconStatus } from "components/ui/StatusIcon/StatusIcon";
import { StepsMenu } from "components/ui/StepsMenu";
import { StepMenuItem } from "components/ui/StepsMenu/StepsMenu";
import { Text } from "components/ui/Text";

import { useGetDebugInfoJob } from "core/api";
import { AttemptRead, AttemptStatus, SynchronousJobRead } from "core/request/AirbyteClient";

import styles from "./JobLogs.module.scss";
import { LogsDetails } from "./LogsDetails";
import Tabs, { TabsData } from "./Tabs";
import { parseAttemptLink } from "../attemptLinkUtils";
import { JobsWithJobs } from "../types";
import { isCancelledAttempt } from "../utils";
import { getJobId, isCancelledAttempt } from "../utils";

interface JobLogsProps {
jobIsFailed?: boolean;
Expand Down Expand Up @@ -50,7 +52,7 @@ const jobIsSynchronousJobRead = (job: SynchronousJobRead | JobsWithJobs): job is
return !!(job as SynchronousJobRead)?.logs?.logLines;
};

export const JobLogs: React.FC<JobLogsProps> = ({ jobIsFailed, job }) => {
export const JobLogs: React.FC<JobLogsProps> = ({ job }) => {
const isSynchronousJobRead = jobIsSynchronousJobRead(job);

const id: number | string = (job as JobsWithJobs).job?.id ?? (job as SynchronousJobRead).id;
Expand Down Expand Up @@ -80,10 +82,14 @@ export const JobLogs: React.FC<JobLogsProps> = ({ jobIsFailed, job }) => {
const currentAttempt = job.attempts?.[attemptNumber];
const path = ["/tmp/workspace", job.job.id, currentAttempt?.id, "logs.log"].join("/");

const attemptsTabs: TabsData[] =
const attemptsTabs: StepMenuItem[] =
job.attempts?.map((item, index) => ({
id: index.toString(),
icon: <StatusIcon status={mapAttemptStatusToIcon(item)} />,
icon: (
<Box mr="md">
<StatusIcon status={mapAttemptStatusToIcon(item)} />
</Box>
),
name: <FormattedMessage id="sources.attemptNum" values={{ number: index + 1 }} />,
})) ?? [];

Expand All @@ -92,16 +98,16 @@ export const JobLogs: React.FC<JobLogsProps> = ({ jobIsFailed, job }) => {
return (
<>
{attempts > 1 && (
<Tabs
<StepsMenu
lightMode
activeStep={attemptNumber.toString()}
onSelect={(at) => setAttemptNumber(parseInt(at))}
data={attemptsTabs}
isFailed={jobIsFailed}
/>
)}
{attempts ? (
<LogsDetails
id={job.job.id}
jobId={getJobId(job)}
path={path}
currentAttempt={currentAttempt}
jobDebugInfo={debugInfo}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
@use "scss/colors";
@use "scss/variables";

.jobSummary {
border-bottom: variables.$border-thin solid transparent;

&--open {
border-bottom: variables.$border-thin solid colors.$grey-100;
}

&__button {
padding: variables.$spacing-lg;
padding-right: 0;
display: flex;
justify-content: flex-start;
align-items: center;
text-align: left;
background: none;
border: none;
flex: 1;
cursor: pointer;
height: unset !important;
min-height: 70px;
gap: variables.$spacing-lg;
user-select: text;
min-width: 0;
}

&__justification {
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: center;
min-width: 0;
}

&__statusIcon {
display: flex;
align-items: center;
justify-content: center;
padding-inline: variables.$spacing-lg;
}

&__timestamp {
text-align: right;
white-space: nowrap;
}

&__chevron {
transition: variables.$transition;

.jobSummary--open & {
transform: rotate(90deg);
}

button:hover & {
opacity: 1;
}
}

&__chevronButton {
height: 100%;
border: none;
background-color: transparent;
padding: variables.$spacing-lg;
padding-left: variables.$spacing-sm;
cursor: pointer;
}
}
Loading

0 comments on commit fd1c287

Please sign in to comment.