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
119 changes: 99 additions & 20 deletions frontend/src/pull-card/commit-statuses.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import { Pull } from '../pull';
import { CommitStatus } from '../types';
import { chakra, Box, useStyleConfig } from "@chakra-ui/react"
import { CommitStatus, StatusState } from '../types';
import { newTab } from "../utils";
import {
chakra,
Box,
useStyleConfig,
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
PopoverArrow,
PopoverCloseButton,
Portal,
BoxProps,
} from "@chakra-ui/react"
import { groupBy } from 'lodash-es';
import { memo } from "react"
import styled from "@emotion/styled"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCircleExclamation, faCircleXmark, faCircleCheck, faClock, IconDefinition } from '@fortawesome/free-solid-svg-icons'

const statusSize = 10;
const marginBetween = 4;
Expand All @@ -19,35 +35,98 @@ const StatusContainer = styled.div`
flex-direction: column;
justify-content: space-between;
gap: ${marginBetween}px;
cursor: pointer;
`;

function Status({status}: {status: CommitStatus}) {
const styles = useStyleConfig('Status', {variant: status.data.state});
const title = status.data.context + ": " + status.data.description;
return (status.data.target_url ?
<chakra.a __css={styles}
target="_blank"
title={title}
href={status.data.target_url}
className="build_status"
/> :
const StatusLinkContainer = Box;

function StatusLink({status}: {status: CommitStatus}) {
const styles = useStyleConfig('StatusLink', {variant: status.data.state});
const link = status.data.target_url;
return (
<StatusLinkContainer
__css={styles}
title={status.data.state}
cursor={link ? "pointer" : "auto"}
onClick={link ? () => newTab(link) : undefined}
>
<FontAwesomeIcon size="lg" icon={iconForStatus(status)}/>
<chakra.span fontWeight="bold">
{status.data.context}:
</chakra.span>
<chakra.span>
{status.data.description}
</chakra.span>
</StatusLinkContainer>
);
}

function iconForStatus(status: CommitStatus): IconDefinition {
const state = status.data.state;
return state === StatusState.pending ? faClock
: state === StatusState.error ? faCircleExclamation
: state === StatusState.failure ? faCircleXmark
: faCircleCheck;
}

type StatusGroupProps = BoxProps & {
statuses: CommitStatus[];
}

function StatusGroup({statuses, ...props}: StatusGroupProps) {
const state = statuses[0].data.state;
const styles = useStyleConfig('StatusGroup', {variant: state});
const contexts = statuses.map(status => status.data.context).join("\n");
const title = `${state}:${statuses.length > 1 ? "\n" : " "}${contexts}`;
return (
<Box __css={styles}
{...props}
flexBasis={1 + statuses.length}
title={title}
className="build_status"
/>
);
}

function SingleStatus(status: CommitStatus) {
const link = status.data.target_url;
return (
<StatusContainer className="build_status_container">
<StatusGroup
onClick={link ? () => newTab(link) : undefined}
statuses={[status]}/>
</StatusContainer>
);
}

export const CommitStatuses = memo(
function CommitStatuses({pull}: {pull: Pull}) {
const statuses = pull.buildStatusesWithRequired();
if (statuses.length === 0) {
return null;
} else if (statuses.length === 1) {
return SingleStatus(statuses[0]);
}
const grouped = groupBy(statuses, (status) => status.data.state);
return (
<StatusContainer className="build_status_container">
{pull.buildStatusesWithRequired().map((status) =>
<Status
status={status}
key={status.data.context}
/>
)}
</StatusContainer>
<Popover isLazy>
<PopoverTrigger>
<StatusContainer className="build_status_container">
{grouped.success && <StatusGroup statuses={grouped.success}/> }
{grouped.pending && <StatusGroup statuses={grouped.pending}/> }
{grouped.failure && <StatusGroup statuses={grouped.failure}/> }
{grouped.error && <StatusGroup statuses={grouped.error}/> }
</StatusContainer>
</PopoverTrigger>
<Portal>
<PopoverContent>
<PopoverArrow />
<PopoverCloseButton />
<PopoverBody>
{statuses.map(status => <StatusLink key={status.data.context} status={status}/>)}
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
);
});
20 changes: 12 additions & 8 deletions frontend/src/pull-card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Age } from './age';
import { Flags } from './flags';
import { Signatures } from './signatures';
import { CopyBranch } from './copy-branch';
import { memo, useEffect, useRef } from "react";
import { memo, useEffect, useRef, RefObject } from "react";
import { RefreshButton } from './refresh';
import { Flex, Box, Link, chakra, Img } from "@chakra-ui/react"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
Expand Down Expand Up @@ -51,13 +51,7 @@ const SigsAndFlags = chakra(Flex, {
export const PullCard = memo(
function PullCard({pull, show}: {pull: Pull, show: boolean}) {
const cardRef = useRef<HTMLElement>(null);

// Animate a highlight when pull.received_at changes
useEffect(() => {
cardRef.current?.classList.add("highlight");
// 1s after the animation, remove the class
setTimeout(() => cardRef.current?.classList.remove("highlight"), 3000);
}, [pull.received_at]);
highlightOnChange(cardRef, [pull.received_at]);

return (
<Card ref={cardRef} display={show ? undefined : "none"}>
Expand Down Expand Up @@ -119,3 +113,13 @@ function avatarClickHandler(event: React.MouseEvent<HTMLElement>) {
window.open(userProfileUrl(user), "_blank");
event.preventDefault();
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function highlightOnChange(ref: RefObject<HTMLElement>, dependencies: Array<any>) {
// Animate a highlight when pull.received_at changes
useEffect(() => {
ref.current?.classList.add("highlight");
// 1s after the animation, remove the class
setTimeout(() => ref.current?.classList.remove("highlight"), 3000);
}, dependencies);
}
3 changes: 2 additions & 1 deletion frontend/src/pull.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sortBy } from "lodash-es";
import { getUser } from "./page-context";
import { PullData, Signature, CommitStatus, StatusState, CommentSource, SignatureGroup } from "./types";

Expand Down Expand Up @@ -129,7 +130,7 @@ export class Pull extends PullData {
});
}
});
return statuses;
return sortBy(statuses, [status => status.data.context.toLowerCase()]);
}

getRequiredBuildStatuses(): string[] {
Expand Down
33 changes: 31 additions & 2 deletions frontend/src/theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,11 @@ export const theme = extendTheme({
}
}
},
Status: {
StatusGroup: {
baseStyle: {
display: "block",
pos: "relative",
w: "100%",
height: "10px",
opacity: 1,
flexGrow: 1,
transition: "opacity 0.3s ease-in-out",
Expand All @@ -98,6 +97,36 @@ export const theme = extendTheme({
},
}
},
StatusLink: {
baseStyle: {
borderBottom: "1px solid var(--build-status-link-divider)",
"&:last-child": {
borderBottom: "none",
},
padding: "5px",
"> span": {
color: "var(--build-status-text)",
marginLeft: "10px",
},
"&:hover": {
"background": "rgb(128,128,128,0.1)",
}
},
variants: {
pending: {
color: "var(--build-state-pending)",
},
success: {
color: "var(--build-state-success)",
},
error: {
color: "var(--build-state-error)",
},
failure: {
color: "var(--build-state-failure)",
},
}
},
Signatures: {
baseStyle: {
fontSize: 14,
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/theme/day_theme.less
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ body[data-theme="day_theme"] {
--build-state-failure: @yellow;
--build-state-error: @red;
--build-state-success: @green;
--build-status-link-divider: @grey;
--build-status-text: @dark-grey;

--tag-default-border: darken(@light-grey, 10%);
--tag-default-background: @white;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/theme/night_theme.less
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ body[data-theme="night_theme"] {
--build-state-failure: @yellow;
--build-state-error: @red;
--build-state-success: @green;
--build-status-link-divider: @grey;
--build-status-text: @light-grey;

--tag-default-border: @dark-grey;
--tag-default-background: @dark-grey;
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ export function userProfileUrl(username: string): string {
const safeUsername = encodeURIComponent(debottedName);
return `https://github.com/${safeUsername}`;
}

export function newTab(url: string) {
window.open(url, "_blank");
}