Merge pull request #4770 from AlexSCorey/Pluralization

Requires individual components to pluralize

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2019-09-24 15:55:11 +00:00
committed by GitHub
16 changed files with 33 additions and 95 deletions

View File

@@ -212,7 +212,6 @@ class AddResourceRole extends React.Component {
selectedLabel={i18n._(t`Selected`)} selectedLabel={i18n._(t`Selected`)}
selectedResourceRows={selectedResourceRows} selectedResourceRows={selectedResourceRows}
sortedColumnKey="username" sortedColumnKey="username"
itemName="user"
/> />
)} )}
{selectedResource === 'teams' && ( {selectedResource === 'teams' && (
@@ -222,7 +221,6 @@ class AddResourceRole extends React.Component {
onSearch={readTeams} onSearch={readTeams}
selectedLabel={i18n._(t`Selected`)} selectedLabel={i18n._(t`Selected`)}
selectedResourceRows={selectedResourceRows} selectedResourceRows={selectedResourceRows}
itemName="team"
/> />
)} )}
</Fragment> </Fragment>

View File

@@ -74,7 +74,6 @@ class SelectResourceStep extends React.Component {
onRowClick, onRowClick,
selectedLabel, selectedLabel,
selectedResourceRows, selectedResourceRows,
itemName,
i18n, i18n,
} = this.props; } = this.props;
@@ -100,7 +99,6 @@ class SelectResourceStep extends React.Component {
<PaginatedDataList <PaginatedDataList
items={resources} items={resources}
itemCount={count} itemCount={count}
itemName={itemName}
qsConfig={this.qsConfig} qsConfig={this.qsConfig}
toolbarColumns={columns} toolbarColumns={columns}
renderItem={item => ( renderItem={item => (
@@ -132,7 +130,6 @@ SelectResourceStep.propTypes = {
selectedLabel: PropTypes.string, selectedLabel: PropTypes.string,
selectedResourceRows: PropTypes.arrayOf(PropTypes.object), selectedResourceRows: PropTypes.arrayOf(PropTypes.object),
sortedColumnKey: PropTypes.string, sortedColumnKey: PropTypes.string,
itemName: PropTypes.string,
}; };
SelectResourceStep.defaultProps = { SelectResourceStep.defaultProps = {
@@ -141,7 +138,6 @@ SelectResourceStep.defaultProps = {
selectedLabel: null, selectedLabel: null,
selectedResourceRows: [], selectedResourceRows: [],
sortedColumnKey: 'name', sortedColumnKey: 'name',
itemName: 'item',
}; };
export { SelectResourceStep as _SelectResourceStep }; export { SelectResourceStep as _SelectResourceStep };

View File

@@ -212,7 +212,7 @@ class Lookup extends React.Component {
i18n, i18n,
} = this.props; } = this.props;
const header = lookupHeader || i18n._(t`items`); const header = lookupHeader || i18n._(t`Items`);
const canDelete = !required || (multiple && value.length > 1); const canDelete = !required || (multiple && value.length > 1);
const chips = value ? ( const chips = value ? (
@@ -268,8 +268,7 @@ class Lookup extends React.Component {
<PaginatedDataList <PaginatedDataList
items={results} items={results}
itemCount={count} itemCount={count}
itemName={lookupHeader} pluralizedItemName={lookupHeader}
itemNamePlural={lookupHeader}
qsConfig={this.qsConfig} qsConfig={this.qsConfig}
toolbarColumns={columns} toolbarColumns={columns}
renderItem={item => ( renderItem={item => (

View File

@@ -17,7 +17,6 @@ import {
parseQueryString, parseQueryString,
replaceParams, replaceParams,
} from '@util/qs'; } from '@util/qs';
import { pluralize, ucFirst } from '@util/strings';
import { QSConfig } from '@types'; import { QSConfig } from '@types';
@@ -61,8 +60,7 @@ class PaginatedDataList extends React.Component {
qsConfig, qsConfig,
renderItem, renderItem,
toolbarColumns, toolbarColumns,
itemName, pluralizedItemName,
itemNamePlural,
showPageSizeOptions, showPageSizeOptions,
location, location,
i18n, i18n,
@@ -80,16 +78,11 @@ class PaginatedDataList extends React.Component {
]; ];
const queryParams = parseQueryString(qsConfig, location.search); const queryParams = parseQueryString(qsConfig, location.search);
const itemDisplayName = ucFirst(pluralize(itemName)); const dataListLabel = i18n._(t`${pluralizedItemName} List`);
const itemDisplayNamePlural = ucFirst(
itemNamePlural || pluralize(itemName)
);
const dataListLabel = i18n._(t`${itemDisplayName} List`);
const emptyContentMessage = i18n._( const emptyContentMessage = i18n._(
t`Please add ${itemDisplayNamePlural} to populate this list ` t`Please add ${pluralizedItemName} to populate this list `
); );
const emptyContentTitle = i18n._(t`No ${itemDisplayNamePlural} Found `); const emptyContentTitle = i18n._(t`No ${pluralizedItemName} Found `);
let Content; let Content;
if (hasContentLoading && items.length <= 0) { if (hasContentLoading && items.length <= 0) {
@@ -161,8 +154,7 @@ const Item = PropTypes.shape({
PaginatedDataList.propTypes = { PaginatedDataList.propTypes = {
items: PropTypes.arrayOf(Item).isRequired, items: PropTypes.arrayOf(Item).isRequired,
itemCount: PropTypes.number.isRequired, itemCount: PropTypes.number.isRequired,
itemName: PropTypes.string, pluralizedItemName: PropTypes.string,
itemNamePlural: PropTypes.string,
qsConfig: QSConfig.isRequired, qsConfig: QSConfig.isRequired,
renderItem: PropTypes.func, renderItem: PropTypes.func,
toolbarColumns: arrayOf( toolbarColumns: arrayOf(
@@ -182,8 +174,7 @@ PaginatedDataList.defaultProps = {
hasContentLoading: false, hasContentLoading: false,
contentError: null, contentError: null,
toolbarColumns: [], toolbarColumns: [],
itemName: 'item', pluralizedItemName: 'Items',
itemNamePlural: '',
showPageSizeOptions: true, showPageSizeOptions: true,
renderItem: item => <PaginatedDataListItem key={item.id} item={item} />, renderItem: item => <PaginatedDataListItem key={item.id} item={item} />,
renderToolbar: props => <DataListToolbar {...props} />, renderToolbar: props => <DataListToolbar {...props} />,

View File

@@ -6,7 +6,6 @@ import styled from 'styled-components';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import AlertModal from '../AlertModal'; import AlertModal from '../AlertModal';
import { pluralize } from '../../util/strings';
const DeleteButton = styled(Button)` const DeleteButton = styled(Button)`
padding: 5px 8px; padding: 5px 8px;
@@ -41,11 +40,11 @@ class ToolbarDeleteButton extends React.Component {
static propTypes = { static propTypes = {
onDelete: func.isRequired, onDelete: func.isRequired,
itemsToDelete: arrayOf(ItemToDelete).isRequired, itemsToDelete: arrayOf(ItemToDelete).isRequired,
itemName: string, pluralizedItemName: string,
}; };
static defaultProps = { static defaultProps = {
itemName: 'item', pluralizedItemName: 'Items',
}; };
constructor(props) { constructor(props) {
@@ -75,7 +74,7 @@ class ToolbarDeleteButton extends React.Component {
} }
renderTooltip() { renderTooltip() {
const { itemsToDelete, itemName, i18n } = this.props; const { itemsToDelete, pluralizedItemName, i18n } = this.props;
const itemsUnableToDelete = itemsToDelete const itemsUnableToDelete = itemsToDelete
.filter(cannotDelete) .filter(cannotDelete)
@@ -85,9 +84,7 @@ class ToolbarDeleteButton extends React.Component {
return ( return (
<div> <div>
{i18n._( {i18n._(
t`You do not have permission to delete the following ${pluralize( t`You do not have permission to delete the following ${pluralizedItemName}: ${itemsUnableToDelete}`
itemName
)}: ${itemsUnableToDelete}`
)} )}
</div> </div>
); );
@@ -99,7 +96,7 @@ class ToolbarDeleteButton extends React.Component {
} }
render() { render() {
const { itemsToDelete, itemName, i18n } = this.props; const { itemsToDelete, pluralizedItemName, i18n } = this.props;
const { isModalOpen } = this.state; const { isModalOpen } = this.state;
const isDisabled = const isDisabled =
@@ -125,11 +122,7 @@ class ToolbarDeleteButton extends React.Component {
{isModalOpen && ( {isModalOpen && (
<AlertModal <AlertModal
variant="danger" variant="danger"
title={ title={pluralizedItemName}
itemsToDelete === 1
? i18n._(t`Delete ${itemName}`)
: i18n._(t`Delete ${pluralize(itemName)}`)
}
isOpen={isModalOpen} isOpen={isModalOpen}
onClose={this.handleCancelDelete} onClose={this.handleCancelDelete}
actions={[ actions={[

View File

@@ -3,9 +3,9 @@
exports[`<ToolbarDeleteButton /> should render button 1`] = ` exports[`<ToolbarDeleteButton /> should render button 1`] = `
<ToolbarDeleteButton <ToolbarDeleteButton
i18n={"/i18n/"} i18n={"/i18n/"}
itemName="item"
itemsToDelete={Array []} itemsToDelete={Array []}
onDelete={[Function]} onDelete={[Function]}
pluralizedItemName="Items"
> >
<Tooltip <Tooltip
appendTo={[Function]} appendTo={[Function]}

View File

@@ -151,7 +151,6 @@ class JobList extends Component {
} = this.state; } = this.state;
const { match, i18n } = this.props; const { match, i18n } = this.props;
const isAllSelected = selected.length === jobs.length; const isAllSelected = selected.length === jobs.length;
const itemName = i18n._(t`Job`);
return ( return (
<PageSection> <PageSection>
<Card> <Card>
@@ -160,7 +159,7 @@ class JobList extends Component {
hasContentLoading={hasContentLoading} hasContentLoading={hasContentLoading}
items={jobs} items={jobs}
itemCount={itemCount} itemCount={itemCount}
itemName={itemName} pluralizedItemName="Jobs"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
toolbarColumns={[ toolbarColumns={[
{ {
@@ -189,7 +188,7 @@ class JobList extends Component {
key="delete" key="delete"
onDelete={this.handleJobDelete} onDelete={this.handleJobDelete}
itemsToDelete={selected} itemsToDelete={selected}
itemName={itemName} pluralizedItemName="Jobs"
/>, />,
]} ]}
/> />

View File

@@ -165,7 +165,7 @@ class OrganizationAccess extends React.Component {
hasContentLoading={hasContentLoading} hasContentLoading={hasContentLoading}
items={accessRecords} items={accessRecords}
itemCount={itemCount} itemCount={itemCount}
itemName="role" pluralizedItemName="Roles"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
toolbarColumns={[ toolbarColumns={[
{ {

View File

@@ -37,8 +37,8 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
error={null} error={null}
hasContentLoading={true} hasContentLoading={true}
itemCount={0} itemCount={0}
itemName="role"
items={Array []} items={Array []}
pluralizedItemName="Roles"
qsConfig={ qsConfig={
Object { Object {
"dateFields": Array [ "dateFields": Array [
@@ -91,8 +91,8 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
hasContentLoading={true} hasContentLoading={true}
i18n={"/i18n/"} i18n={"/i18n/"}
itemCount={0} itemCount={0}
itemName="role"
items={Array []} items={Array []}
pluralizedItemName="Roles"
qsConfig={ qsConfig={
Object { Object {
"dateFields": Array [ "dateFields": Array [
@@ -144,8 +144,6 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
history={"/history/"} history={"/history/"}
i18n={"/i18n/"} i18n={"/i18n/"}
itemCount={0} itemCount={0}
itemName="role"
itemNamePlural=""
items={Array []} items={Array []}
location={ location={
Object { Object {
@@ -163,6 +161,7 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
"url": "", "url": "",
} }
} }
pluralizedItemName="Roles"
qsConfig={ qsConfig={
Object { Object {
"dateFields": Array [ "dateFields": Array [

View File

@@ -153,7 +153,7 @@ class OrganizationsList extends Component {
hasContentLoading={hasContentLoading} hasContentLoading={hasContentLoading}
items={organizations} items={organizations}
itemCount={itemCount} itemCount={itemCount}
itemName="organization" pluralizedItemName="Organizations"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
toolbarColumns={[ toolbarColumns={[
{ {
@@ -187,7 +187,7 @@ class OrganizationsList extends Component {
key="delete" key="delete"
onDelete={this.handleOrgDelete} onDelete={this.handleOrgDelete}
itemsToDelete={selected} itemsToDelete={selected}
itemName="Organization" pluralizedItemName="Organizations"
/>, />,
canAdd ? ( canAdd ? (
<ToolbarAddButton key="add" linkTo={`${match.url}/add`} /> <ToolbarAddButton key="add" linkTo={`${match.url}/add`} />

View File

@@ -204,7 +204,7 @@ class OrganizationNotifications extends Component {
hasContentLoading={hasContentLoading} hasContentLoading={hasContentLoading}
items={notifications} items={notifications}
itemCount={itemCount} itemCount={itemCount}
itemName="notification" pluralizedItemName="Notifications"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
toolbarColumns={COLUMNS} toolbarColumns={COLUMNS}
renderItem={notification => ( renderItem={notification => (

View File

@@ -42,7 +42,6 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
contentError={null} contentError={null}
hasContentLoading={false} hasContentLoading={false}
itemCount={2} itemCount={2}
itemName="notification"
items={ items={
Array [ Array [
Object { Object {
@@ -65,6 +64,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
}, },
] ]
} }
pluralizedItemName="Notifications"
qsConfig={ qsConfig={
Object { Object {
"dateFields": Array [ "dateFields": Array [
@@ -116,7 +116,6 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
hasContentLoading={false} hasContentLoading={false}
i18n={"/i18n/"} i18n={"/i18n/"}
itemCount={2} itemCount={2}
itemName="notification"
items={ items={
Array [ Array [
Object { Object {
@@ -139,6 +138,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
}, },
] ]
} }
pluralizedItemName="Notifications"
qsConfig={ qsConfig={
Object { Object {
"dateFields": Array [ "dateFields": Array [
@@ -188,8 +188,6 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
history={"/history/"} history={"/history/"}
i18n={"/i18n/"} i18n={"/i18n/"}
itemCount={2} itemCount={2}
itemName="notification"
itemNamePlural=""
items={ items={
Array [ Array [
Object { Object {
@@ -228,6 +226,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"url": "", "url": "",
} }
} }
pluralizedItemName="Notifications"
qsConfig={ qsConfig={
Object { Object {
"dateFields": Array [ "dateFields": Array [
@@ -1828,10 +1827,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
</Route> </Route>
</withRouter(ListHeader)> </withRouter(ListHeader)>
<DataList <DataList
aria-label="{itemDisplayName} List" aria-label="{pluralizedItemName} List"
> >
<ul <ul
aria-label="{itemDisplayName} List" aria-label="{pluralizedItemName} List"
className="pf-c-data-list" className="pf-c-data-list"
> >
<WithI18n <WithI18n

View File

@@ -65,7 +65,7 @@ class OrganizationTeams extends React.Component {
hasContentLoading={hasContentLoading} hasContentLoading={hasContentLoading}
items={teams} items={teams}
itemCount={itemCount} itemCount={itemCount}
itemName="team" pluralizedItemName="Notifications"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
/> />
); );

View File

@@ -178,7 +178,7 @@ class TemplatesList extends Component {
hasContentLoading={hasContentLoading} hasContentLoading={hasContentLoading}
items={templates} items={templates}
itemCount={itemCount} itemCount={itemCount}
itemName={i18n._(t`Template`)} pluralizedItemName="Templates"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
toolbarColumns={[ toolbarColumns={[
{ {
@@ -213,7 +213,7 @@ class TemplatesList extends Component {
key="delete" key="delete"
onDelete={this.handleTemplateDelete} onDelete={this.handleTemplateDelete}
itemsToDelete={selected} itemsToDelete={selected}
itemName={i18n._(t`Template`)} pluralizedItemName="Templates"
/>, />,
canAdd && ( canAdd && (
<Dropdown <Dropdown

View File

@@ -1,15 +1,3 @@
// TODO: switch to using Lingui i18n for pluralization
export function pluralize(str) {
const lastChar = str[str.length - 1];
if (lastChar === 's') {
return `${str}es`;
}
if (lastChar === 'y') {
return `${str.substr(0, str.length - 1)}ies`;
}
return `${str}s`;
}
// TODO: switch to using Lingui i18n for articles // TODO: switch to using Lingui i18n for articles
export function getArticle(str) { export function getArticle(str) {
const first = str[0]; const first = str[0];
@@ -19,10 +7,6 @@ export function getArticle(str) {
return 'a'; return 'a';
} }
export function ucFirst(str) {
return `${str[0].toUpperCase()}${str.substr(1)}`;
}
export const toTitleCase = string => { export const toTitleCase = string => {
if (!string) { if (!string) {
return ''; return '';

View File

@@ -1,20 +1,6 @@
import { pluralize, getArticle, ucFirst, toTitleCase } from './strings'; import { getArticle, toTitleCase } from './strings';
describe('string utils', () => { describe('string utils', () => {
describe('pluralize', () => {
test('should add an "s"', () => {
expect(pluralize('team')).toEqual('teams');
});
test('should add an "es"', () => {
expect(pluralize('class')).toEqual('classes');
});
test('should handle word ending in y', () => {
expect(pluralize('inventory')).toEqual('inventories');
});
});
describe('getArticle', () => { describe('getArticle', () => {
test('should return "a"', () => { test('should return "a"', () => {
expect(getArticle('team')).toEqual('a'); expect(getArticle('team')).toEqual('a');
@@ -30,12 +16,6 @@ describe('string utils', () => {
}); });
}); });
describe('ucFirst', () => {
test('should capitalize first character', () => {
expect(ucFirst('team')).toEqual('Team');
});
});
describe('toTitleCase', () => { describe('toTitleCase', () => {
test('should upper case each word', () => { test('should upper case each word', () => {
expect(toTitleCase('a_string_of_words')).toEqual('A String Of Words'); expect(toTitleCase('a_string_of_words')).toEqual('A String Of Words');