Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update homepage design. #496

Merged
merged 20 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions services/frontend-service/src/assets/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@import 'src/ui/components/ReleaseCardMini/ReleaseCardMini';
@import 'src/ui/components/ReleaseDialog/ReleaseDialog';
@import 'src/ui/components/TopAppBar/TopAppBar';
@import 'src/ui/components/tooltip/tooltip';
@import 'src/ui/components/SideBar/SideBar';
@import 'src/ui/components/ServiceLane/ServiceLane';
@import 'src/ui/components/navigation/navListItem';
Expand Down
8 changes: 7 additions & 1 deletion services/frontend-service/src/assets/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ $release-dialog-inner-border-radius: 10px;
$release-dialog-label-displacement: -10px;
$release-dialog-lock-icon-size: 24px;

// Release Card Tooltip
$release-card-tooltip-border-radius: 10px;
$release-card-tooltip-border-color: #BABABA;
$release-card-tooltip-width: 285px;
$release-card-tooltip-caret-shift-left: 55px;

// Logo
// 6em = 96px - Logo_width(60px) = 36px / 2 = 18px
// top_bar_height(8em) = 128px - Logo_height(72px) = 56px / 2 = 28px
Expand All @@ -96,7 +102,7 @@ $main-content-padding: ($top-app-bar-height + 0.5em) 2em 2em ($nav-bar-width + 2
--mdc-theme-primary: #2D70D6;
--mdc-theme-on-primary: #ffffff;
--mdc-theme-background: #ffffff;
--mdc-theme-surface: #F7F9FA;
--mdc-theme-surface: #F5F5F5;
--mdc-theme-on-surface: #4A4A4A;
--mdc-theme-secondary: #00AFFF;
--mdc-theme-on-secondary: #FFFFFF;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,44 @@
@use '@material/typography/mdc-typography';
@use '@material/card/mdc-card';

.release-card__container {
display: flex;
flex-direction: column;
}

.release-card {
border-radius: 10px;
min-height: 228px;
min-width: 512px;
max-width: 512px;
margin: 12px;
min-height: 136px;
max-height: 136px;
min-width: 240px;
max-width: 240px;
margin: -20px 16px 4px 16px;
color: var(--mdc-theme-on-surface);
box-shadow: none;

.release-card__description {
height: 100%;
padding: 0 16px 16px 16px;
height: 100vh;
padding: 20px;
}
}

.release-card__header {
padding: 16px 16px 8px 16px;
display: flex;
justify-content: space-between;

.release__title{
@extend .sub-headline1;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}

.release__hash {
@extend .text-regular;
min-width: auto;
border: 1px solid var(--mdc-theme-secondary);
border-radius: 15px;
color: var(--mdc-theme-secondary);
}

.release__hash:hover {
background: var(--mdc-theme-secondary);
color: var(--mdc-theme-on-secondary);
}
}

.release__details {
height: 100%;
padding: 12px 8px;
div {
@extend .text-regular;
}
.release__metadata {
display: flex;
div:first-child {
margin-right: 2em;
}
color: var(--mdc-theme-primary);
}

}
Expand All @@ -65,17 +51,19 @@
}
}
.release-environment.release-environment-prod {
background-color: #50C241;
background-color: #DC0000;
}
.release-environment.release-environment-pre_prod {
background-color: #00AFFF;
background-color: #F3BE00;
}
.release-environment.release-environment-upstream {
background-color: #FF8A00;
background-color: #50C241;
}

.release__environments {
@extend .text-bold;
align-self: center;
z-index: 2;

> *:not(:last-child) {
margin-right: 8px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,61 @@ You should have received a copy of the MIT License
along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>.

Copyright 2023 freiheit.com*/
import { ReleaseCard, ReleaseCardProps } from './ReleaseCard';
import { getFormattedReleaseDate, ReleaseCard, ReleaseCardProps } from './ReleaseCard';
import { render } from '@testing-library/react';
import { UpdateOverview } from '../../utils/store';

describe('Relative Date Calculation', () => {
// the test release date === 18/06/2001 is constant across this test
const testReleaseDate = new Date(2001, 5, 8);

const data = [
{
name: 'less than 1 hour ago',
systemTime: new Date(2001, 5, 8, 0, 1),
expected: '< 1 hour ago',
},
{
name: 'little over 1 hour ago',
systemTime: new Date(2001, 5, 8, 1, 1),
expected: '1 hour ago',
},
{
name: '5 hours ago',
systemTime: new Date(2001, 5, 8, 5, 1),
expected: '5 hours ago',
},
{
name: 'little over 1 day ago',
systemTime: new Date(2001, 5, 9, 1, 1),
expected: '1 day ago',
},
{
name: '92 days ago',
systemTime: new Date(2001, 8, 8, 5, 1),
expected: '92 days ago',
},
];

describe.each(data)('calculates the right date and time', (testcase) => {
it(testcase.name, () => {
// given
jest.useFakeTimers('modern'); // fake time is now "0"
jest.setSystemTime(testcase.systemTime.valueOf()); // time is now at the exact moment when release was created
const { container } = render(getFormattedReleaseDate(testReleaseDate));

// then
expect(container.textContent).toContain('2001-06-08');
expect(container.textContent).toContain('0:0');
expect(container.textContent).toContain(testcase.expected);

// finally
jest.runOnlyPendingTimers();
jest.useRealTimers();
});
});
});

describe('Release Card', () => {
const getNode = (overrides: ReleaseCardProps) => <ReleaseCard {...overrides} />;
const getWrapper = (overrides: ReleaseCardProps) => render(getNode(overrides));
Expand All @@ -30,7 +81,7 @@ describe('Release Card', () => {
{
name: 'using a sample undeploy release - useRelease hook',
props: { app: 'test2', version: -1 },
rels: [{ undeployVersion: true, sourceMessage: 'test-rel' }],
rels: [{ undeployVersion: true }],
},
{
name: 'using a full release - component test',
Expand Down Expand Up @@ -117,7 +168,14 @@ describe('Release Card', () => {
} as any);
const { container } = getWrapper(testcase.props);

expect(container.querySelector('.release__title')?.textContent).toContain(testcase.rels[0].sourceMessage);
// then
if (testcase.rels[0].undeployVersion) {
expect(container.querySelector('.release__title')?.textContent).toContain('Undeploy Version');
} else {
expect(container.querySelector('.release__title')?.textContent).toContain(
testcase.rels[0].sourceMessage
);
}

if (testcase.rels[0].sourceCommitId) {
expect(container.querySelector('.release__hash')?.textContent).toContain(
Expand All @@ -126,7 +184,7 @@ describe('Release Card', () => {
}
if (testcase.rels[0].createdAt) {
expect(container.querySelector('.release__metadata')?.textContent).toContain(
(testcase.rels[0].createdAt as Date).toLocaleDateString()
(testcase.rels[0].createdAt as Date).getFullYear()
);
}
expect(container.querySelector('.env-group-chip-list-test')).not.toBeEmptyDOMElement();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,57 @@ along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>
Copyright 2023 freiheit.com*/
import classNames from 'classnames';
import { Button } from '../button';
import { Tooltip } from '../tooltip/tooltip';
import React, { useEffect, useRef } from 'react';
import { MDCRipple } from '@material/ripple';
import { updateReleaseDialog, useRelease } from '../../utils/store';
import { EnvironmentGroupChipList } from '../chip/EnvironmentGroupChip';

const getRelativeDate = (date: Date): string => {
const millisecondsPerHour = 1000 * 60 * 60; // 1000ms * 60s * 60m
const elapsedTime = Date.now().valueOf() - date.valueOf();
const hoursSinceDate = Math.floor(elapsedTime / millisecondsPerHour);

if (hoursSinceDate < 24) {
// recent date, calculate relative time in hours
if (hoursSinceDate === 0) {
return '< 1 hour ago';
} else if (hoursSinceDate === 1) {
return '1 hour ago';
} else {
return `${hoursSinceDate} hours ago`;
}
} else {
// too many hours, calculate relative time in days
const daysSinceDate = Math.floor(hoursSinceDate / 24);
if (daysSinceDate === 1) {
return '1 day ago';
} else {
return `${daysSinceDate} days ago`;
}
}
};

export const getFormattedReleaseDate = (createdAt: Date): JSX.Element => {
// Adds leading zero to get two digit day and month
const twoDigit = (num: number): string => (num < 10 ? '0' : '') + num;
// date format (ISO): yyyy-mm-dd, with no leading zeros, month is 0-indexed.
// createdAt.toISOString() can't be used because it ignores the current time zone.
const formattedDate = `${createdAt.getFullYear()}-${twoDigit(createdAt.getMonth() + 1)}-${twoDigit(
createdAt.getDate()
)}`;

// getHours automatically gets the hours in the correct timezone. in 24h format (no timezone calculation needed)
const formattedTime = `${createdAt.getHours()}:${createdAt.getMinutes()}`;

return (
<div>
{formattedDate + ' @ ' + formattedTime + ' | '}
<i>{getRelativeDate(createdAt)}</i>
</div>
);
};

export type ReleaseCardProps = {
className?: string;
version: number;
Expand All @@ -30,7 +76,7 @@ export const ReleaseCard: React.FC<ReleaseCardProps> = (props) => {
const MDComponent = useRef<MDCRipple>();
const control = useRef<HTMLDivElement>(null);
const { className, app, version } = props;
const { createdAt, sourceMessage, sourceCommitId, sourceAuthor } = useRelease(app, version);
const { createdAt, sourceMessage, sourceCommitId, sourceAuthor, undeployVersion } = useRelease(app, version);
const clickHandler = React.useCallback(() => {
updateReleaseDialog(app, version);
}, [app, version]);
Expand All @@ -42,28 +88,35 @@ export const ReleaseCard: React.FC<ReleaseCardProps> = (props) => {
return (): void => MDComponent.current?.destroy();
}, []);

const tooltipContents = (
<h2 className="mdc-tooltip__title release__details">
{!!sourceMessage && <b>{sourceMessage}</b>}
{!!sourceCommitId && <Button className="release__hash" label={sourceCommitId} />}
{!!sourceAuthor && <div>{'| ' + sourceAuthor + ' |'}</div>}
{!!createdAt && <div className="release__metadata">{getFormattedReleaseDate(createdAt)}</div>}
</h2>
);

return (
<div className={classNames('mdc-card release-card', className)} onClick={clickHandler}>
<div className="release-card__header">
<div className="release__title mdc-typography--headline6">{sourceMessage}</div>
{!!sourceCommitId && <Button className="release__hash" label={sourceCommitId} />}
</div>
<div className="mdc-card__primary-action release-card__description" ref={control} tabIndex={0}>
<div className="mdc-card__ripple" />
<div className="release__details">
{!!createdAt && (
<div className="release__metadata mdc-typography--subtitle2">
<div>{'Created at: ' + createdAt.toLocaleDateString()}</div>
<div>{'Time ' + createdAt.toLocaleTimeString()}</div>
</div>
)}
<div className="release__version mdc-typography--body2">{'Version: ' + version}</div>
<div className="release__author mdc-typography--body1">{'Author: ' + sourceAuthor}</div>
</div>
<Tooltip id={app + version} content={tooltipContents}>
<div className="release-card__container">
<div className="release__environments">
<EnvironmentGroupChipList app={props.app} version={props.version} />
<EnvironmentGroupChipList app={props.app} version={props.version} useFirstLetter />
</div>
<div className={classNames('mdc-card release-card', className)}>
<div
className="mdc-card__primary-action release-card__description"
ref={control}
tabIndex={0}
onClick={clickHandler}>
<div className="release-card__header">
<div className="release__title">{undeployVersion ? 'Undeploy Version' : sourceMessage}</div>
{!!sourceCommitId && <Button className="release__hash" label={sourceCommitId} />}
</div>
<div className="mdc-card__ripple" />
</div>
</div>
</div>
</div>
</Tooltip>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
@use '@material/typography/mdc-typography';
@use '@material/card/mdc-card';

.release-dialog {

div.MuiPaper-root {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { ReleaseDialog, ReleaseDialogProps } from './ReleaseDialog';
import { render } from '@testing-library/react';
import { UpdateOverview, updateReleaseDialog } from '../../utils/store';
import { Priority, Release } from '../../../api/api';
import { Spy } from 'spy4js';

const mock_getFormattedReleaseDate = Spy.mockModule('../ReleaseCard/ReleaseCard', 'getFormattedReleaseDate');

describe('Release Dialog', () => {
interface dataT {
Expand Down Expand Up @@ -183,6 +186,8 @@ describe('Release Dialog', () => {

describe.each(data)(`Renders the environment locks`, (testcase) => {
it(testcase.name, () => {
// given
mock_getFormattedReleaseDate.getFormattedReleaseDate.returns(<div>some formatted date</div>);
// when
UpdateOverview.set({
applications: { [testcase.props.app as string]: { releases: testcase.rels } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { addAction, updateReleaseDialog, useOverview } from '../../utils/store';
import { Button } from '../button';
import { Locks } from '../../../images';
import { EnvironmentChip } from '../chip/EnvironmentGroupChip';
import { getFormattedReleaseDate } from '../ReleaseCard/ReleaseCard';

export type ReleaseDialogProps = {
className?: string;
Expand Down Expand Up @@ -214,16 +215,7 @@ export const ReleaseDialog: React.FC<ReleaseDialogProps> = (props) => {
</span>
</div>
<div className={classNames('release-dialog-createdAt', className)}>
{!!release?.createdAt && (
<div>
{'Release date ' +
release?.createdAt.toISOString().split('T')[0] +
' ' +
release?.createdAt.toISOString().split('T')[1].split(':')[0] +
':' +
release?.createdAt.toISOString().split('T')[1].split(':')[1]}
</div>
)}
{!!release?.createdAt && getFormattedReleaseDate(release.createdAt)}
</div>
<div className={classNames('release-dialog-author', className)}>
{release?.sourceAuthor ? 'Author: ' + release?.sourceAuthor : ''}
Expand Down
Loading