Unverified Commit df559ade authored by Marcos Iglesias's avatar Marcos Iglesias Committed by GitHub

feat: Adds Shimmer loader to the Table Issues and Footer (#483)

* Adds loading state to Table Issues and Footer

* Updates spacing on issues loader

* Updates issues block height to adapt to the content's height

* Prettier errors

* Reducing number of lines

* Retriveing pre-commit hook

* Fixing linting issues
parent 2718eb5f
...@@ -11,14 +11,14 @@ jest.spyOn(DateUtils, 'formatDateTimeLong').mockReturnValue(MOCK_DATE_STRING); ...@@ -11,14 +11,14 @@ jest.spyOn(DateUtils, 'formatDateTimeLong').mockReturnValue(MOCK_DATE_STRING);
describe('Footer', () => { describe('Footer', () => {
let props: FooterProps; let props: FooterProps;
let subject; let wrapper;
beforeEach(() => { beforeEach(() => {
props = { props = {
lastIndexed: 1555632106, lastIndexed: 1555632106,
getLastIndexed: jest.fn(), getLastIndexed: jest.fn(),
}; };
subject = shallow(<Footer {...props} />); wrapper = shallow(<Footer {...props} />);
}); });
describe('componentDidMount', () => { describe('componentDidMount', () => {
...@@ -29,25 +29,34 @@ describe('Footer', () => { ...@@ -29,25 +29,34 @@ describe('Footer', () => {
describe('render', () => { describe('render', () => {
it('calls generateDateTimeString if this.state.lastIndexed', () => { it('calls generateDateTimeString if this.state.lastIndexed', () => {
jest.spyOn(subject.instance(), 'generateDateTimeString'); jest.spyOn(wrapper.instance(), 'generateDateTimeString');
subject.instance().render(); wrapper.instance().render();
expect(subject.instance().generateDateTimeString).toHaveBeenCalled(); expect(wrapper.instance().generateDateTimeString).toHaveBeenCalled();
}); });
it('renders correct content if this.state.lastIndexed', () => { it('renders correct content if this.state.lastIndexed', () => {
const expectedText = `Amundsen was last indexed on ${MOCK_DATE_STRING}`; const expectedText = `Amundsen was last indexed on ${MOCK_DATE_STRING}`;
expect(subject.find('#footer').props().children).toBeTruthy();
expect(subject.find('#footer').text()).toEqual(expectedText); expect(wrapper.find('#footer').props().children).toBeTruthy();
expect(wrapper.find('#footer').text()).toEqual(expectedText);
}); });
it('renders no content if this.state.lastIndexed is null', () => { describe('when state.lastIndexed is falsy', () => {
subject.setProps({ lastIndexed: null }); it('renders the shimmering loader if this.state.lastIndexed is null', () => {
expect(subject.find('#footer').props().children).toBeFalsy(); const expected = 1;
wrapper.setProps({ lastIndexed: null });
expect(wrapper.find('ShimmeringFooterLoader').length).toEqual(expected);
}); });
it('renders no content if this.state.lastIndexed is undefined', () => { it('renders the shimmering loader if this.state.lastIndexed is undefined', () => {
subject.setProps({ lastIndexed: undefined }); const expected = 1;
expect(subject.find('#footer').props().children).toBeFalsy();
wrapper.setProps({ lastIndexed: undefined });
expect(wrapper.find('ShimmeringFooterLoader').length).toEqual(expected);
});
}); });
}); });
}); });
......
...@@ -20,6 +20,14 @@ interface DispatchFromProps { ...@@ -20,6 +20,14 @@ interface DispatchFromProps {
export type FooterProps = StateFromProps & DispatchFromProps; export type FooterProps = StateFromProps & DispatchFromProps;
const ShimmeringFooterLoader: React.FC = () => {
return (
<div className="shimmer-footer">
<div className="shimmer-footer-row is-shimmer-animated" />
</div>
);
};
export class Footer extends React.Component<FooterProps> { export class Footer extends React.Component<FooterProps> {
componentDidMount() { componentDidMount() {
this.props.getLastIndexed(); this.props.getLastIndexed();
...@@ -30,12 +38,14 @@ export class Footer extends React.Component<FooterProps> { ...@@ -30,12 +38,14 @@ export class Footer extends React.Component<FooterProps> {
}; };
render() { render() {
let content; let content = <ShimmeringFooterLoader />;
if (this.props.lastIndexed) { if (this.props.lastIndexed) {
content = ( content = (
<div>{`Amundsen was last indexed on ${this.generateDateTimeString()}`}</div> <div>{`Amundsen was last indexed on ${this.generateDateTimeString()}`}</div>
); );
} }
return ( return (
<footer> <footer>
<div className="phantom-div" /> <div className="phantom-div" />
......
@import 'variables'; @import 'variables';
$shimmer-loader-height: 20px;
/* TODO: Not the greatest sticky footer implementation */ /* TODO: Not the greatest sticky footer implementation */
.footer { .footer {
background-color: $white; background-color: $white;
...@@ -22,3 +24,16 @@ ...@@ -22,3 +24,16 @@
padding: 20px; padding: 20px;
width: 100%; width: 100%;
} }
.shimmer-footer {
height: $shimmer-loader-height;
width: 100%;
text-align: center;
margin: 0 auto;
}
.shimmer-footer-row {
height: $shimmer-loader-height - $spacer-1;
display: inline-block;
width: 33%;
}
...@@ -6,7 +6,6 @@ import AppConfig from 'config/config'; ...@@ -6,7 +6,6 @@ import AppConfig from 'config/config';
import globalState from 'fixtures/globalState'; import globalState from 'fixtures/globalState';
import { NO_DATA_ISSUES_TEXT } from 'components/TableDetail/TableIssues/constants'; import { NO_DATA_ISSUES_TEXT } from 'components/TableDetail/TableIssues/constants';
import ReportTableIssue from 'components/TableDetail/ReportTableIssue';
import { import {
TableIssues, TableIssues,
TableIssueProps, TableIssueProps,
...@@ -15,8 +14,6 @@ import { ...@@ -15,8 +14,6 @@ import {
} from '.'; } from '.';
describe('TableIssues', () => { describe('TableIssues', () => {
const setStateSpy = jest.spyOn(TableIssues.prototype, 'setState');
const setup = (propOverrides?: Partial<TableIssueProps>) => { const setup = (propOverrides?: Partial<TableIssueProps>) => {
const props: TableIssueProps = { const props: TableIssueProps = {
isLoading: false, isLoading: false,
...@@ -37,19 +34,22 @@ describe('TableIssues', () => { ...@@ -37,19 +34,22 @@ describe('TableIssues', () => {
AppConfig.issueTracking.enabled = true; AppConfig.issueTracking.enabled = true;
}); });
it('renders LoadingSpinner if loading', () => { it('renders Shimmer loader if loading', () => {
const { props, wrapper } = setup({ isLoading: true }); const { wrapper } = setup({ isLoading: true });
expect(wrapper.find('LoadingSpinner').exists()).toBe(true); const expected = 1;
const actual = wrapper.find('ShimmeringIssuesLoader').length;
expect(actual).toEqual(expected);
}); });
it('renders text if no issues', () => { it('renders text if no issues', () => {
const { props, wrapper } = setup({ issues: [] }); const { wrapper } = setup({ issues: [] });
expect(wrapper.find('.issue-banner').text()).toEqual(NO_DATA_ISSUES_TEXT); expect(wrapper.find('.issue-banner').text()).toEqual(NO_DATA_ISSUES_TEXT);
}); });
it('renders issues if they exist', () => { it('renders issues if they exist', () => {
AppConfig.issueTracking.enabled = true; AppConfig.issueTracking.enabled = true;
const { props, wrapper } = setup({ const { wrapper } = setup({
issues: [ issues: [
{ {
issue_key: 'issue_key', issue_key: 'issue_key',
...@@ -68,7 +68,7 @@ describe('TableIssues', () => { ...@@ -68,7 +68,7 @@ describe('TableIssues', () => {
}); });
it('renders no link to issues if no issues', () => { it('renders no link to issues if no issues', () => {
const { props, wrapper } = setup({ const { wrapper } = setup({
issues: [], issues: [],
total: 0, total: 0,
allIssuesUrl: null, allIssuesUrl: null,
...@@ -77,7 +77,7 @@ describe('TableIssues', () => { ...@@ -77,7 +77,7 @@ describe('TableIssues', () => {
}); });
it('renders link if there are issues', () => { it('renders link if there are issues', () => {
const { props, wrapper } = setup({ const { wrapper } = setup({
issues: [ issues: [
{ {
issue_key: 'issue_key', issue_key: 'issue_key',
......
...@@ -32,6 +32,15 @@ export type TableIssueProps = StateFromProps & ...@@ -32,6 +32,15 @@ export type TableIssueProps = StateFromProps &
DispatchFromProps & DispatchFromProps &
ComponentProps; ComponentProps;
const ShimmeringIssuesLoader: React.FC = () => {
return (
<div className="shimmer-issues">
<div className="shimmer-issues-row shimmer-issues-line--1 is-shimmer-animated" />
<div className="shimmer-issues-row shimmer-issues-line--2 is-shimmer-animated" />
</div>
);
};
export class TableIssues extends React.Component<TableIssueProps> { export class TableIssues extends React.Component<TableIssueProps> {
componentDidMount() { componentDidMount() {
this.props.getIssues(this.props.tableKey); this.props.getIssues(this.props.tableKey);
...@@ -109,7 +118,7 @@ export class TableIssues extends React.Component<TableIssueProps> { ...@@ -109,7 +118,7 @@ export class TableIssues extends React.Component<TableIssueProps> {
<> <>
{this.renderIssueTitle()} {this.renderIssueTitle()}
<div className="table-issues"> <div className="table-issues">
<LoadingSpinner /> <ShimmeringIssuesLoader />
</div> </div>
</> </>
); );
......
@import 'variables'; @import 'variables';
$issue-banner-height: 40px;
$shimmer-line-height: 16px;
$shimmer-loader-lines: 1, 2;
.table-issues { .table-issues {
margin-bottom: $spacer-1; margin-bottom: $spacer-1;
...@@ -8,7 +12,7 @@ ...@@ -8,7 +12,7 @@
color: $text-secondary; color: $text-secondary;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 40px; height: $issue-banner-height;
line-height: 24px; line-height: 24px;
padding: $spacer-1; padding: $spacer-1;
...@@ -17,7 +21,7 @@ ...@@ -17,7 +21,7 @@
} }
.table-issue-priority { .table-issue-priority {
line-height: 20px; line-height: $issue-banner-height / 2;
} }
} }
...@@ -72,8 +76,19 @@ ...@@ -72,8 +76,19 @@
background-color: rgba($priority-bg-color, 0.1); background-color: rgba($priority-bg-color, 0.1);
} }
.loading-spinner { .shimmer-issues {
margin-top: auto; width: 100%;
}
.shimmer-issues-row {
margin-bottom: $spacer-1;
height: $shimmer-line-height;
}
@each $line in $shimmer-loader-lines {
.shimmer-issues-line--#{$line} {
width: percentage(random(100) / 100);
}
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment