Skip to content

Commit

Permalink
Add badges to table details (#356)
Browse files Browse the repository at this point in the history
- Added badge support for tables
- Added BadgeConfig to customize display names/styles of defined badges
  • Loading branch information
Daniel authored Dec 12, 2019
1 parent e7b3b6a commit c1b50cb
Show file tree
Hide file tree
Showing 15 changed files with 182 additions and 6 deletions.
1 change: 1 addition & 0 deletions frontend/amundsen_application/api/utils/metadata_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def marshall_table_full(table: Dict) -> Dict:
"""
# Filter and parse the response dictionary from the metadata service
fields = [
'badges',
'columns',
'cluster',
'database',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getTableData } from 'ducks/tableMetadata/reducer';
import { GetTableDataRequest } from 'ducks/tableMetadata/types';

import AppConfig from 'config/config';
import BadgeList from 'components/common/BadgeList';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import Breadcrumb from 'components/common/Breadcrumb';
import DataPreviewButton from 'components/TableDetail/DataPreviewButton';
Expand All @@ -28,11 +29,10 @@ import TagInput from 'components/Tags/TagInput';
import { TableMetadata } from 'interfaces/TableMetadata';

import { EditableSection } from 'components/TableDetail/EditableSection';
import RequestDescriptionText from './RequestDescriptionText';
import { getDatabaseDisplayName, getDatabaseIconClass, notificationsEnabled } from 'config/config-utils';

import './styles';

import RequestDescriptionText from './RequestDescriptionText';
import RequestMetadataForm from './RequestMetadataForm';

export interface StateFromProps {
Expand Down Expand Up @@ -124,8 +124,14 @@ class TableDetail extends React.Component<TableDetailProps & RouteComponentProps
{ getDatabaseDisplayName(data.database) }
&nbsp;&bull;&nbsp;
{ data.cluster }
&nbsp;
{
data.badges.length > 0 &&
<BadgeList badges={ data.badges } />
}
{
data.is_view && <Flag text="Table View" labelStyle="primary"/>
data.is_view &&
<Flag text="table view" labelStyle="warning"/>
}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from 'react';

import Flag from 'components/common/Flag';
import { getBadgeConfig } from 'config/config-utils';
import { Badge } from 'interfaces/Tags';

export interface BadgeListProps {
badges: Badge[];
}

const BadgeList: React.SFC<BadgeListProps> = ({ badges }) => {
return (
<span className="badge-list">
{
badges.map((badge, index) => {
const badgeConfig = getBadgeConfig(badge.tag_name);

return <Flag text={ badgeConfig.displayName }
labelStyle={ badgeConfig.style }
key={`badge-${index}`}/>;
})
}
</span>
);
};

export default BadgeList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react';
import { shallow } from 'enzyme';

import BadgeList from '../'
import Flag from 'components/common/Flag';
import { BadgeStyle } from 'config/config-types';
import * as ConfigUtils from 'config/config-utils';
import { Badge, TagType } from 'interfaces/Tags';

describe('BadgeList', () => {
const getBadgeConfigSpy = jest.spyOn(ConfigUtils, 'getBadgeConfig');
getBadgeConfigSpy.mockImplementation((badgeName: string) => {
return {
displayName: badgeName + " test name",
style: BadgeStyle.PRIMARY,
};
});

describe('BadgeList function component', () => {
const badges: Badge[] = [
{
tag_name: 'test_1',
tag_type: TagType.BADGE,
},
{
tag_name: 'test_3',
tag_type: TagType.BADGE,
},
];

const badgeList = shallow(<BadgeList badges={ badges } />);

it('renders a badge-list element', () => {
const container = badgeList.find('.badge-list')
expect(container.exists()).toBe(true);
});

it('renders a <Flag> for each badge in the input', () => {
expect(badgeList.find(Flag).length).toEqual(badges.length);
});

it('passes the correct props to the flag', () => {
badges.forEach((badge, index) => {
const flag = badgeList.childAt(index);
const flagProps = flag.props();
const badgeConfig = ConfigUtils.getBadgeConfig(badge.tag_name);
expect(flagProps.text).toEqual(badgeConfig.displayName);
expect(flagProps.labelStyle).toEqual(badgeConfig.style);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
.flag {
height: 20px;
display: inline-block;
margin: 0px 8px;
margin: 0 4px;
font-size: 14px;
border-radius: 5px;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AppConfig } from './config-types';

const configDefault: AppConfig = {
badges: {},
browse: {
curatedTags: [],
showAllTags: true,
Expand Down
25 changes: 25 additions & 0 deletions frontend/amundsen_application/static/js/config/config-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

export interface AppConfig {
badges: BadgeConfig;
browse: BrowseConfig;
editableText: EditableTextConfig;
google: GoogleAnalyticsConfig;
Expand All @@ -18,6 +19,7 @@ export interface AppConfig {
}

export interface AppConfigCustom {
badges?: BadgeConfig;
browse?: BrowseConfig;
editableText?: EditableTextConfig;
google?: GoogleAnalyticsConfig
Expand Down Expand Up @@ -52,6 +54,29 @@ interface BrowseConfig {
showAllTags: boolean;
}

export enum BadgeStyle {
DANGER = "danger",
DEFAULT = "default",
INFO = "info",
PRIMARY = "primary",
SUCCESS = "success",
WARNING = "warning",
}

export interface BadgeStyleConfig {
style: BadgeStyle;
displayName?: string;
}

/**
* BadgeConfig - Configure badge colors
*
* An object that maps badges to BadgeStyleConfigs
*/
interface BadgeConfig {
[badge: string]: BadgeStyleConfig;
}

/** ResourceConfig - For customizing values related to how various resources
* are displayed in the UI.
*
Expand Down
16 changes: 16 additions & 0 deletions frontend/amundsen_application/static/js/config/config-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AppConfig from 'config/config';
import { BadgeStyleConfig, BadgeStyle } from 'config/config-types';

export const DEFAULT_DATABASE_ICON_CLASS = 'icon-database icon-color';

Expand Down Expand Up @@ -31,6 +32,21 @@ export function getDatabaseIconClass(databaseId: string): string {
return databaseConfig.iconClass;
}

/**
* Given a badge name, this will return a badge style and a display name.
* If these are not specified by config, it will default to some simple rules:
* use BadgeStyle.DEFAULT and replace '-' and '_' with spaces for display name.
*/
export function getBadgeConfig(badgeName: string): BadgeStyleConfig {
const config = AppConfig.badges[badgeName] || {};

return {
style: BadgeStyle.DEFAULT,
displayName: badgeName.replace(/[-_]/g, ' '),
...config,
};
}

/**
* Returns whether or not feedback features should be enabled
*/
Expand Down
28 changes: 28 additions & 0 deletions frontend/amundsen_application/static/js/config/tests/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import AppConfig from 'config/config';
import * as ConfigUtils from 'config/config-utils';
import { BadgeStyle } from 'config/config-types';

describe('getDatabaseDisplayName', () => {
it('returns given id if no config for that id exists', () => {
Expand Down Expand Up @@ -27,6 +28,33 @@ describe('getDatabaseIconClass', () => {
})
});

describe('getBadgeConfig', () => {
AppConfig.badges = {
'test_1': {
style: BadgeStyle.DANGER,
displayName: 'badge display value 1',
},
'test_2': {
style: BadgeStyle.DANGER,
displayName: 'badge display value 2',
}
};

it('Returns the badge config for a given badge', () => {
const config = ConfigUtils.getBadgeConfig('test_1');
const expectedConfig = AppConfig.badges['test_1'];
expect(config.style).toEqual(expectedConfig.style);
expect(config.displayName).toEqual(expectedConfig.displayName);
});

it('Returns default badge config for unspecified badges', () => {
const badgeName = 'not_configured_badge';
const badgeConfig = ConfigUtils.getBadgeConfig(badgeName);
expect(badgeConfig.style).toEqual(BadgeStyle.DEFAULT);
expect(badgeConfig.displayName).toEqual('not configured badge');
});
});

describe('feedbackEnabled', () => {
it('returns whether or not the feaadback feature is enabled', () => {
expect(ConfigUtils.feedbackEnabled()).toBe(AppConfig.mailClientFeatures.feedbackEnabled);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export const initialPreviewState = {
};

export const initialTableDataState: TableMetadata = {
badges: [],
cluster: '',
columns: [],
database: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const globalState: GlobalState = {
},
statusCode: null,
tableData: {
badges: [],
cluster: '',
columns: [],
database: '',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { UpdateMethod } from './Enums';
import { User } from './User';
import { Badge } from 'interfaces/Tags';

interface PartitionData {
is_partitioned: boolean;
Expand Down Expand Up @@ -68,6 +69,7 @@ export interface TableOwners {
}

export interface TableMetadata {
badges: Badge[];
cluster: string;
columns: TableColumn[];
database: string;
Expand Down
14 changes: 12 additions & 2 deletions frontend/amundsen_application/static/js/interfaces/Tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@ export interface UpdateTagData {
tagName: string;
}

export enum TagType {
TAG = 'default',
BADGE = 'badge',
}

export interface Tag {
tag_count: number;
tag_count?: number;
tag_name: string;
tag_type?: TagType.TAG;
}

export interface Badge {
tag_name: string;
tag_type?: string;
tag_type: TagType.BADGE;
}
5 changes: 5 additions & 0 deletions frontend/docs/application_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ This document describes how to leverage the frontend service's application confi

**NOTE: This document is a work in progress and does not include 100% of features. We welcome PRs to complete this document**

## Badge Config
Badges are a special type of tag that cannot be edited through the UI.

`BadgeConfig` can be used to customize the text and color of badges. This config defines a mapping of badge name to a `BadgeStyle` and optional `displayName`. Badges that are not defined will default to use the `BadgeStyle.default` style and `displayName` use the badge name with any `_` or `-` characters replaced with a space.

## Browse Tags Feature

_TODO: Please add doc_
Expand Down
1 change: 1 addition & 0 deletions frontend/tests/unit/api/metadata/test_v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def setUp(self) -> None:
}
self.expected_parsed_metadata = {
'key': 'table_key',
'badges': [],
'cluster': 'test_cluster',
'database': 'test_db',
'schema': 'test_schema',
Expand Down

0 comments on commit c1b50cb

Please sign in to comment.