Skip to content

Commit

Permalink
feat(core): New App Icon with refresher (#8382)
Browse files Browse the repository at this point in the history
* feat(core): New App Icon with refresher

* Use observable

* feat(core): Update refresh tooltip text
  • Loading branch information
caseyhebebrand committed Jun 30, 2020
1 parent 7ae702e commit 162e6c0
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 3 deletions.
12 changes: 12 additions & 0 deletions app/scripts/modules/core/src/application/ApplicationFreshIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

import { Overridable } from '../overrideRegistry/Overridable';

import { Icon } from '../presentation';

@Overridable('applicationIcon')
export class ApplicationFreshIcon extends React.Component<{}> {
public render() {
return <Icon name="spMenuAppInSync" size="small" appearance="light" />;
}
}
37 changes: 37 additions & 0 deletions app/scripts/modules/core/src/application/nav/AppRefresher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';

import { useObservable } from '../../presentation/hooks';
import { Application } from '../application.model';
import { IFetchStatus } from '../service/applicationDataSource';

import { AppRefresherIcon } from './AppRefresherIcon';

export interface IAppRefresherProps {
app: Application;
}

export const AppRefresher = ({ app }: IAppRefresherProps) => {
const [isRefreshing, setIsRefreshing] = React.useState(false);
const [lastRefresh, setLastRefresh] = React.useState(0);

const updateStatus = (fetchStatus: IFetchStatus): void => {
if (fetchStatus.status === 'FETCHING') {
setIsRefreshing(true);
} else if (fetchStatus.status === 'FETCHED') {
setIsRefreshing(false);
setLastRefresh(fetchStatus.lastRefresh);
} else {
setIsRefreshing(false);
}
};

useObservable(app.status$, updateStatus);

const handleRefresh = (): void => {
app.refresh(true);
};

return (
<AppRefresherIcon appName={app.name} lastRefresh={lastRefresh} refreshing={isRefreshing} refresh={handleRefresh} />
);
};
63 changes: 63 additions & 0 deletions app/scripts/modules/core/src/application/nav/AppRefresherIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import { $window } from 'ngimport';

import { ApplicationFreshIcon } from '../ApplicationFreshIcon';
import { SchedulerFactory } from 'core/scheduler';
import { Icon, Tooltip } from 'core/presentation';
import { relativeTime, timestamp } from 'core/utils/timeFormatters';

export interface IAppRefreshIconProps {
appName: string;
lastRefresh: number;
refresh: () => void;
refreshing: boolean;
}

export const AppRefresherIcon = ({ appName, lastRefresh, refresh, refreshing }: IAppRefreshIconProps) => {
const activeRefresher = SchedulerFactory.createScheduler(2000);
const [timeSinceRefresh, setTimeSinceRefresh] = React.useState(relativeTime(lastRefresh));
const [iconPulsing, setIconPulsing] = React.useState(false);

React.useEffect(() => {
activeRefresher.subscribe(() => {
setTimeSinceRefresh(relativeTime(lastRefresh));
});

return () => activeRefresher.unsubscribe();
}, [lastRefresh]);

const oldAge = 2 * 60 * 1000; // 2 minutes;
const age = new Date().getTime() - lastRefresh;
const isStale = age > oldAge;

const refreshApp = () => {
setIconPulsing(true);
setTimeout(() => {
setIconPulsing(false);
}, 3000);
refresh();
};

const RefresherTooltip = (
<span>
<p>
<strong>{appName}</strong>
</p>
<p>{`app data is ${refreshing ? 'refreshing' : 'up to date (click to refresh)'}`}</p>
<p>
Last refresh: {timestamp(lastRefresh)} <br /> {timeSinceRefresh}
</p>
<p>Note: Due to caching, data may be delayed up to 2 minutes</p>
</span>
);
const isRefreshing = iconPulsing || refreshing;

return (
<Tooltip template={RefresherTooltip} placement={$window.innerWidth < 1100 ? 'bottom' : 'right'}>
<div className={`application-header-icon${isRefreshing ? ' header-icon-pulsing' : ''}`} onClick={refreshApp}>
{!isStale && !isRefreshing && <ApplicationFreshIcon />}
{(isStale || isRefreshing) && <Icon name="spMenuAppUnsynced" size="small" appearance="light" />}
</div>
</Tooltip>
);
};
12 changes: 9 additions & 3 deletions app/scripts/modules/core/src/application/nav/verticalNav.less
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import (reference) '~core/presentation/less/imports/commonImports.less';

.vertical-navigation {
width: 240px;
height: 100%;
Expand All @@ -13,12 +15,16 @@
border: 2px solid var(--color-white);
color: var(--color-white);
text-align: center;
width: 30px;
height: 30px;
line-height: 28px;
width: 32px;
height: 32px;
line-height: 32px;
font-size: 15px;
}

.header-icon-pulsing {
.pulsing(4);
}

.refresher {
font-size: 75%;
margin-left: 10px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import { ReactComponent as spCIMerged } from './vectors/spCIMerged.svg';
import { ReactComponent as spCIPullRequest } from './vectors/spCIPullRequest.svg';
import { ReactComponent as spCIPullRequestClosed } from './vectors/spCIPullRequestClosed.svg';
import { ReactComponent as spEnvironments } from './vectors/spEnvironments.svg';
import { ReactComponent as spMenuAppInSync } from './vectors/spMenuAppInSync.svg';
import { ReactComponent as spMenuAppUnsynced } from './vectors/spMenuAppUnsynced.svg';
import { ReactComponent as spMenuCanaryConfig } from './vectors/spMenuCanaryConfig.svg';
import { ReactComponent as spMenuCanaryReport } from './vectors/spMenuCanaryReport.svg';
import { ReactComponent as spMenuClusters } from './vectors/spMenuClusters.svg';
Expand Down Expand Up @@ -202,6 +204,8 @@ export const iconsByName = {
spCIPullRequest,
spCIPullRequestClosed,
spEnvironments,
spMenuAppInSync,
spMenuAppUnsynced,
spMenuCanaryConfig,
spMenuCanaryReport,
spMenuClusters,
Expand Down
2 changes: 2 additions & 0 deletions app/scripts/modules/core/src/presentation/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,8 @@ dl {

.tooltip-inner {
overflow-wrap: break-word;
max-width: 350px;
text-align: left;
p:last-child {
margin-bottom: 0;
}
Expand Down

0 comments on commit 162e6c0

Please sign in to comment.