Unverified Commit 79135345 authored by Daniel's avatar Daniel Committed by GitHub

Add badges to table details (#356)

- Added badge support for tables
- Added BadgeConfig to customize display names/styles of defined badges
parent 57f05b10
...@@ -36,6 +36,7 @@ def marshall_table_full(table: Dict) -> Dict: ...@@ -36,6 +36,7 @@ def marshall_table_full(table: Dict) -> Dict:
""" """
# Filter and parse the response dictionary from the metadata service # Filter and parse the response dictionary from the metadata service
fields = [ fields = [
'badges',
'columns', 'columns',
'cluster', 'cluster',
'database', 'database',
......
...@@ -10,6 +10,7 @@ import { getTableData } from 'ducks/tableMetadata/reducer'; ...@@ -10,6 +10,7 @@ import { getTableData } from 'ducks/tableMetadata/reducer';
import { GetTableDataRequest } from 'ducks/tableMetadata/types'; import { GetTableDataRequest } from 'ducks/tableMetadata/types';
import AppConfig from 'config/config'; import AppConfig from 'config/config';
import BadgeList from 'components/common/BadgeList';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon'; import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import Breadcrumb from 'components/common/Breadcrumb'; import Breadcrumb from 'components/common/Breadcrumb';
import DataPreviewButton from 'components/TableDetail/DataPreviewButton'; import DataPreviewButton from 'components/TableDetail/DataPreviewButton';
...@@ -28,11 +29,10 @@ import TagInput from 'components/Tags/TagInput'; ...@@ -28,11 +29,10 @@ import TagInput from 'components/Tags/TagInput';
import { TableMetadata } from 'interfaces/TableMetadata'; import { TableMetadata } from 'interfaces/TableMetadata';
import { EditableSection } from 'components/TableDetail/EditableSection'; import { EditableSection } from 'components/TableDetail/EditableSection';
import RequestDescriptionText from './RequestDescriptionText';
import { getDatabaseDisplayName, getDatabaseIconClass, notificationsEnabled } from 'config/config-utils'; import { getDatabaseDisplayName, getDatabaseIconClass, notificationsEnabled } from 'config/config-utils';
import './styles'; import './styles';
import RequestDescriptionText from './RequestDescriptionText';
import RequestMetadataForm from './RequestMetadataForm'; import RequestMetadataForm from './RequestMetadataForm';
export interface StateFromProps { export interface StateFromProps {
...@@ -124,8 +124,14 @@ class TableDetail extends React.Component<TableDetailProps & RouteComponentProps ...@@ -124,8 +124,14 @@ class TableDetail extends React.Component<TableDetailProps & RouteComponentProps
{ getDatabaseDisplayName(data.database) } { getDatabaseDisplayName(data.database) }
&nbsp;&bull;&nbsp; &nbsp;&bull;&nbsp;
{ data.cluster } { 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>
</div> </div>
......
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;
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);
});
});
});
});
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.flag { .flag {
height: 20px; height: 20px;
display: inline-block; display: inline-block;
margin: 0px 8px; margin: 0 4px;
font-size: 14px; font-size: 14px;
border-radius: 5px; border-radius: 5px;
} }
......
import { AppConfig } from './config-types'; import { AppConfig } from './config-types';
const configDefault: AppConfig = { const configDefault: AppConfig = {
badges: {},
browse: { browse: {
curatedTags: [], curatedTags: [],
showAllTags: true, showAllTags: true,
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
*/ */
export interface AppConfig { export interface AppConfig {
badges: BadgeConfig;
browse: BrowseConfig; browse: BrowseConfig;
editableText: EditableTextConfig; editableText: EditableTextConfig;
google: GoogleAnalyticsConfig; google: GoogleAnalyticsConfig;
...@@ -18,6 +19,7 @@ export interface AppConfig { ...@@ -18,6 +19,7 @@ export interface AppConfig {
} }
export interface AppConfigCustom { export interface AppConfigCustom {
badges?: BadgeConfig;
browse?: BrowseConfig; browse?: BrowseConfig;
editableText?: EditableTextConfig; editableText?: EditableTextConfig;
google?: GoogleAnalyticsConfig google?: GoogleAnalyticsConfig
...@@ -52,6 +54,29 @@ interface BrowseConfig { ...@@ -52,6 +54,29 @@ interface BrowseConfig {
showAllTags: boolean; 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 /** ResourceConfig - For customizing values related to how various resources
* are displayed in the UI. * are displayed in the UI.
* *
......
import AppConfig from 'config/config'; import AppConfig from 'config/config';
import { BadgeStyleConfig, BadgeStyle } from 'config/config-types';
export const DEFAULT_DATABASE_ICON_CLASS = 'icon-database icon-color'; export const DEFAULT_DATABASE_ICON_CLASS = 'icon-database icon-color';
...@@ -31,6 +32,21 @@ export function getDatabaseIconClass(databaseId: string): string { ...@@ -31,6 +32,21 @@ export function getDatabaseIconClass(databaseId: string): string {
return databaseConfig.iconClass; 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 * Returns whether or not feedback features should be enabled
*/ */
......
import AppConfig from 'config/config'; import AppConfig from 'config/config';
import * as ConfigUtils from 'config/config-utils'; import * as ConfigUtils from 'config/config-utils';
import { BadgeStyle } from 'config/config-types';
describe('getDatabaseDisplayName', () => { describe('getDatabaseDisplayName', () => {
it('returns given id if no config for that id exists', () => { it('returns given id if no config for that id exists', () => {
...@@ -27,6 +28,33 @@ describe('getDatabaseIconClass', () => { ...@@ -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', () => { describe('feedbackEnabled', () => {
it('returns whether or not the feaadback feature is enabled', () => { it('returns whether or not the feaadback feature is enabled', () => {
expect(ConfigUtils.feedbackEnabled()).toBe(AppConfig.mailClientFeatures.feedbackEnabled); expect(ConfigUtils.feedbackEnabled()).toBe(AppConfig.mailClientFeatures.feedbackEnabled);
......
...@@ -179,6 +179,7 @@ export const initialPreviewState = { ...@@ -179,6 +179,7 @@ export const initialPreviewState = {
}; };
export const initialTableDataState: TableMetadata = { export const initialTableDataState: TableMetadata = {
badges: [],
cluster: '', cluster: '',
columns: [], columns: [],
database: '', database: '',
......
...@@ -109,6 +109,7 @@ const globalState: GlobalState = { ...@@ -109,6 +109,7 @@ const globalState: GlobalState = {
}, },
statusCode: null, statusCode: null,
tableData: { tableData: {
badges: [],
cluster: '', cluster: '',
columns: [], columns: [],
database: '', database: '',
......
import { UpdateMethod } from './Enums'; import { UpdateMethod } from './Enums';
import { User } from './User'; import { User } from './User';
import { Badge } from 'interfaces/Tags';
interface PartitionData { interface PartitionData {
is_partitioned: boolean; is_partitioned: boolean;
...@@ -68,6 +69,7 @@ export interface TableOwners { ...@@ -68,6 +69,7 @@ export interface TableOwners {
} }
export interface TableMetadata { export interface TableMetadata {
badges: Badge[];
cluster: string; cluster: string;
columns: TableColumn[]; columns: TableColumn[];
database: string; database: string;
......
...@@ -5,8 +5,18 @@ export interface UpdateTagData { ...@@ -5,8 +5,18 @@ export interface UpdateTagData {
tagName: string; tagName: string;
} }
export enum TagType {
TAG = 'default',
BADGE = 'badge',
}
export interface Tag { export interface Tag {
tag_count: number; tag_count?: number;
tag_name: string;
tag_type?: TagType.TAG;
}
export interface Badge {
tag_name: string; tag_name: string;
tag_type?: string; tag_type: TagType.BADGE;
} }
...@@ -4,6 +4,11 @@ This document describes how to leverage the frontend service's application confi ...@@ -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** **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 ## Browse Tags Feature
_TODO: Please add doc_ _TODO: Please add doc_
......
...@@ -78,6 +78,7 @@ class MetadataTest(unittest.TestCase): ...@@ -78,6 +78,7 @@ class MetadataTest(unittest.TestCase):
} }
self.expected_parsed_metadata = { self.expected_parsed_metadata = {
'key': 'table_key', 'key': 'table_key',
'badges': [],
'cluster': 'test_cluster', 'cluster': 'test_cluster',
'database': 'test_db', 'database': 'test_db',
'schema': 'test_schema', 'schema': 'test_schema',
......
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