Skip to content

Commit

Permalink
feat(front): add anchor links to builds and artifacts (#205)
Browse files Browse the repository at this point in the history
remove unused state items

remove invalid feather icon sizes

add copy anchor link to clipboard button

Add anchor link component and artifact anchor links

expand build card if URL bar hash link matches child artifact
  • Loading branch information
ekelen authored May 11, 2020
1 parent ed995d4 commit cea22ac
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 29 deletions.
22 changes: 22 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"dayjs": "^1.8.25",
"js-cookie": "^2.2.1",
"react": "16.13.1",
"react-copy-to-clipboard": "^5.0.2",
"react-dom": "16.13.1",
"react-feather": "2.0.3",
"react-hook-form": "5.2.0",
Expand Down
6 changes: 0 additions & 6 deletions web/src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ export const ARTIFACT_KINDS = Object.values(ARTIFACT_KIND_VALUE).map((kind) =>
kind.toString()
);

export const PLATFORMS = {
iOS: '1',
android: '2',
none: '3',
};

export const BRANCH = {
MASTER: 'MASTER',
};
Expand Down
7 changes: 1 addition & 6 deletions web/src/store/ResultStore.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {useReducer} from 'react';
import {cloneDeep} from 'lodash';
import {retrieveAuthCookie} from '../api/auth';
import {actions, PLATFORMS, ARTIFACT_KIND_VALUE} from '../constants';
import {actions, ARTIFACT_KIND_VALUE} from '../constants';

// TODO: Yes, this file needs a new name, and should maybe be split
export const ResultContext = React.createContext();
Expand All @@ -18,10 +18,6 @@ export const INITIAL_STATE = {
uiFilters: {
artifact_kinds: [ARTIFACT_KIND_VALUE.IPA],
},
filtersPlatform: {
iOS: true,
android: false,
},
filtersBranch: {
master: false,
develop: false,
Expand All @@ -33,7 +29,6 @@ export const INITIAL_STATE = {
},
filtersImplemented: {
apps: ['chat'],
os: ['iOS', 'android'],
branch: ['all'],
},
};
Expand Down
34 changes: 34 additions & 0 deletions web/src/ui/components/AnchorLink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import {CopyToClipboard} from 'react-copy-to-clipboard';

const AnchorLink = ({
tooltipMessage,
setTooltipMessage,
location,
children,
target,
}) => {
return (
<>
<div className="copy-artifact-to-clipboard-icon">
{tooltipMessage && (
<div className="badge badge-secondary confirm-copy">
{tooltipMessage}
</div>
)}
<CopyToClipboard
text={`${window.location.protocol}//${window.location.host}${location.pathname}${location.search}#${target}`}
title="Copy link to clipboard"
onCopy={() => {
setTooltipMessage('Link copied');
setTimeout(() => setTooltipMessage(''), 1000);
}}
>
{children}
</CopyToClipboard>
</div>
</>
);
};

export default AnchorLink;
45 changes: 42 additions & 3 deletions web/src/ui/components/BuildCard/ArtifactCard.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
import React, {useContext} from 'react';
import React, {useContext, useState, useRef, useEffect} from 'react';
import {ThemeContext} from '../../../store/ThemeStore';
import {Clock, Calendar, ArrowDownCircle, AlertTriangle} from 'react-feather';
import {
Clock,
Calendar,
ArrowDownCircle,
AlertTriangle,
Link,
} from 'react-feather';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faAndroid, faApple} from '@fortawesome/free-brands-svg-icons';
import {
faQuestionCircle,
faFile,
faHammer,
} from '@fortawesome/free-solid-svg-icons';
import {useLocation} from 'react-router-dom';

import {tagStyle, actionButtonStyle} from '../../styleTools/buttonStyler';
import AnchorLink from '../AnchorLink';

import {KIND_TO_PLATFORM, ARTIFACT_STATE} from '../../../constants';
import {getTimeDuration, getRelativeTime} from '../../../util/date';

import './BuildCard.scss';

// TODO: Factor out into DOM util file
const scrollToRef = (ref) => window.scrollTo(0, ref.current.offsetTop);

const ArtifactCard = ({
artifact,
buildMergeUpdatedAt,
mrShortId,
buildStartedAt,
buildFinishedAt,
hashLinkMatch,
}) => {
const {theme} = useContext(ThemeContext);
const location = useLocation();
const {
id: artifactId = '',
state: artifactState = '',
Expand All @@ -34,6 +47,15 @@ const ArtifactCard = ({
file_size: artifactFileSize = '',
driver: artifactDriver = '',
} = artifact;
const [tooltipMessage, setTooltipMessage] = useState('');
const artifactRef = useRef(null);

const executeScroll = () => scrollToRef(artifactRef);
useEffect(() => {
if (hashLinkMatch === true) {
executeScroll();
}
}, []);

const timeSinceBuildUpdated = getRelativeTime(buildMergeUpdatedAt);
const buildDurationSeconds = getTimeDuration(buildStartedAt, buildFinishedAt);
Expand Down Expand Up @@ -148,13 +170,30 @@ const ArtifactCard = ({
</div>
);

const SharableArtifactLink = (
<AnchorLink
theme={theme}
color={theme.text.sectionText}
tooltipMessage={tooltipMessage}
setTooltipMessage={setTooltipMessage}
location={location}
children={<Link size={16} />}
target={artifactId}
/>
);

return (
<React.Fragment key={artifactId}>
<div
id={artifactId}
className="card-row expanded"
style={{color: theme.text.sectionText}}
ref={artifactRef}
>
<div className="card-left-icon icon-top">{PlatformIcon}</div>
<div className="card-left-icon icon-top">
{SharableArtifactLink}
{PlatformIcon}
</div>
<div className="card-details">
<div className="card-details-row">
<div className="">
Expand Down
55 changes: 50 additions & 5 deletions web/src/ui/components/BuildCard/BuildCard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useContext, useState} from 'react';
import React, {useContext, useState, useRef, useEffect} from 'react';
import {
GitCommit,
GitMerge,
Expand All @@ -8,6 +8,7 @@ import {
ChevronDown,
AlertCircle,
Calendar,
Link,
} from 'react-feather';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faGithub} from '@fortawesome/free-brands-svg-icons';
Expand All @@ -17,6 +18,7 @@ import {
faPencilAlt,
faFile,
} from '@fortawesome/free-solid-svg-icons';
import {useLocation} from 'react-router-dom';

import {ThemeContext} from '../../../store/ThemeStore';
import {sharedThemes} from '../../styleTools/themes';
Expand All @@ -28,11 +30,23 @@ import {MR_STATE, BUILD_STATE} from '../../../constants';
import {getRelativeTime, getTimeLabel} from '../../../util/date';

import './BuildCard.scss';
import AnchorLink from '../AnchorLink';

const scrollToRef = (ref) => window.scrollTo(0, ref.current.offsetTop);

const BuildCard = ({build, toCollapse}) => {
const [expanded, toggleExpanded] = useState(!toCollapse);
const [messageExpanded, toggleMessageExpanded] = useState(false);
const [tooltipMessage, setTooltipMessage] = useState('');
const [artifactHashLinkMatch, setContainsArtifactHashLinkMatch] = useState(
''
);
const {theme} = useContext(ThemeContext);
const location = useLocation();
const buildRef = useRef(null);

const executeScroll = () => scrollToRef(buildRef);

const {
short_id: buildShortId = '',
id: buildId = '',
Expand All @@ -46,6 +60,7 @@ const BuildCard = ({build, toCollapse}) => {
completed_at: buildCompletedAt = '',
updated_at: buildUpdatedAt = '',
driver: buildDriver = '',
has_mergerequest: buildHasMr = null,
has_mergerequest: {
short_id: mrShortId = '',
commit_url: mrCommitUrl = '',
Expand All @@ -61,12 +76,29 @@ const BuildCard = ({build, toCollapse}) => {
} = {},
} = {},
has_project: {id: buildProjectUrl = ''} = {},
has_artifacts: buildHasArtifacts = [],
} = build || {};

useEffect(() => {
if (location.hash) {
if (location.hash.slice(1) === buildId) {
executeScroll();
} else if (buildHasArtifacts.length) {
const artifactMatch = buildHasArtifacts.find(
(a) => a.id === location.hash.slice(1)
);
if (artifactMatch) {
setContainsArtifactHashLinkMatch(artifactMatch);
toggleExpanded(true);
}
}
}
}, []);

const COMMIT_LEN = 7;
const MESSAGE_LEN = 280;
const isMaster = buildBranch && buildBranch.toUpperCase() === 'MASTER';
const {has_mergerequest: buildHasMr = false} = build;
// const {has_mergerequest: buildHasMr = false} = build;

const timeSinceUpdated = getRelativeTime(buildUpdatedAt);
const timeSinceCreated = getRelativeTime(buildCreatedAt);
Expand Down Expand Up @@ -155,6 +187,17 @@ const BuildCard = ({build, toCollapse}) => {
</>
);

const SharableLink = (
<AnchorLink
color={theme.text.sectionText}
tooltipMessage={tooltipMessage}
setTooltipMessage={setTooltipMessage}
location={location}
children={<Link size={16} />}
target={buildId}
/>
);

const CommitIcon = mrCommitUrl ? (
<a
href={mrCommitUrl}
Expand Down Expand Up @@ -220,7 +263,7 @@ const BuildCard = ({build, toCollapse}) => {
}}
onClick={() => toggleExpanded(!expanded)}
>
{expanded ? <ChevronUp size="1.25rem" /> : <ChevronDown size="1.25rem" />}
{expanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div>
);

Expand All @@ -233,7 +276,7 @@ const BuildCard = ({build, toCollapse}) => {
</div>
) : (
<div className="card-avatar" title="Unknown author">
<User color={theme.text.sectionText} size="1.5rem" />
<User color={theme.text.sectionText} size={14} />
</div>
);

Expand Down Expand Up @@ -363,7 +406,7 @@ const BuildCard = ({build, toCollapse}) => {
);

return (
<div className="BuildCard">
<div className="BuildCard" id={buildId} ref={buildRef}>
<div
className="card"
style={{
Expand All @@ -376,6 +419,7 @@ const BuildCard = ({build, toCollapse}) => {
key={buildId}
>
<div className={'card-row' + (expanded ? ' expanded' : '')}>
{SharableLink}
{CardIcon}
<h2 className="card-title" style={{color: theme.text.blockTitle}}>
{CardTitle}
Expand Down Expand Up @@ -429,6 +473,7 @@ const BuildCard = ({build, toCollapse}) => {
buildStartedAt={buildStartedAt}
buildFinishedAt={buildFinishedAt}
key={artifact.id}
artifactHashLinkMatch={artifactHashLinkMatch === artifact.id}
/>
))}
</div>
Expand Down
Loading

0 comments on commit cea22ac

Please sign in to comment.