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

feat: Update 'table view', 'alumni' and SLA status badges (#595)

* removed badges section for users cause we dont need that moving forward
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* removed ALumni badge from user profile and added it to header-bullets instead
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* removed Alumni badge checks for user profiles
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* not working for some reason
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* lint and missed test update
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

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

* updated DashboardPage tests
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

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

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

* fixed alignment issue
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* fixed table view
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* added variables for style
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* made component more genric, still need to rename and fix icon issue
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* updated text to use typography defined
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

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

* hit icon broken
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* fixed hit icon a bit
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

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

* moved setup into it clause
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* added resource badges back in
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* lint + added back tests
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

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

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

* fixed icon for hit
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* bettered updated file
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* removed text prop
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

* fixed text issue
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>

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

* cleanup
Signed-off-by: 's avatarAllison Suarez Miranda <asuarezmiranda@lyft.com>
parent 7c9a29a7
......@@ -29,6 +29,10 @@ $users: (
users: '/static/images/icons/users.svg',
);
$check: (
check: '/static/images/icons/check.svg',
);
// Given a Map of key/value pairs, generates a new class
@mixin iconBackgrounds($map) {
@each $name, $url in $map {
......@@ -43,6 +47,7 @@ span.icon {
@include iconBackgrounds($data-stores);
@include iconBackgrounds($dashboards);
@include iconBackgrounds($users);
@include iconBackgrounds($check);
background-color: $icon-bg;
border: none;
......@@ -55,6 +60,8 @@ span.icon {
}
img.icon {
/*DEPRECATED: follow behavior above to generate
icons*/
background-color: $icon-bg;
border: none;
height: $icon-size;
......
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#F2F2F2" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
\ No newline at end of file
......@@ -7,4 +7,3 @@ export const ADD_DESC_TEXT = 'Add Description in';
export const EDIT_DESC_TEXT = 'Click to edit description in';
export const LAST_RUN_SUCCEEDED = 'succeeded';
export const LAST_RUN_FAILED = 'failed';
......@@ -10,13 +10,11 @@ import { shallow } from 'enzyme';
import LoadingSpinner from 'components/common/LoadingSpinner';
import Breadcrumb from 'components/common/Breadcrumb';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import Flag from 'components/common/Flag';
import ResourceList from 'components/common/ResourceList';
import TabsComponent from 'components/common/TabsComponent';
import { dashboardMetadata } from 'fixtures/metadata/dashboard';
import { NO_TIMESTAMP_TEXT } from 'components/constants';
import { ResourceType } from 'interfaces';
import { BadgeStyle } from 'config/config-types';
import ChartList from './ChartList';
import DashboardOwnerEditor from './DashboardOwnerEditor';
import ImagePreview from './ImagePreview';
......@@ -139,26 +137,6 @@ describe('DashboardPage', () => {
});
});
describe('mapStatusToStyle', () => {
let wrapper;
beforeAll(() => {
({ wrapper } = setup());
});
it('returns BadgeStyle.SUCCESS if status === LAST_RUN_SUCCEEDED', () => {
expect(
wrapper.instance().mapStatusToStyle(Constants.LAST_RUN_SUCCEEDED)
).toBe(BadgeStyle.SUCCESS);
});
it('returns BadgeStyle.DANGER if status !== LAST_RUN_SUCCEEDED', () => {
expect(wrapper.instance().mapStatusToStyle('anythingelse')).toBe(
BadgeStyle.DANGER
);
});
});
describe('render', () => {
const { props, wrapper } = setup();
......@@ -215,19 +193,13 @@ describe('DashboardPage', () => {
expect(wrapper.find(DashboardOwnerEditor).exists()).toBe(true);
});
it('renders a Flag for last run state', () => {
const mockStyle = BadgeStyle.DANGER;
const mapStatusToStyleSpy = jest
.spyOn(wrapper.instance(), 'mapStatusToStyle')
.mockImplementationOnce(() => mockStyle);
wrapper.instance().forceUpdate();
const element = wrapper.find('.last-run-state').find(Flag);
expect(element.props().text).toBe(props.dashboard.last_run_state);
expect(mapStatusToStyleSpy).toHaveBeenCalledWith(
props.dashboard.last_run_state
);
expect(element.props().labelStyle).toBe(mockStyle);
it('renders a ResourceStatusMarker for last run state', () => {
const expected = 1;
const actual = wrapper
.find('.last-run-state')
.find('ResourceStatusMarker').length;
expect(actual).toEqual(expected);
});
it('renders an ImagePreview with correct props', () => {
......
......@@ -13,7 +13,6 @@ import * as ReactMarkdown from 'react-markdown';
import AvatarLabel from 'components/common/AvatarLabel';
import Breadcrumb from 'components/common/Breadcrumb';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import Flag from 'components/common/Flag';
import EditableSection from 'components/common/EditableSection';
import LoadingSpinner from 'components/common/LoadingSpinner';
import TabsComponent from 'components/common/TabsComponent';
......@@ -25,15 +24,16 @@ import { DashboardMetadata } from 'interfaces/Dashboard';
import DashboardOwnerEditor from 'components/DashboardPage/DashboardOwnerEditor';
import QueryList from 'components/DashboardPage/QueryList';
import ChartList from 'components/DashboardPage/ChartList';
import ResourceStatusMarker from 'components/common/ResourceStatusMarker';
import { formatDateTimeShort } from 'utils/dateUtils';
import ResourceList from 'components/common/ResourceList';
import {
ADD_DESC_TEXT,
EDIT_DESC_TEXT,
DASHBOARD_SOURCE,
LAST_RUN_SUCCEEDED,
OWNER_HEADER_TEXT,
DASHBOARD_SOURCE,
TABLES_PER_PAGE,
LAST_RUN_SUCCEEDED,
} from 'components/DashboardPage/constants';
import TagInput from 'components/common/Tags/TagInput';
import { ResourceType } from 'interfaces';
......@@ -109,11 +109,11 @@ export class DashboardPage extends React.Component<
}
}
mapStatusToStyle = (status: string): BadgeStyle => {
mapStatusToBoolean = (status: string): boolean => {
if (status === LAST_RUN_SUCCEEDED) {
return BadgeStyle.SUCCESS;
return true;
}
return BadgeStyle.DANGER;
return false;
};
renderTabs() {
......@@ -304,10 +304,9 @@ export class DashboardPage extends React.Component<
: NO_TIMESTAMP_TEXT}
</time>
<div className="last-run-state">
<Flag
caseType="sentenceCase"
text={dashboard.last_run_state}
labelStyle={this.mapStatusToStyle(
<ResourceStatusMarker
stateText={dashboard.last_run_state}
succeeded={this.mapStatusToBoolean(
dashboard.last_run_state
)}
/>
......
......@@ -18,3 +18,5 @@ export const GITHUB_LINK_TEXT = 'Github';
export const EMPTY_TEXT_PREFIX = 'User has no';
export const FOOTER_TEXT_PREFIX = 'View all';
export const NOT_ACTIVE_USER_TEXT = 'Alumni';
......@@ -335,23 +335,6 @@ describe('ProfilePage', () => {
);
});
it('renders Flag with correct props if user not active', () => {
const userCopy = {
...globalState.user.profile.user,
is_active: false,
};
const { wrapper } = setup({
user: userCopy,
});
expect(
wrapper.find('.header-title-text').find(Flag).props()
).toMatchObject({
caseType: 'sentenceCase',
labelStyle: BadgeStyle.DANGER,
text: 'Alumni',
});
});
it('renders user role', () => {
expect(wrapper.find('#user-role').text()).toEqual('Tester');
});
......@@ -366,6 +349,18 @@ describe('ProfilePage', () => {
);
});
it('renders alumni bullet is user not active', () => {
const userCopy = {
...globalState.user.profile.user,
is_active: false,
};
const { wrapper } = setup({
user: userCopy,
});
const expected = 1;
expect(wrapper.find('#alumni').length).toEqual(expected);
});
it('renders github link with correct href', () => {
expect(wrapper.find('#github-link').props().href).toEqual(
'https://github.com/githubName'
......
......@@ -43,6 +43,7 @@ import {
FOOTER_TEXT_PREFIX,
GITHUB_LINK_TEXT,
ITEMS_PER_PAGE,
NOT_ACTIVE_USER_TEXT,
OWNED_LABEL,
OWNED_SOURCE,
OWNED_TITLE_PREFIX,
......@@ -213,16 +214,7 @@ export class ProfilePage extends React.Component<
);
} else {
userName = (
<h1 className="h3 header-title-text truncated">
{user.display_name}
{!user.is_active && (
<Flag
caseType="sentenceCase"
labelStyle={BadgeStyle.DANGER}
text="Alumni"
/>
)}
</h1>
<h1 className="h3 header-title-text truncated">{user.display_name}</h1>
);
}
......@@ -238,6 +230,7 @@ export class ProfilePage extends React.Component<
{user.manager_fullname && (
<li id="user-manager">{`Manager: ${user.manager_fullname}`}</li>
)}
{!user.is_active && <li id="alumni">{NOT_ACTIVE_USER_TEXT}</li>}
</ul>
</div>
);
......
......@@ -15,6 +15,7 @@ import TableHeaderBullets, { TableHeaderBulletsProps } from '.';
const MOCK_RESOURCE_DISPLAY_NAME = 'Test';
const MOCK_DB_DISPLAY_NAME = 'AlsoTest';
const TABLE_VIEW_TEXT = 'table view';
jest.mock('config/config-utils', () => ({
getDisplayNameByResource: jest.fn(),
......@@ -26,6 +27,7 @@ describe('TableHeaderBullets', () => {
const props: TableHeaderBulletsProps = {
database: 'hive',
cluster: 'main',
isView: true,
...propOverrides,
};
const wrapper = shallow(<TableHeaderBullets {...props} />);
......@@ -71,5 +73,9 @@ describe('TableHeaderBullets', () => {
it('renders a list with cluster', () => {
expect(listElement.find('li').at(2).text()).toEqual(props.cluster);
});
it('renders a list with table view', () => {
expect(listElement.find('li').at(3).text()).toEqual(TABLE_VIEW_TEXT);
});
});
});
......@@ -10,20 +10,25 @@ import {
import { ResourceType } from 'interfaces/Resources';
import { TABLE_VIEW_TEXT } from './constants';
export interface TableHeaderBulletsProps {
cluster: string;
database: string;
isView: boolean;
}
const TableHeaderBullets: React.FC<TableHeaderBulletsProps> = ({
cluster,
database,
isView,
}: TableHeaderBulletsProps) => {
return (
<ul className="header-bullets">
<li>{getDisplayNameByResource(ResourceType.table)}</li>
<li>{getSourceDisplayName(database, ResourceType.table)}</li>
<li>{cluster}</li>
{isView && <li>{TABLE_VIEW_TEXT}</li>}
</ul>
);
};
......@@ -31,6 +36,7 @@ const TableHeaderBullets: React.FC<TableHeaderBulletsProps> = ({
TableHeaderBullets.defaultProps = {
cluster: '',
database: '',
isView: false,
};
export default TableHeaderBullets;
......@@ -27,7 +27,6 @@ import TabsComponent from 'components/common/TabsComponent';
import TagInput from 'components/common/Tags/TagInput';
import EditableText from 'components/common/EditableText';
import LoadingSpinner from 'components/common/LoadingSpinner';
import Flag from 'components/common/Flag';
import ColumnList from 'components/TableDetail/ColumnList';
import DataPreviewButton from 'components/TableDetail/DataPreviewButton';
......@@ -238,11 +237,9 @@ export class TableDetail extends React.Component<
<TableHeaderBullets
database={data.database}
cluster={data.cluster}
isView={data.is_view}
/>
{data.badges.length > 0 && <BadgeList badges={data.badges} />}
{data.is_view && (
<Flag text="table view" labelStyle={BadgeStyle.WARNING} />
)}
</div>
</div>
<div className="header-section header-links">
......
......@@ -86,7 +86,8 @@ $card-copy-max-lines: 3;
@each $line in $shimmer-loader-items {
.shimmer-row-line--#{$line} {
width: (
width:
(
random($shimmer-loader-row-max-width - $shimmer-loader-row-min-width) +
$shimmer-loader-row-min-width
) +
......
......@@ -182,38 +182,6 @@ describe('UserListItem', () => {
expect(resourceBadges.exists()).toBe(true);
});
it('does not render Alumni flag if user is active', () => {
expect(resourceBadges.find(Flag).exists()).toBe(false);
});
it('renders Alumni flag if user not active', () => {
const { wrapper } = setup({
user: {
type: ResourceType.user,
display_name: 'firstname lastname',
email: 'test@test.com',
employee_type: 'fulltime',
first_name: 'firstname',
full_name: 'firstname lastname',
github_username: 'githubName',
is_active: false,
last_name: 'lastname',
manager_fullname: 'Test Manager',
profile_url: 'www.test.com',
role_name: 'Tester',
slack_id: 'www.slack.com',
team_name: 'QA',
user_id: 'test0',
},
});
const flagComponent = wrapper.find('.resource-badges').find(Flag);
expect(flagComponent.exists()).toBe(true);
expect(flagComponent.props()).toMatchObject({
text: 'Alumni',
labelStyle: BadgeStyle.DANGER,
});
});
it('renders correct end icon', () => {
const expectedClassName = 'icon icon-right';
expect(resourceBadges.find('img').props().className).toEqual(
......
......@@ -56,9 +56,6 @@ class UserListItem extends React.Component<UserListItemProps, {}> {
</div>
<div className="resource-type">User</div>
<div className="resource-badges">
{!user.is_active && (
<Flag text="Alumni" labelStyle={BadgeStyle.DANGER} />
)}
<img className="icon icon-right" alt="" />
</div>
</Link>
......
// Copyright Contributors to the Amundsen project.
// SPDX-License-Identifier: Apache-2.0
import * as React from 'react';
import { mount } from 'enzyme';
import ResourceStatusMarker, { StatusMarkerProps } from '.';
const setup = (propsOverrides?: Partial<StatusMarkerProps>) => {
const props = {
stateText: '',
succeeded: false,
...propsOverrides,
};
const wrapper = mount<typeof ResourceStatusMarker>(
<ResourceStatusMarker {...props} />
);
return { props, wrapper };
};
describe('RunStateContainer', () => {
describe('Succeded', () => {
it('renders SuccessState when lastRunState successful', () => {
const { wrapper } = setup({
stateText: 'Succeeded',
succeeded: true,
});
const expected = 1;
const actual = wrapper.find('.success').length;
expect(actual).toEqual(expected);
});
});
describe('Failed', () => {
const { wrapper } = setup({
stateText: 'Failed',
});
it('renders MissedState when lastRunState failed', () => {
const expected = 1;
const actual = wrapper.find('.failure').length;
expect(actual).toEqual(expected);
});
it('renders failure icon', () => {
const expected = 1;
const actual = wrapper.find('.failure-icon').length;
expect(expected).toEqual(actual);
});
});
});
// Copyright Contributors to the Amundsen project.
// SPDX-License-Identifier: Apache-2.0
import * as React from 'react';
import './styles.scss';
export interface StatusMarkerProps {
stateText: string;
succeeded: boolean;
}
export interface StateProps {
stateText: string;
}
const FailureState: React.FC<StateProps> = ({ stateText }: StateProps) => {
return (
<div className="failure">
<div className="failure-icon">
<div className="exclamation-top" />
<div className="exclamation-bottom" />
</div>
<span className="status-text">{stateText}</span>
</div>
);
};
const SuccessState: React.FC<StateProps> = ({ stateText }: StateProps) => {
return (
<div className="success">
<div className="success-icon">
<span className="icon icon-check" />
</div>
<span className="status-text">{stateText}</span>
</div>
);
};
const ResourceStatusMarker: React.FC<StatusMarkerProps> = ({
stateText,
succeeded,
}: StatusMarkerProps) => {
const state = stateText.charAt(0).toUpperCase() + stateText.slice(1);
if (succeeded) {
return <SuccessState stateText={state} />;
}
return <FailureState stateText={state} />;
};
export default ResourceStatusMarker;
// Copyright Contributors to the Amundsen project.
// SPDX-License-Identifier: Apache-2.0
@import 'variables';
@import 'typography-default';
$icon-status-color: #f2f2f2;
$icon-status-missed: $sunset60;
$icon-status-hit: #00824c;
$icon-size: 14px;
.failure-icon,
.success-icon {
width: $icon-size;
height: $icon-size;
border-radius: 100%;
display: inline-block;
vertical-align: middle;
}
.failure-icon {
background-color: $icon-status-missed;
.exclamation-top {
width: 2px;
height: 4px;
background-color: $icon-status-color;
margin: 4px 6px 3px;
}
.exclamation-bottom {
width: 2px;
height: 2px;
border-radius: 100%;
background-color: $icon-status-color;
margin: -2px 6px;
}
}
.success-icon {
background-color: $icon-status-hit;
.icon-check {
background-color: $icon-status-color;
width: 10px;
height: 10px;
margin-top: -6px;
margin-left: 2px;
min-width: 0;
}
}
.status-text {
@extend %text-body-w2;
display: inline-block;
margin-left: $spacer-1;
vertical-align: middle;
}
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