Skip to content

Commit

Permalink
feat(core/managed): status indicators, explanation cards on artifacts (
Browse files Browse the repository at this point in the history
…#8069)

* feat(core/managed): status indicators, explanation cards on artifacts

* explicitly surface that vetoed versions were never deployed

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
Erik Munson and mergify[bot] committed Mar 23, 2020
1 parent 11f2563 commit e51e342
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 48 deletions.
35 changes: 35 additions & 0 deletions app/scripts/modules/core/src/managed/ArtifactDetail.less
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,39 @@
.detail-section-right {
flex: 1 1 auto;
}

.resource-badge {
width: 16px;
height: 16px;
border-radius: 16px;
border-radius: 50%;

&.current {
background-color: #00b237;
}

&.approved {
border: 1px solid var(--color-status-info);
}

&.deploying {
background-color: var(--color-status-info);
}

&.pending {
border: 1px solid rgba(0, 0, 0, 0.5);
}

&.previous {
background-color: var(--color-nobel);
}

&.vetoed {
background-color: var(--color-status-error);
}

i.fa {
color: var(--color-white);
}
}
}
106 changes: 98 additions & 8 deletions app/scripts/modules/core/src/managed/ArtifactDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import React from 'react';
import classNames from 'classnames';
import { DateTime } from 'luxon';

import { relativeTime, timestamp } from '../utils';
import { IManagedArtifactVersion, IManagedResourceSummary } from '../domain';
import { useEventListener } from '../presentation';

import { ArtifactDetailHeader } from './ArtifactDetailHeader';
import { NoticeCard } from './NoticeCard';
import { Pill } from './Pill';
import { ManagedResourceObject } from './ManagedResourceObject';
import { parseName } from './Frigga';

import './ArtifactDetail.less';

Expand Down Expand Up @@ -42,14 +48,98 @@ export const ArtifactDetail = ({
{/* a short summary with actions/buttons will live here */}
<div className="detail-section-right">{/* artifact metadata will live here */}</div>
</div>
{environments.map(({ name }) => (
<div key={name}>
<h3>{name.toUpperCase()}</h3>
{resourcesByEnvironment[name].filter(shouldDisplayResource).map(resource => (
<ManagedResourceObject key={resource.id} resource={resource} />
))}
</div>
))}
{environments.map(({ name, state, deployedAt, replacedAt, replacedBy }) => {
const deployedAtMillis = DateTime.fromISO(deployedAt).toMillis();
const replacedAtMillis = DateTime.fromISO(replacedAt).toMillis();
const { version: replacedByPackageVersion, buildNumber: replacedByBuildNumber } =
parseName(replacedBy || '') || {};

return (
<div key={name}>
<h3>{name.toUpperCase()}</h3>
{state === 'deploying' && (
<NoticeCard
className="sp-margin-l-right"
icon="md-actuation-launched"
text={undefined}
title="Deploying"
isActive={true}
noticeType="info"
/>
)}
{state === 'current' && deployedAt && (
<NoticeCard
className="sp-margin-l-right"
icon="cloud-check"
text={undefined}
title={
<span>
Deployed {relativeTime(deployedAtMillis)}{' '}
<span className="text-italic text-regular sp-margin-xs-left">
({timestamp(deployedAtMillis)})
</span>
</span>
}
isActive={true}
noticeType="success"
/>
)}
{state === 'previous' && (
<NoticeCard
className="sp-margin-l-right"
icon="checkbox-indeterminate"
text={undefined}
title={
<span className="sp-group-margin-xs-xaxis">
Decommissioned {relativeTime(replacedAtMillis)}{' '}
<span className="text-italic text-regular sp-margin-xs-left">
({timestamp(replacedAtMillis)})
</span>{' '}
<span className="text-regular"></span> <span className="text-regular">replaced by </span>
<Pill
text={
replacedByBuildNumber ? `#${replacedByBuildNumber}` : replacedByPackageVersion || replacedBy
}
/>
</span>
}
isActive={true}
noticeType="neutral"
/>
)}
{state === 'vetoed' && (
<NoticeCard
className="sp-margin-l-right"
icon="skull"
text={undefined}
title={
<span className="sp-group-margin-xs-xaxis">
Marked as bad <span className="text-regular sp-margin-xs-left"></span>{' '}
{deployedAt ? (
<>
<span className="text-regular">last deployed {relativeTime(deployedAtMillis)}</span>{' '}
<span className="text-italic text-regular">({timestamp(deployedAtMillis)})</span>
</>
) : (
<span className="text-regular">never deployed here</span>
)}
</span>
}
isActive={true}
noticeType="error"
/>
)}
{resourcesByEnvironment[name].filter(shouldDisplayResource).map(resource => (
<div className="flex-container-h middle">
<div
className={classNames('resource-badge flex-container-h center middle sp-margin-s-right', state)}
></div>
<ManagedResourceObject key={resource.id} resource={resource} />
</div>
))}
</div>
);
})}
</div>
</>
);
Expand Down
6 changes: 3 additions & 3 deletions app/scripts/modules/core/src/managed/ArtifactRow.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@
}

&.approved {
background-color: #4bafff;
background-color: var(--color-status-info);
}

&.deploying {
background-color: #4bafff;
background-color: var(--color-status-info);
}

&.pending {
Expand All @@ -76,7 +76,7 @@
}

&.vetoed {
background-color: #be0000;
background-color: var(--color-status-error);
}
}

Expand Down
2 changes: 1 addition & 1 deletion app/scripts/modules/core/src/managed/EnvironmentsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function EnvironmentsList({ environments, resourcesById, artifacts }: IEn
artifacts.length === 1 ? 'artifact is' : 'artifacts are'
} deployed in 2 environments with no issues detected.`}
isActive={true}
noticeType={'ok'}
noticeType="success"
/>
{environments.map(({ name, resources }) => (
<div key={name}>
Expand Down
51 changes: 21 additions & 30 deletions app/scripts/modules/core/src/managed/NoticeCard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
color: #444;
}
.NoticeCard.active {
background-color: #fff0a0;
background-color: #e7e7e7;
}
.NoticeCard.warning {
background-color: #ff8282;
animation: noticeStrobe 4s linear;
.NoticeCard.error {
background-color: #fff0a0;
}

.iconContainer {
Expand All @@ -23,14 +22,27 @@
align-items: center;
justify-content: center;
border-radius: 48px;
}
.iconContainer.warning {
background-color: #ff8282;
color: #ffffff;

.icon {
font-size: 24px;
}
}
.iconContainer.ok {

.iconContainer.success {
background-color: #00b237;
color: #ffffff;
}

.iconContainer.neutral {
background-color: var(--color-nobel);
}

.iconContainer.info {
background-color: var(--color-status-info);
}

.iconContainer.error {
background-color: var(--color-status-error);
}

.title {
Expand All @@ -45,24 +57,3 @@
margin-top: 8px;
align-self: center;
}

@keyframes noticeStrobe {
0% {
background-color: transparent;
}
20% {
background-color: colorWarn;
}
40% {
background-color: transparent;
}
60% {
background-color: colorWarn;
}
80% {
background-color: transparent;
}
100% {
background-color: colorWarn;
}
}
13 changes: 7 additions & 6 deletions app/scripts/modules/core/src/managed/NoticeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import classNames from 'classnames';
import styles from './NoticeCard.module.css';

interface INoticeCardProps {
title: string;
text: string;
title: JSX.Element | string;
text: JSX.Element | string;
icon: string;
noticeType: 'ok'; // TypeScript doesn't like this being arbitrary strings because it can't validate styles[noticeType]
noticeType: 'success' | 'neutral' | 'info' | 'error';
isActive: boolean;
className?: string;
}

export function NoticeCard({ title, text, icon, noticeType, isActive }: INoticeCardProps) {
export function NoticeCard({ title, text, icon, noticeType, isActive, className }: INoticeCardProps) {
const NoticeCardClasses = classNames({
[styles.NoticeCard]: true,
[styles[noticeType]]: noticeType,
Expand All @@ -22,10 +23,10 @@ export function NoticeCard({ title, text, icon, noticeType, isActive }: INoticeC
});

return (
<div className={NoticeCardClasses}>
<div className={classNames(NoticeCardClasses, className)}>
{icon && (
<div className={IconContainerClasses}>
<i className={`ico icon-${icon}`} />
<i className={classNames(styles.icon, 'ico', `icon-${icon}`)} />
</div>
)}
{title && <div className={styles.title}>{title}</div>}
Expand Down
1 change: 1 addition & 0 deletions app/scripts/modules/core/src/managed/ObjectRow.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
.ObjectRow {
display: flex;
align-items: center;
flex: auto;

box-shadow: inset 0 -1px 0 0 #cccccc;
height: 40px;
Expand Down

0 comments on commit e51e342

Please sign in to comment.