Unverified Commit fd970158 authored by Allison Suarez Miranda's avatar Allison Suarez Miranda Committed by GitHub

feat: alphabetized badges (#804)

* alphabetized badges
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* Moving BadgeList container into a features folder
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>
Co-authored-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>
parent a6c0d5ab
......@@ -28,7 +28,7 @@ import {
Badge,
} from 'interfaces';
import BadgeList from 'components/common/BadgeList';
import BadgeList from 'features/BadgeList';
import ColumnType from './ColumnType';
import ColumnDescEditableText from './ColumnDescEditableText';
import ColumnStats from './ColumnStats';
......
......@@ -2,15 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
import * as React from 'react';
import { Provider } from 'react-redux';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import globalState from 'fixtures/globalState';
import Flag from 'components/common/Flag';
import { BadgeStyle } from 'config/config-types';
import * as ConfigUtils from 'config/config-utils';
import { Badge } from 'interfaces/Badges';
import * as UtilMethods from 'ducks/utilMethods';
import Flag from 'components/common/Flag';
import BadgeList, { BadgeListProps } from '.';
const columnBadges: Badge[] = [
......@@ -34,22 +35,17 @@ const badges: Badge[] = [
},
];
const middlewares = [];
const mockStore = configureStore(middlewares);
const logClickSpy = jest.spyOn(UtilMethods, 'logClick');
logClickSpy.mockImplementation(() => null);
const setup = (propOverrides?: Partial<BadgeListProps>) => {
const props = {
badges: [],
badges,
onBadgeClick: () => {},
...propOverrides,
};
const testState = globalState;
testState.tableMetadata.tableData.badges = badges;
const wrapper = mount<BadgeListProps>(
<Provider store={mockStore(testState)}>
<BadgeList {...props} />
</Provider>
);
const wrapper = mount<BadgeListProps>(<BadgeList {...props} />);
return { props, wrapper };
};
......@@ -63,57 +59,101 @@ describe('BadgeList', () => {
};
});
describe('when no badges are passed', () => {
it('renders a badge-list element', () => {
const { wrapper } = setup();
const expected = 1;
const actual = wrapper.find('.badge-list').length;
describe('render', () => {
describe('when no badges are passed', () => {
it('renders a badge-list element', () => {
const { wrapper } = setup();
const expected = 1;
const actual = wrapper.find('.badge-list').length;
expect(actual).toEqual(expected);
});
expect(actual).toEqual(expected);
});
it('does not render any badges', () => {
const { wrapper } = setup();
const actual = wrapper.find(Flag).length;
const expected = 0;
it('does not render any badges', () => {
const { wrapper } = setup();
const actual = wrapper.find(Flag).length;
const expected = 0;
expect(actual).toEqual(expected);
expect(actual).toEqual(expected);
});
});
});
describe('when badges are passed', () => {
it('renders a badge-list element', () => {
const { wrapper } = setup({ badges });
const expected = 1;
const actual = wrapper.find('.badge-list').length;
describe('when badges are passed', () => {
it('renders a badge-list element', () => {
const { wrapper } = setup({ badges });
const expected = 1;
const actual = wrapper.find('.badge-list').length;
expect(actual).toEqual(expected);
});
it('renders a .actionable-badge for each badge in the input', () => {
const { wrapper } = setup({ badges });
const expected = badges.length;
const actual = wrapper.find('.actionable-badge').length;
expect(actual).toEqual(expected);
expect(actual).toEqual(expected);
});
});
it('renders a .actionable-badge for each badge in the input', () => {
const { wrapper } = setup({ badges });
const expected = badges.length;
const actual = wrapper.find('.actionable-badge').length;
describe('when badge category is column', () => {
it('renders a badge-list element', () => {
const { wrapper } = setup({ badges: columnBadges });
const expected = 1;
const actual = wrapper.find('.badge-list').length;
expect(actual).toEqual(expected);
expect(actual).toEqual(expected);
});
it('renders a .static-badge for each badge in the input', () => {
const { wrapper } = setup({ badges: columnBadges });
const expected = 2;
const actual = wrapper.find('.static-badge').length;
expect(actual).toEqual(expected);
});
});
});
describe('when badge category is column', () => {
it('renders a badge-list element', () => {
const { wrapper } = setup({ badges: columnBadges });
const expected = 1;
const actual = wrapper.find('.badge-list').length;
describe.only('lifetime', () => {
describe('when clicking on a badge', () => {
it('should log the interaction', () => {
logClickSpy.mockClear();
const { wrapper } = setup();
expect(actual).toEqual(expected);
});
wrapper.find('span.actionable-badge').at(0).simulate('click');
expect(logClickSpy).toHaveBeenCalled();
});
it('should call the handler', () => {
logClickSpy.mockClear();
const handlerSpy = jest.fn();
const { wrapper } = setup({
onBadgeClick: handlerSpy,
});
const expected = 1;
wrapper.find('span.actionable-badge').at(0).simulate('click');
const actual = handlerSpy.mock.calls.length;
expect(actual).toEqual(expected);
});
it('should call the handler with the proper badge name', () => {
logClickSpy.mockClear();
const handlerSpy = jest.fn();
const { wrapper } = setup({
onBadgeClick: handlerSpy,
});
const expected = 'beta test name';
wrapper.find('span.actionable-badge').at(0).simulate('click');
it('renders a .static-badge for each badge in the input', () => {
const { wrapper } = setup({ badges: columnBadges });
const expected = 2;
const actual = wrapper.find('.static-badge').length;
const actual = handlerSpy.mock.calls[0][0];
expect(actual).toEqual(expected);
expect(actual).toEqual(expected);
});
});
});
});
......@@ -4,26 +4,21 @@
import * as React from 'react';
import { getBadgeConfig } from 'config/config-utils';
import { BadgeStyle, BadgeStyleConfig } from 'config/config-types';
import { convertText, CaseType } from 'utils/textUtils';
import { Badge } from 'interfaces/Badges';
import { BadgeStyle, BadgeStyleConfig } from 'config/config-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { ResourceType } from 'interfaces';
import { updateSearchState } from 'ducks/search/reducer';
import { UpdateSearchStateRequest } from 'ducks/search/types';
import { logClick } from 'ducks/utilMethods';
import './styles.scss';
const COLUMN_BADGE_CATEGORY = 'column';
export interface ListProps {
export interface BadgeListProps {
badges: Badge[];
}
export interface DispatchFromProps {
searchBadge: (badgeText: string) => UpdateSearchStateRequest;
onBadgeClick: (badgeText: string) => void;
}
export interface ActionableBadgeProps {
......@@ -32,8 +27,6 @@ export interface ActionableBadgeProps {
action: any;
}
export type BadgeListProps = ListProps & DispatchFromProps;
const StaticBadge: React.FC<BadgeStyleConfig> = ({
style,
displayName,
......@@ -57,19 +50,28 @@ const ActionableBadge: React.FC<ActionableBadgeProps> = ({
);
};
export class BadgeList extends React.Component<BadgeListProps> {
export default class BadgeList extends React.Component<BadgeListProps> {
handleClick = (index: number, badgeText: string, e) => {
const { onBadgeClick } = this.props;
logClick(e, {
target_type: 'badge',
label: badgeText,
});
this.props.searchBadge(convertText(badgeText, CaseType.LOWER_CASE));
onBadgeClick(convertText(badgeText, CaseType.LOWER_CASE));
};
render() {
const { badges } = this.props;
const alphabetizedBadges = badges.sort((a, b) => {
const aName = (a.badge_name ? a.badge_name : a.tag_name) || '';
const bName = (b.badge_name ? b.badge_name : b.tag_name) || '';
return aName.localeCompare(bName);
});
return (
<span className="badge-list">
{this.props.badges.map((badge, index) => {
{alphabetizedBadges.map((badge, index) => {
let badgeConfig;
// search badges with just name
if (badge.tag_name) {
......@@ -93,7 +95,7 @@ export class BadgeList extends React.Component<BadgeListProps> {
<ActionableBadge
displayName={badgeConfig.displayName}
style={badgeConfig.style}
action={(e) =>
action={(e: React.SyntheticEvent) =>
this.handleClick(index, badgeConfig.displayName, e)
}
key={`badge-${index}`}
......@@ -105,23 +107,3 @@ export class BadgeList extends React.Component<BadgeListProps> {
);
}
}
export const mapDispatchToProps = (dispatch: any) => {
return bindActionCreators(
{
searchBadge: (badgeText: string) =>
updateSearchState({
filters: {
[ResourceType.table]: { badges: badgeText },
},
submitSearch: true,
}),
},
dispatch
);
};
export default connect<null, DispatchFromProps, ListProps>(
null,
mapDispatchToProps
)(BadgeList);
......@@ -11,7 +11,7 @@ import SchemaInfo from 'components/common/ResourceListItem/SchemaInfo';
import { ResourceType, TagType } from 'interfaces';
import * as ConfigUtils from 'config/config-utils';
import BadgeList from 'components/common/BadgeList';
import BadgeList from 'features/BadgeList';
import TableListItem, { TableListItemProps } from '.';
const MOCK_DISPLAY_NAME = 'displayName';
......
......@@ -10,7 +10,7 @@ import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import { getSourceDisplayName, getSourceIconClass } from 'config/config-utils';
import BadgeList from 'components/common/BadgeList';
import BadgeList from 'features/BadgeList';
import SchemaInfo from 'components/common/ResourceListItem/SchemaInfo';
import { LoggingParams } from '../types';
......
// Copyright Contributors to the Amundsen project.
// SPDX-License-Identifier: Apache-2.0
import * as React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { ResourceType, Badge } from 'interfaces';
import { updateSearchState } from 'ducks/search/reducer';
import { UpdateSearchStateRequest } from 'ducks/search/types';
import BadgeList from 'components/common/BadgeList';
export interface DispatchFromProps {
onBadgeClick: (badgeText: string) => UpdateSearchStateRequest;
}
export interface BadgeListFeatureProps {
badges: Badge[];
}
export const mapDispatchToProps = (dispatch: any) => {
return bindActionCreators(
{
onBadgeClick: (badgeText: string) =>
updateSearchState({
filters: {
[ResourceType.table]: { badges: badgeText },
},
submitSearch: true,
}),
},
dispatch
);
};
export default connect<null, DispatchFromProps, BadgeListFeatureProps>(
null,
mapDispatchToProps
)(BadgeList);
......@@ -100,7 +100,6 @@ describe('TableHeaderBullets', () => {
expect(actual).toEqual(expected);
});
it('renders a list with resource display name', () => {
console.log(wrapper.debug());
expect(getDisplayNameByResource).toHaveBeenCalledWith(ResourceType.table);
expect(wrapper.find('ul').find('li').at(0).text()).toEqual(
MOCK_RESOURCE_DISPLAY_NAME
......
......@@ -27,7 +27,7 @@ import {
notificationsEnabled,
} from 'config/config-utils';
import BadgeList from 'components/common/BadgeList';
import BadgeList from 'features/BadgeList';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import Breadcrumb from 'components/common/Breadcrumb';
import TabsComponent, { TabInfo } from 'components/common/TabsComponent';
......
......@@ -20,7 +20,8 @@
"eslint": "eslint --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx .",
"eslint:errors": "eslint --ignore-path=.eslintignore --quiet --ext .js,.jsx,.ts,.tsx .",
"eslint:fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx .",
"tsc": "tsc",
"tsc": "tsc --pretty --noEmit",
"type-check": "tsc --pretty --noEmit",
"clean-sass-vars": "find-unused-sass-variables ./js",
"stylelint": "stylelint '**/*.scss'",
"stylelint:fix": "stylelint --fix '**/*.scss'",
......@@ -29,7 +30,7 @@
"build-storybook": "cross-env TS_NODE_PROJECT='tsconfig.webpack.json' build-storybook",
"betterer": "betterer",
"betterer:update": "betterer --update",
"check": "npm run eslint:errors && npm run stylelint && npm run tsc && npm run betterer"
"check": "npm run eslint:errors && npm run stylelint && npm run type-check && npm run betterer"
},
"author": "",
"license": "Apache-2.0",
......@@ -70,7 +71,7 @@
"@types/react-router": "^4.4.5",
"@types/react-tagsinput": "^3.19.7",
"@types/storybook__addon-knobs": "^5.2.1",
"@types/webpack": "^4.41.22",
"@types/webpack": "^4.41.25",
"@typescript-eslint/eslint-plugin": "4.5.0",
"@typescript-eslint/eslint-plugin-tslint": "4.5.0",
"@typescript-eslint/parser": "4.6.1",
......@@ -93,7 +94,7 @@
"eslint-plugin-react": "^7.20.5",
"eslint-plugin-react-hooks": "^4.0.4",
"find-unused-sass-variables": "^3.0.0",
"html-webpack-plugin": "^4.5.0",
"html-webpack-plugin": "5.0.0-alpha.4",
"husky": "^4.3.0",
"jest": "^26.6.0",
"jest-css-modules": "^2.1.0",
......
......@@ -18,6 +18,7 @@
"baseUrl": "js",
"paths": {
"components/*": ["components/*"],
"features/*": ["features/*"],
"config/*": ["config/*"],
"ducks/*": ["ducks/*"],
"interfaces/*": ["interfaces/*"],
......
......@@ -28,6 +28,7 @@ const PATHS = {
dist: resolve('/dist'),
pages: resolve('/js/pages'),
components: resolve('/js/components'),
features: resolve('/js/features'),
config: resolve('/js/config'),
ducks: resolve('/js/ducks'),
interfaces: resolve('/js/interfaces'),
......@@ -68,6 +69,7 @@ const config: webpack.Configuration = {
alias: {
pages: PATHS.pages,
components: PATHS.components,
features: PATHS.features,
config: PATHS.config,
ducks: PATHS.ducks,
interfaces: PATHS.interfaces,
......
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