Unverified Commit 2718eb5f authored by Marcos Iglesias's avatar Marcos Iglesias Committed by GitHub

fix: Fixes sectioning and accessibility issues all over the application (#480)

* Adds alt attribute to images

* Uses time tags on times on the application

* Dealing with sectioning and accessibility issues

* Formatting

* Fixing stuff

* Tamika's comments

* Fixing tests

* Fixing watermark label size

* Tamika's comments

* Tweaks

* More tweaks
parent adf52564
......@@ -185,3 +185,17 @@ body {
overflow: hidden;
text-overflow: ellipsis;
}
// Text for Screen Readers only
// Reference: Bootstrap 4 codebase
.sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
......@@ -52,17 +52,19 @@ export class AnnouncementPage extends React.Component<AnnouncementPageProps> {
render() {
return (
<DocumentTitle title="Announcements - Amundsen">
<div className="container announcement-container">
<main className="container announcement-container">
<div className="row">
<div className="col-xs-12">
<h3 id="announcement-header">Announcements</h3>
<h1 id="announcement-header" className="h3">
Announcements
</h1>
<hr />
<div id="announcement-content" className="announcement-content">
{this.createPosts()}
</div>
</div>
</div>
</div>
</main>
</DocumentTitle>
);
}
......
......@@ -7,15 +7,17 @@ export class BrowsePage extends React.Component {
render() {
return (
<DocumentTitle title="Browse - Amundsen">
<div className="container">
<main className="container">
<div className="row">
<div className="col-xs-12">
<h3 id="browse-header">Browse Tags</h3>
<h1 className="h3" id="browse-header">
Browse Tags
</h1>
<hr className="header-hr" />
<TagsList />
</div>
</div>
</div>
</main>
</DocumentTitle>
);
}
......
......@@ -2,6 +2,7 @@ export const ERROR_MESSAGE =
'Unable to load the dashboard preview image. \
Please view the dashboard at';
export const PREVIEW_BUTTON_TEXT = 'See Dashboard Preview';
export const PREVIEW_BASE = '/api/dashboard_preview/v0/dashboard';
export const PREVIEW_END = 'preview.jpg';
......
......@@ -43,7 +43,7 @@ const PreviewModal = ({ imageSrc, onClose }: PreviewModalProps) => {
</Modal.Title>
</Modal.Header>
<Modal.Body>
<img src={imageSrc} height="auto" width="100%" />
<img src={imageSrc} height="auto" width="100%" alt="" />
</Modal.Body>
</Modal>
);
......@@ -89,6 +89,7 @@ export class ImagePreview extends React.Component<
type="button"
onClick={this.handlePreviewButton}
>
<span className="sr-only">{Constants.PREVIEW_BUTTON_TEXT}</span>
<img
className="preview"
style={
......@@ -99,6 +100,7 @@ export class ImagePreview extends React.Component<
onError={this.onError}
height="auto"
width="100%"
alt=""
/>
</button>
)}
......
import * as React from 'react';
import { shallow } from 'enzyme';
import { ResourceType } from 'interfaces';
import QueryList, { QueryListProps } from '.';
import QueryListItem from '../QueryListItem';
import { ResourceType } from 'interfaces';
const setup = (propOverrides?: Partial<QueryListProps>) => {
const props = {
queries: [],
......
import * as React from 'react';
import QueryListItem from '../QueryListItem';
import { QueryResource } from 'interfaces';
import QueryListItem from '../QueryListItem';
import './styles.scss';
......@@ -26,7 +25,11 @@ class QueryList extends React.Component<QueryListProps> {
/>
));
return <ul className="query-list list-group">{queryList}</ul>;
return (
<ul className="query-list list-group" role="tablist">
{queryList}
</ul>
);
}
}
......
......@@ -168,7 +168,7 @@ export class DashboardPage extends React.Component<
/>
</div>
<div className="header-section header-title">
<h3 className="header-title-text truncated">{dashboard.name}</h3>
<h1 className="h3 header-title-text truncated">{dashboard.name}</h1>
<BookmarkIcon
bookmarkKey={dashboard.uri}
resourceType={ResourceType.dashboard}
......@@ -203,7 +203,7 @@ export class DashboardPage extends React.Component<
</div>
</header>
<article className="column-layout-1">
<section className="left-panel">
<aside className="left-panel">
<EditableSection
title="Description"
readOnly
......@@ -257,19 +257,19 @@ export class DashboardPage extends React.Component<
</section>
<section className="metadata-section">
<div className="section-title title-3">Created</div>
<div className="body-2 text-primary">
<time className="body-2 text-primary">
{formatDateTimeShort({
epochTimestamp: dashboard.created_timestamp,
})}
</div>
</time>
</section>
<section className="metadata-section">
<div className="section-title title-3">Last Updated</div>
<div className="body-2 text-primary">
<time className="body-2 text-primary">
{formatDateTimeShort({
epochTimestamp: dashboard.updated_timestamp,
})}
</div>
</time>
</section>
<section className="metadata-section">
<div className="section-title title-3">Recent View Count</div>
......@@ -289,25 +289,25 @@ export class DashboardPage extends React.Component<
<div className="section-title title-3">
Last Successful Run
</div>
<div className="last-successful-run-timestamp body-2 text-primary">
<time className="last-successful-run-timestamp body-2 text-primary">
{dashboard.last_successful_run_timestamp
? formatDateTimeShort({
epochTimestamp:
dashboard.last_successful_run_timestamp,
})
: NO_TIMESTAMP_TEXT}
</div>
</time>
</section>
<section className="metadata-section">
<div className="section-title title-3">Last Run</div>
<div>
<div className="last-run-timestamp body-2 text-primary">
<time className="last-run-timestamp body-2 text-primary">
{dashboard.last_run_timestamp
? formatDateTimeShort({
epochTimestamp: dashboard.last_run_timestamp,
})
: NO_TIMESTAMP_TEXT}
</div>
</time>
<div className="last-run-state">
<Flag
caseType="sentenceCase"
......@@ -322,8 +322,8 @@ export class DashboardPage extends React.Component<
</section>
</section>
<ImagePreview uri={this.state.uri} redirectUrl={dashboard.url} />
</section>
<section className="right-panel">{this.renderTabs()}</section>
</aside>
<main className="right-panel">{this.renderTabs()}</main>
</article>
</div>
);
......
......@@ -17,7 +17,7 @@ import {
} from 'components/Feedback/constants';
import globalState from 'fixtures/globalState';
import { BugReportFeedbackForm, mapDispatchToProps, mapStateToProps } from '..';
import { BugReportFeedbackForm, mapDispatchToProps, mapStateToProps } from '.';
describe('BugReportFeedbackForm', () => {
const setup = () => {
......
......@@ -15,7 +15,7 @@ import {
} from 'components/Feedback/constants';
import globalState from 'fixtures/globalState';
import { RatingFeedbackForm, mapDispatchToProps, mapStateToProps } from '..';
import { RatingFeedbackForm, mapDispatchToProps, mapStateToProps } from '.';
describe('RatingFeedbackForm', () => {
const setup = () => {
......
......@@ -34,8 +34,8 @@ export class RatingFeedbackForm extends AbstractFeedbackForm {
return (
<form id={AbstractFeedbackForm.FORM_ID} onSubmit={this.submitForm}>
<input type="hidden" name="feedback-type" value="NPS Rating" />
<div className="form-group">
<label>{RATING_LABEL}</label>
<div className="form-group clearfix">
<label htmlFor="rating">{RATING_LABEL}</label>
<div>
<div className="radio-set">{radioButtonSet}</div>
<div>
......@@ -48,14 +48,19 @@ export class RatingFeedbackForm extends AbstractFeedbackForm {
</div>
</div>
</div>
<textarea
className="form-control form-group"
name="comment"
form={AbstractFeedbackForm.FORM_ID}
rows={8}
maxLength={2000}
placeholder={COMMENTS_PLACEHOLDER}
/>
<div className="form-group">
<label htmlFor="comment">{COMMENTS_PLACEHOLDER}</label>
<textarea
className="form-control form-group"
name="comment"
id="comment"
form={AbstractFeedbackForm.FORM_ID}
rows={8}
maxLength={2000}
placeholder={COMMENTS_PLACEHOLDER}
/>
</div>
<button className="btn btn-primary" type="submit">
{SUBMIT_TEXT}
</button>
......
......@@ -17,7 +17,7 @@ import {
} from 'components/Feedback/constants';
import globalState from 'fixtures/globalState';
import { RequestFeedbackForm, mapDispatchToProps, mapStateToProps } from '..';
import { RequestFeedbackForm, mapDispatchToProps, mapStateToProps } from '.';
describe('RequestFeedbackForm', () => {
const setup = () => {
......
......@@ -9,6 +9,7 @@ export const SUBMIT_SUCCESS_MESSAGE =
/* Button Set */
export const BUG_REPORT_TEXT = 'Bug Report';
export const FEEDBACK_BUTTON_TEXT = 'Give us Feedback';
export const FEEDBACK_TYPE_TEXT = 'Feedback Type Selector';
export const RATING_TEXT = 'Rating';
export const REQUEST_TEXT = 'Request';
......
......@@ -7,6 +7,7 @@ import RequestFeedbackForm from './FeedbackForm/RequestFeedbackForm';
import {
BUG_REPORT_TEXT,
FEEDBACK_BUTTON_TEXT,
BUTTON_CLOSE_TEXT,
FEEDBACK_TITLE,
FEEDBACK_TYPE_TEXT,
......@@ -81,7 +82,8 @@ export default class Feedback extends React.Component<
}`}
onClick={this.toggle}
>
<img className="icon icon-help" />
<span className="sr-only">{FEEDBACK_BUTTON_TEXT}</span>
<img className="icon icon-help" alt="" />
</button>
{this.state.isOpen && (
<div className="feedback-component">
......
......@@ -37,12 +37,12 @@ export class Footer extends React.Component<FooterProps> {
);
}
return (
<div>
<footer>
<div className="phantom-div" />
<div id="footer" className="footer">
{content}
</div>
</div>
</footer>
);
}
}
......
export const SEARCH_BREADCRUMB_TEXT = 'Advanced Search';
export const HOMEPAGE_TITLE = 'Amundsen Homepage';
......@@ -13,7 +13,7 @@ import { resetSearchState } from 'ducks/search/reducer';
import { UpdateSearchStateReset } from 'ducks/search/types';
import SearchBar from 'components/common/SearchBar';
import TagsList from 'components/common/TagsList';
import { SEARCH_BREADCRUMB_TEXT } from './constants';
import { SEARCH_BREADCRUMB_TEXT, HOMEPAGE_TITLE } from './constants';
export interface DispatchFromProps {
searchReset: () => UpdateSearchStateReset;
......@@ -28,9 +28,10 @@ export class HomePage extends React.Component<HomePageProps> {
render() {
return (
<div className="container home-page">
<main className="container home-page">
<div className="row">
<div className="col-xs-12 col-md-offset-1 col-md-10">
<h1 className="sr-only">{HOMEPAGE_TITLE}</h1>
<SearchBar />
<div className="filter-breadcrumb pull-right">
<Breadcrumb
......@@ -56,7 +57,7 @@ export class HomePage extends React.Component<HomePageProps> {
</div>
</div>
</div>
</div>
</main>
);
}
}
......
......@@ -69,7 +69,7 @@ export class NavBar extends React.Component<NavBarProps> {
render() {
return (
<div className="container-fluid">
<nav className="container-fluid">
<div className="row">
<div className="nav-bar">
<div id="nav-bar-left" className="nav-bar-left">
......@@ -79,6 +79,7 @@ export class NavBar extends React.Component<NavBarProps> {
id="logo-icon"
className="logo-icon"
src={AppConfig.logoPath}
alt=""
/>
)}
<span className="title-3">AMUNDSEN</span>
......@@ -130,7 +131,7 @@ export class NavBar extends React.Component<NavBarProps> {
</div>
</div>
</div>
</div>
</nav>
);
}
}
......
......@@ -12,7 +12,7 @@ const NotFoundPage: React.SFC<any> = () => {
<div className="container not-found-page">
<Breadcrumb path="/" text="Home" />
<h1>404 Page Not Found</h1>
<img className="icon icon-alert" />
<img className="icon icon-alert" alt="" />
</div>
</DocumentTitle>
);
......
......@@ -192,7 +192,7 @@ export class ProfilePage extends React.Component<
const { user } = this.props;
return (
<DocumentTitle title={`${user.display_name} - Amundsen Profile`}>
<div className="resource-detail-layout profile-page">
<main className="resource-detail-layout profile-page">
<header className="resource-header">
<div className="header-section">
<Breadcrumb />
......@@ -204,7 +204,7 @@ export class ProfilePage extends React.Component<
</div>
<div className="header-section header-title">
<h3 className="header-title-text truncated">
<h1 className="h3 header-title-text truncated">
{user.display_name}
{!user.is_active && (
<Flag
......@@ -213,7 +213,7 @@ export class ProfilePage extends React.Component<
text="Alumni"
/>
)}
</h3>
</h1>
<div className="body-3">
<ul className="header-bullets">
{user.role_name && <li id="user-role">{user.role_name}</li>}
......@@ -241,7 +241,7 @@ export class ProfilePage extends React.Component<
target="_blank"
rel="noreferrer"
>
<img className="icon icon-dark icon-mail" />
<img className="icon icon-dark icon-mail" alt="" />
<span className="email-link-label body-2">{user.email}</span>
</a>
)}
......@@ -267,19 +267,19 @@ export class ProfilePage extends React.Component<
target="_blank"
rel="noreferrer"
>
<img className="icon icon-dark icon-github" />
<img className="icon icon-dark icon-github" alt="" />
<span className="github-link-label body-2">Github</span>
</a>
)}
</div>
</header>
<main className="profile-body">
<div className="profile-body">
<TabsComponent
tabs={this.generateTabInfo()}
defaultTab={this.generateTabKey(ResourceType.table)}
/>
</main>
</div>
</div>
</main>
</DocumentTitle>
);
}
......
......@@ -68,7 +68,7 @@ export class FilterSection extends React.Component<FilterSectionProps> {
{hasValue && (
/* eslint-disable jsx-a11y/anchor-is-valid */
<a onClick={this.onClearFilter} className="btn btn-flat-icon">
<img className="icon icon-left" />
<img className="icon icon-left" alt="" />
<span>{CLEAR_BTN_TEXT}</span>
</a>
)}
......
......@@ -8,7 +8,7 @@ type SearchPanelProps = {
const SearchPanel: React.SFC = ({ children }: SearchPanelProps) => {
return (
<div className="search-control-panel">
<aside className="search-control-panel">
{React.Children.map(children, (child, index) => {
return (
<div key={`search-panel-child:${index}`} className="section">
......@@ -16,7 +16,7 @@ const SearchPanel: React.SFC = ({ children }: SearchPanelProps) => {
</div>
);
})}
</div>
</aside>
);
};
......
......@@ -6,6 +6,7 @@ export const RESULTS_PER_PAGE = 10;
// TODO: Hard-coded text strings should be translatable/customizable
export const DOCUMENT_TITLE_SUFFIX = ' - Amundsen Search';
export const SEARCHPAGE_TITLE = 'Amundsen Search';
export const PAGE_INDEX_ERROR_MESSAGE =
'Page index out of bounds for available matches';
......
......@@ -37,6 +37,7 @@ import {
DASHBOARD_RESOURCE_TITLE,
TABLE_RESOURCE_TITLE,
USER_RESOURCE_TITLE,
SEARCHPAGE_TITLE,
} from './constants';
export interface StateFromProps {
......@@ -169,7 +170,10 @@ export class SearchPage extends React.Component<SearchPageProps> {
<ResourceSelector />
<SearchFilter />
</SearchPanel>
<div className="search-results">{this.renderContent()}</div>
<main className="search-results">
<h1 className="sr-only">{SEARCHPAGE_TITLE}</h1>
{this.renderContent()}
</main>
</div>
);
if (searchTerm.length > 0) {
......
......@@ -14,6 +14,8 @@ import { RequestMetadataType, TableColumn } from 'interfaces';
import './styles.scss';
import EditableSection from 'components/common/EditableSection';
const MORE_BUTTON_TEXT = 'More options';
interface DispatchFromProps {
openRequestDescriptionDialog: (
requestMetadataType: RequestMetadataType,
......@@ -149,7 +151,8 @@ export class ColumnListItem extends React.Component<
className="column-dropdown"
>
<Dropdown.Toggle noCaret>
<img className="icon icon-more" />
<span className="sr-only">{MORE_BUTTON_TEXT}</span>
<img className="icon icon-more" alt="" />
</Dropdown.Toggle>
<Dropdown.Menu>
<MenuItem onClick={this.openRequest}>
......
......@@ -35,8 +35,8 @@ export function renderReader(
>
<Link
className="avatar-overlap"
id="frequent-users"
onClick={logClick}
data-type="frequent-users"
to={link}
target={target}
>
......
......@@ -196,7 +196,7 @@ export class OwnerEditor extends React.Component<
onClick={() => this.recordDeleteItem(key)}
/* tslint:enable */
>
<img className="icon icon-delete" />
<img className="icon icon-delete" alt="" />
</button>
</li>
);
......@@ -268,7 +268,7 @@ export class OwnerEditor extends React.Component<
className="btn btn-flat-icon add-item-button"
onClick={this.handleShow}
>
<img className="icon icon-plus-circle" />
<img className="icon icon-plus-circle" alt="" />
<span>Add Owner</span>
</button>
)}
......
......@@ -92,9 +92,9 @@ describe('ReportTableIssue', () => {
const { wrapper } = setup();
const previsOpenState = wrapper.state().isOpen;
wrapper
.instance()
.toggle({ currentTarget: { id: 'id', nodeName: 'button' } });
wrapper.instance().toggle({
currentTarget: { id: 'id', nodeName: 'button' },
});
expect(setStateSpy).toHaveBeenCalledWith({ isOpen: !previsOpenState });
});
......
......@@ -232,7 +232,7 @@ export class RequestMetadataForm extends React.Component<
className="btn btn-primary submit-request-button"
type="submit"
>
<img className="icon icon-send" />
<img className="icon icon-send" alt="" />
{SEND_BUTTON}
</button>
</form>
......@@ -258,9 +258,11 @@ export const mapStateToProps = (state: GlobalState) => {
tableOwners: Object.keys(ownerObj),
};
if (columnName) {
// eslint-disable-next-line @typescript-eslint/dot-notation
mappedProps['columnName'] = columnName;
}
if (requestMetadataType) {
// eslint-disable-next-line @typescript-eslint/dot-notation
mappedProps['requestMetadataType'] = requestMetadataType;
}
return mappedProps;
......
......@@ -83,9 +83,12 @@ describe('WatermarkLabel', () => {
'2018-08-03',
'2019-10-15'
);
expect(mount(watermarkInfo).find('.range-dates').text()).toBe(
'Aug 03, 2018Oct 15, 2019'
);
const watermark = mount(watermarkInfo);
const startTime = watermark.find('.date-range-value').at(0).text();
const endTime = watermark.find('.date-range-value').at(1).text();
expect(startTime).toBe('Aug 03, 2018');
expect(endTime).toBe('Oct 15, 2019');
});
});
......
......@@ -43,18 +43,24 @@ class WatermarkLabel extends React.Component<WatermarkLabelProps> {
}
return (
<>
<div className="range-labels body-2">
{LOW_WATERMARK_LABEL}
<br />
{HIGH_WATERMARK_LABEL}
</div>
<div className="range-dates body-2">
{low && this.formatWatermarkDate(low)}
<br />
{high && this.formatWatermarkDate(high)}
</div>
</>
<div className="date-ranges">
{low && (
<p className="date-range body-2">
<span className="date-range-label">{LOW_WATERMARK_LABEL}</span>
<time className="date-range-value">
{this.formatWatermarkDate(low)}
</time>
</p>
)}
{high && (
<p className="date-range body-2">
<span className="date-range-label">{HIGH_WATERMARK_LABEL}</span>
<time className="date-range-value">
{this.formatWatermarkDate(high)}
</time>
</p>
)}
</div>
);
};
......@@ -64,7 +70,11 @@ class WatermarkLabel extends React.Component<WatermarkLabelProps> {
return (
<div className="watermark-label">
<img className="range-icon" src="/static/images/watermark-range.png" />
<img
className="range-icon"
src="/static/images/watermark-range.png"
alt=""
/>
{this.renderWatermarkInfo(low, high)}
</div>
);
......
@import 'variables';
$date-range-label-width: 45px;
.watermark-label {
display: flex;
.range-icon {
flex-basis: 12px;
height: 40px;
margin-right: 8px;
margin-right: $spacer-1;
width: 12px;
margin-top: 2px;
}
.range-labels {
flex-basis: 42px;
}
.date-range {
margin-top: 0;
margin-bottom: $spacer-1/2;
}
.date-range-label {
width: $date-range-label-width;
display: inline-block;
}
.date-range-value {
margin-left: $spacer-1/2;
}
}
......@@ -180,9 +180,9 @@ export class TableDetail extends React.Component<
/>
</div>
<div className="header-section header-title">
<h3 className="header-title-text truncated">
<h1 className="h3 header-title-text truncated">
{this.getDisplayName()}
</h3>
</h1>
<BookmarkIcon
bookmarkKey={data.key}
resourceType={ResourceType.table}
......@@ -208,8 +208,8 @@ export class TableDetail extends React.Component<
<ExploreButton tableData={data} />
</div>
</header>
<article className="column-layout-1">
<section className="left-panel">
<div className="column-layout-1">
<aside className="left-panel">
<EditableSection title="Description">
<TableDescEditableText
maxLength={AppConfig.editableText.tableDescLength}
......@@ -239,11 +239,11 @@ export class TableDetail extends React.Component<
{!!data.last_updated_timestamp && (
<section className="metadata-section">
<div className="section-title title-3">Last Updated</div>
<div className="body-2">
<time className="body-2">
{formatDateTimeShort({
epochTimestamp: data.last_updated_timestamp,
})}
</div>
</time>
</section>
)}
<section className="metadata-section">
......@@ -283,9 +283,9 @@ export class TableDetail extends React.Component<
</EditableSection>
</section>
))}
</section>
<section className="right-panel">{this.renderTabs()}</section>
</article>
</aside>
<main className="right-panel">{this.renderTabs()}</main>
</div>
</div>
);
}
......
......@@ -63,7 +63,6 @@ describe('TagInfo', () => {
it('renders a button with correct props', () => {
expect(wrapper.find('button').props()).toMatchObject({
id: `tag::${props.data.tag_name}`,
role: 'button',
onClick: wrapper.instance().onClick,
className: 'btn tag-button',
});
......
......@@ -40,7 +40,6 @@ export class TagInfo extends React.Component<TagInfoProps> {
return (
<button
id={`tag::${name}`}
role="button"
className={'btn tag-button' + (this.props.compact ? ' compact' : '')}
onClick={this.onClick}
>
......
......@@ -275,7 +275,7 @@ class TagInput extends React.Component<TagInputProps, TagInputState> {
className="btn btn-default muted add-btn"
onClick={this.startEditing}
>
<img className="icon icon-plus" />
<img className="icon icon-plus" alt="" />
New
</button>
);
......
......@@ -62,6 +62,7 @@ export class BookmarkIcon extends React.Component<BookmarkIconProps> {
'icon ' +
(this.props.isBookmarked ? 'icon-bookmark-filled' : 'icon-bookmark')
}
alt=""
/>
</div>
);
......
......@@ -29,9 +29,9 @@ export const Breadcrumb: React.SFC<BreadcrumbProps> = (
return (
<div className="amundsen-breadcrumb">
<Link to={path} className="btn btn-flat-icon title-3">
{direction === 'left' && <img className="icon icon-left" />}
{direction === 'left' && <img className="icon icon-left" alt="" />}
<span>{text}</span>
{direction === 'right' && <img className="icon icon-right" />}
{direction === 'right' && <img className="icon icon-right" alt="" />}
</Link>
</div>
);
......@@ -43,8 +43,8 @@ export const Breadcrumb: React.SFC<BreadcrumbProps> = (
onClick={props.loadPreviousSearch}
className="btn btn-flat-icon title-3"
>
{direction === 'left' && <img className="icon icon-left" />}
{direction === 'right' && <img className="icon icon-right" />}
{direction === 'left' && <img className="icon icon-left" alt="" />}
{direction === 'right' && <img className="icon icon-right" alt="" />}
</a>
</div>
);
......
......@@ -75,7 +75,8 @@ describe('EditableSection', () => {
it('renders children as-is for non-react elements', () => {
const child = 'non-react-child';
const { wrapper } = setup(null, child);
expect(wrapper.childAt(1).text()).toBe(child);
expect(wrapper.find('.editable-section-content').text()).toBe(child);
});
it('renders edit button correctly when readOnly=false', () => {
......
......@@ -62,10 +62,12 @@ export class EditableSection extends React.Component<
}`}
onClick={this.toggleEdit}
>
<span className="sr-only">{Constants.EDIT_TEXT}</span>
<img
className={`icon icon-small icon-edit ${
this.state.isEditing ? 'icon-color' : ''
}`}
alt=""
/>
</button>
);
......@@ -92,7 +94,8 @@ export class EditableSection extends React.Component<
target="_blank"
rel="noopener noreferrer"
>
<img className="icon icon-small icon-edit" />
<span className="sr-only">{Constants.EDIT_TEXT}</span>
<img className="icon icon-small icon-edit" alt="" />
</a>
</OverlayTrigger>
);
......@@ -105,6 +108,7 @@ export class EditableSection extends React.Component<
if (!React.isValidElement(child)) {
return child;
}
return React.cloneElement(child, {
isEditing: this.state.isEditing,
setEditMode: this.setEditMode,
......@@ -114,11 +118,15 @@ export class EditableSection extends React.Component<
return (
<section className="editable-section">
<div className="section-title title-3">
{EditableSection.convertText(title)}
{!readOnly ? this.renderButton() : this.renderReadOnlyButton()}
</div>
{childrenWithProps}
<label className="editable-section-label">
<div className="editable-section-label-wrapper">
<span className="section-title title-3">
{EditableSection.convertText(title)}
</span>
{!readOnly ? this.renderButton() : this.renderReadOnlyButton()}
</div>
<div className="editable-section-content">{childrenWithProps}</div>
</label>
</section>
);
}
......
@import 'variables';
.editable-section {
.editable-section-label {
display: block;
font-weight: 400;
margin-bottom: 0;
}
.editable-section-label-wrapper {
margin-bottom: $spacer-1;
}
.section-title {
color: $text-tertiary;
margin-bottom: 4px;
margin-bottom: $spacer-1/2;
font-weight: 700;
}
.edit-button {
margin-left: 4px;
margin-left: $spacer-1/2;
opacity: 0;
img {
......
......@@ -156,7 +156,7 @@ class EditableText extends React.Component<
className="btn btn-primary refresh-button"
onClick={this.refreshText}
>
<img className="icon icon-refresh" />
<img className="icon icon-refresh" alt="" />
{REFRESH_BUTTON_TEXT}
</button>
</>
......
......@@ -15,7 +15,7 @@ const FlashMessage: React.SFC<FlashMessageProps> = ({
}: FlashMessageProps) => {
return (
<div className="flash-message">
{iconClass && <img className={`icon ${iconClass}`} />}
{iconClass && <img className={`icon ${iconClass}`} alt="" />}
<div className="message">{message}</div>
<button
type="button"
......
......@@ -7,6 +7,8 @@ import SanitizedHTML from 'react-sanitized-html';
// TODO: Use css-modules instead of 'import'
import './styles.scss';
const INFO_BUTTON_TEXT = 'More info';
export interface InfoButtonProps {
infoText?: string;
title?: string;
......@@ -32,7 +34,9 @@ const InfoButton: React.SFC<InfoButtonProps> = ({
placement={placement}
overlay={popoverHoverFocus}
>
<button className={'btn icon info-button ' + size} />
<button className={'btn icon info-button ' + size} type="button">
<span className="sr-only">{INFO_BUTTON_TEXT}</span>
</button>
</OverlayTrigger>
);
};
......
......@@ -73,15 +73,15 @@ class DashboardListItem extends React.Component<DashboardListItemProps, {}> {
<div className="resource-badges">
<div>
<div className="title-3">{Constants.LAST_RUN_TITLE}</div>
<div className="body-secondary-3">
<time className="body-secondary-3">
{dashboard.last_successful_run_timestamp
? formatDate({
epochTimestamp: dashboard.last_successful_run_timestamp,
})
: NO_TIMESTAMP_TEXT}
</div>
</time>
</div>
<img className="icon icon-right" />
<img className="icon icon-right" alt="" />
</div>
</Link>
</li>
......
......@@ -82,7 +82,7 @@ class TableListItem extends React.Component<TableListItemProps, {}> {
</div>
</div>
)}
<img className="icon icon-right" />
<img className="icon icon-right" alt="" />
</div>
</Link>
</li>
......
......@@ -53,7 +53,7 @@ class UserListItem extends React.Component<UserListItemProps, {}> {
<div className="resource-type">User</div>
<div className="resource-badges">
{!user.is_active && <Flag text="Alumni" labelStyle="danger" />}
<img className="icon icon-right" />
<img className="icon icon-right" alt="" />
</div>
</Link>
</li>
......
......@@ -56,7 +56,7 @@ export class SearchItem extends React.Component<SearchItemProps, {}> {
onClick={this.onViewAllResults}
target="_blank"
>
<img className="icon icon-search" />
<img className="icon icon-search" alt="" />
<div className="title-2 search-item-info">
<div className="search-term">{`${searchTerm}\u00a0`}</div>
<div className="search-item-text">{listItemText}</div>
......
// TODO: Hard-coded text strings should be translatable/customizable
export const PLACEHOLDER_DEFAULT = 'search for data resources...';
export const SEARCH_BUTTON_TEXT = 'Search';
export const BUTTON_CLOSE_TEXT = 'Close';
export const SIZE_SMALL = 'small';
......
......@@ -24,6 +24,7 @@ import './styles.scss';
import {
BUTTON_CLOSE_TEXT,
SEARCH_BUTTON_TEXT,
INVALID_SYNTAX_MESSAGE,
PLACEHOLDER_DEFAULT,
SIZE_SMALL,
......@@ -207,7 +208,8 @@ export class SearchBar extends React.Component<SearchBarProps, SearchBarState> {
autoComplete="off"
/>
<button className={searchButtonClass} type="submit">
<img className="icon icon-search" />
<span className="sr-only">{SEARCH_BUTTON_TEXT}</span>
<img className="icon icon-search" alt="" />
</button>
{this.props.size === SIZE_SMALL && (
<button
......
......@@ -117,6 +117,7 @@ export default function reducer(
};
const { columnName } = (<OpenRequestAction>action).payload;
if (columnName) {
// eslint-disable-next-line @typescript-eslint/dot-notation
newState['columnName'] = columnName;
}
return newState;
......
......@@ -40,7 +40,8 @@ export function logClick(
const target = event.currentTarget;
const inferredProps: ActionLogParams = {
command: 'click',
target_id: target.id,
target_id:
target.dataset && target.dataset.type ? target.dataset.type : target.id,
label: target.innerText || target.textContent,
};
......
......@@ -40,7 +40,7 @@ ReactDOM.render(
<DocumentTitle title="Amundsen - Data Discovery Portal">
<Provider store={store}>
<Router history={BrowserHistory}>
<main id="main">
<div id="main">
<Preloader />
<Route component={NavBar} />
<Switch>
......@@ -57,7 +57,7 @@ ReactDOM.render(
<Route path="/" component={HomePage} />
</Switch>
<Footer />
</main>
</div>
</Router>
</Provider>
</DocumentTitle>,
......
......@@ -32,6 +32,7 @@ export const generateSearchUrl = (searchParams: SearchParams): string => {
index: searchParams.index,
};
if (hasFilters) {
// eslint-disable-next-line @typescript-eslint/dot-notation
queryStringValues['filters'] = filtersForResource;
}
......
......@@ -68,7 +68,6 @@
"enzyme-adapter-react-16": "^1.15.2",
"enzyme-to-json": "^3.4.4",
"eslint": "^7.2.0",
"eslint-config-airbnb": "^18.1.0",
"eslint-config-airbnb-typescript": "^8.0.2",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.21.1",
......@@ -201,8 +200,9 @@
"extends": [
"plugin:react/recommended",
"airbnb-typescript",
"prettier",
"prettier/@typescript-eslint"
"prettier/@typescript-eslint",
"plugin:prettier/recommended",
"prettier/react"
],
"ignorePatterns": [
"**/*{.,-}min.js",
......@@ -283,6 +283,7 @@
}
}
],
"@typescript-eslint/indent": "off",
"arrow-parens": [
"off",
"always"
......@@ -389,6 +390,7 @@
"react/no-unescaped-entities": 0,
"react/no-unused-prop-types": 0,
"react/no-string-refs": 0,
"react/jsx-indent": 0,
"indent": 0,
"no-multi-spaces": 0,
"padded-blocks": 0,
......@@ -428,7 +430,7 @@
"react/jsx-closing-tag-location": "warn",
"react/no-did-update-set-state": "warn",
"react/no-access-state-in-setstate": "warn",
"react/jsx-one-expression-per-line": "warn",
"react/jsx-one-expression-per-line": "off",
"react/sort-comp": "warn",
"react/jsx-props-no-spreading": "warn",
"react/prefer-stateless-function": "warn",
......@@ -520,10 +522,25 @@
}
},
"lint-staged": {
"*.js": "eslint --fix",
"*.ts": "eslint --fix",
"*.tsx": "eslint --fix",
"*.scss": "npm run stylelint-fix",
"*.{ts,tsx,css,scss}": "prettier --write"
"*.js": [
"prettier --write",
"eslint --fix"
],
"*.jsx": [
"prettier --write",
"eslint --fix"
],
"*.ts": [
"prettier --write",
"eslint --fix"
],
"*.tsx": [
"prettier --write",
"eslint --fix"
],
"*.scss": [
"prettier --write",
"npm run stylelint-fix"
]
}
}
<!doctype html>
<html lang="en-US">
<head>
{% if <%= htmlWebpackPlugin.options.config.google.enabled%> %}
{% include 'fragments/google-analytics-loader.html' %}
{% endif %}
<meta charset="utf-8">
<title>Amundsen - Data Discovery Portal</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% if env == "development" %}
{% include 'fragments/icons-dev.html' %}
{% elif env == "staging" %}
......@@ -18,6 +13,10 @@
{% include 'fragments/icons-prod.html' %}
{% endif %}
{% if <%= htmlWebpackPlugin.options.config.google.enabled%> %}
{% include 'fragments/google-analytics-loader.html' %}
{% endif %}
<%= htmlWebpackPlugin.tags.headTags %>
</head>
<body class="{{ env }}">
......
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