Unverified Commit 2612b4b9 authored by Daniel's avatar Daniel Committed by GitHub

Integrate metadata user API (#90)

* Integrate metadata '/user' API
* Update UserSchema/User class and TS interfaces
* '/auth_user' API now piggybacks on the metadata '/user' API
* Move user test data into fixtures/globalState.ts
parent 41735ddd
......@@ -13,9 +13,10 @@ from amundsen_application.models.user import load_user, dump_user
from amundsen_application.api.utils.request_utils import get_query_param, request_wrapper
LOGGER = logging.getLogger(__name__)
REQUEST_SESSION_TIMEOUT = 10
REQUEST_SESSION_TIMEOUT_SEC = 3
metadata_blueprint = Blueprint('metadata', __name__, url_prefix='/api/metadata/v0')
......@@ -23,6 +24,7 @@ TABLE_ENDPOINT = '/table'
LAST_INDEXED_ENDPOINT = '/latest_updated_ts'
POPULAR_TABLES_ENDPOINT = '/popular_tables/'
TAGS_ENDPOINT = '/tags/'
USER_ENDPOINT = '/user'
def _get_table_endpoint() -> str:
......@@ -72,7 +74,7 @@ def popular_tables() -> Response:
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
......@@ -124,7 +126,7 @@ def _send_metadata_get_request(url: str) -> Response:
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
def _get_partition_data(watermarks: Dict) -> Dict:
......@@ -238,7 +240,7 @@ def _update_table_owner(*, table_key: str, method: str, owner: str) -> Dict[str,
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
# TODO: Figure out a way to get this payload from flask.jsonify which wraps with app's response_class
return {'msg': 'Updated owner'}
......@@ -275,7 +277,7 @@ def get_last_indexed() -> Response:
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
......@@ -305,7 +307,7 @@ def get_table_description() -> Response:
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
......@@ -337,7 +339,7 @@ def get_column_description() -> Response:
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
......@@ -379,7 +381,7 @@ def put_table_description() -> Response:
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
......@@ -421,7 +423,7 @@ def put_column_description() -> Response:
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
......@@ -452,7 +454,7 @@ def get_tags() -> Response:
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
......@@ -497,7 +499,7 @@ def update_table_tags() -> Response:
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
......@@ -516,7 +518,40 @@ def update_table_tags() -> Response:
return make_response(payload, HTTPStatus.INTERNAL_SERVER_ERROR)
# TODO: Implement
@metadata_blueprint.route('/user', methods=['GET'])
def get_user() -> Response:
return make_response(jsonify({'msg': 'Not implemented'}), HTTPStatus.NOT_IMPLEMENTED)
@action_logging
def _log_get_user(*, user_id: str) -> None:
pass # pragma: no cover
try:
user_id = get_query_param(request.args, 'user_id')
url = '{0}{1}/{2}'.format(app.config['METADATASERVICE_BASE'], USER_ENDPOINT, user_id)
_log_get_user(user_id=user_id)
response = request_wrapper(method='GET',
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
if status_code == HTTPStatus.OK:
message = 'Success'
else:
message = 'Encountered error: failed to fetch user with user_id: {0}'.format(user_id)
logging.error(message)
payload = {
'msg': message,
'user': dump_user(load_user(response.json())),
}
return make_response(jsonify(payload), status_code)
except Exception as e:
message = 'Encountered exception: ' + str(e)
logging.exception(message)
payload = jsonify({'msg': message})
return make_response(payload, HTTPStatus.INTERNAL_SERVER_ERROR)
......@@ -13,7 +13,7 @@ from amundsen_application.api.utils.request_utils import get_query_param, reques
LOGGER = logging.getLogger(__name__)
REQUEST_SESSION_TIMEOUT = 10
REQUEST_SESSION_TIMEOUT_SEC = 3
search_blueprint = Blueprint('search', __name__, url_prefix='/api/search/v0')
......@@ -201,7 +201,7 @@ def _search_table(*, search_term: str, page_index: int) -> Dict[str, Any]:
url=url,
client=app.config['SEARCHSERVICE_REQUEST_CLIENT'],
headers=app.config['SEARCHSERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT)
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
......
import logging
from flask import Response
from http import HTTPStatus
from flask import Response, jsonify, make_response
from flask import current_app as app
from flask.blueprints import Blueprint
from amundsen_application.models.user import load_user
from amundsen_application.api.metadata.v0 import USER_ENDPOINT
from amundsen_application.api.utils.request_utils import request_wrapper
from amundsen_application.models.user import load_user, dump_user
REQUEST_SESSION_TIMEOUT_SEC = 3
LOGGER = logging.getLogger(__name__)
......@@ -13,9 +19,34 @@ blueprint = Blueprint('api', __name__, url_prefix='/api')
@blueprint.route('/auth_user', methods=['GET'])
def current_user() -> Response:
if (app.config['AUTH_USER_METHOD']):
user = app.config['AUTH_USER_METHOD'](app)
else:
user = load_user({'user_id': 'undefined', 'display_name': '*'})
try:
if app.config['AUTH_USER_METHOD']:
user = app.config['AUTH_USER_METHOD'](app)
else:
raise Exception('AUTH_USER_METHOD is not configured')
url = '{0}{1}/{2}'.format(app.config['METADATASERVICE_BASE'], USER_ENDPOINT, user.user_id)
response = request_wrapper(method='GET',
url=url,
client=app.config['METADATASERVICE_REQUEST_CLIENT'],
headers=app.config['METADATASERVICE_REQUEST_HEADERS'],
timeout_sec=REQUEST_SESSION_TIMEOUT_SEC)
status_code = response.status_code
if status_code == HTTPStatus.OK:
message = 'Success'
else:
message = 'Encountered error: failed to fetch user with user_id: {0}'.format(user.user_id)
logging.error(message)
return user.to_json()
payload = {
'msg': message,
'user': dump_user(load_user(response.json()))
}
return make_response(jsonify(payload), status_code)
except Exception as e:
message = 'Encountered exception: ' + str(e)
logging.exception(message)
payload = jsonify({'msg': message})
return make_response(payload, HTTPStatus.INTERNAL_SERVER_ERROR)
......@@ -13,35 +13,36 @@ redesign how User handles names
class User:
# TODO: alphabetize after we have the real params
def __init__(self,
display_name: str = None,
email: str = None,
employee_type: str = None,
first_name: str = None,
full_name: str = None,
github_username: str = None,
is_active: bool = True,
last_name: str = None,
email: str = None,
display_name: str = None,
manager_fullname: str = None,
profile_url: str = None,
user_id: str = None,
github_name: str = None,
is_active: bool = True,
manager_name: str = None,
role_name: str = None,
slack_url: str = None,
team_name: str = None) -> None:
slack_id: str = None,
team_name: str = None,
user_id: str = None) -> None:
self.display_name = display_name
self.email = email
self.employee_type = employee_type
self.first_name = first_name
self.full_name = full_name
self.github_username = github_username
self.is_active = is_active
self.last_name = last_name
self.email = email
self.display_name = display_name
self.manager_fullname = manager_fullname
self.profile_url = profile_url
# TODO: modify the following names as needed after backend support is implemented
self.user_id = user_id
self.github_name = github_name
self.is_active = is_active
self.manager_name = manager_name
self.role_name = role_name
self.slack_url = slack_url
self.slack_id = slack_id
self.team_name = team_name
# TODO: frequent_used, bookmarked, & owned resources
self.user_id = user_id
# TODO: Add frequent_used, bookmarked, & owned resources
def to_json(self) -> Response:
user_info = dump_user(self)
......@@ -49,40 +50,34 @@ class User:
class UserSchema(Schema):
display_name = fields.Str(allow_none=True)
email = fields.Str(allow_none=True)
employee_type = fields.Str(allow_none=True)
first_name = fields.Str(allow_none=True)
full_name = fields.Str(allow_none=True)
github_username = fields.Str(allow_none=True)
is_active = fields.Bool(allow_none=True)
last_name = fields.Str(allow_none=True)
email = fields.Str(allow_none=True)
display_name = fields.Str(required=True)
manager_fullname = fields.Str(allow_none=True)
profile_url = fields.Str(allow_none=True)
user_id = fields.Str(required=True)
github_name = fields.Str(allow_none=True)
is_active = fields.Bool(allow_none=True)
manager_name = fields.Str(allow_none=True)
role_name = fields.Str(allow_none=True)
slack_url = fields.Str(allow_none=True)
slack_id = fields.Str(allow_none=True)
team_name = fields.Str(allow_none=True)
user_id = fields.Str(required=True)
@pre_load
def generate_display_name(self, data: Dict) -> Dict:
if data.get('display_name', None):
return data
if data.get('first_name', None) or data.get('last_name', None):
data['display_name'] = "{} {}".format(data.get('first_name', ''), data.get('last_name', '')).strip()
return data
def preprocess_data(self, data: Dict) -> Dict:
if not data.get('user_id', None):
data['user_id'] = data.get('email', None)
data['display_name'] = data.get('email', None)
return data
if not data.get('profile_url', None):
data['profile_url'] = ''
if app.config['GET_PROFILE_URL']:
data['profile_url'] = app.config['GET_PROFILE_URL'](data['user_id'])
@pre_load
def generate_profile_url(self, data: Dict) -> Dict:
if data.get('profile_url', None):
return data
if not data.get('display_name', None):
data['display_name'] = data.get('full_name', data.get('email'))
data['profile_url'] = ''
if app.config['GET_PROFILE_URL']:
data['profile_url'] = app.config['GET_PROFILE_URL'](data['display_name'])
return data
@post_load
......@@ -92,7 +87,8 @@ class UserSchema(Schema):
@validates_schema
def validate_user(self, data: Dict) -> None:
if not data.get('display_name', None):
raise ValidationError('One or more must be provided: "first_name", "last_name", "email", "display_name"')
raise ValidationError('"display_name" must be provided')
if not data.get('user_id', None):
raise ValidationError('"user_id" must be provided')
......
......@@ -37,10 +37,7 @@ import globalState from 'fixtures/globalState';
describe('NavBar', () => {
const setup = (propOverrides?: Partial<NavBarProps>) => {
const props: NavBarProps = {
loggedInUser: {
user_id: 'test0',
display_name: 'Test User',
},
loggedInUser: globalState.user.loggedInUser,
getLoggedInUser: jest.fn(),
...propOverrides
};
......@@ -108,7 +105,7 @@ describe('NavBar', () => {
it('renders homepage Link with correct text', () => {
element = wrapper.find('#nav-bar-left').find(Link);
expect(element.children().text()).toEqual('AMUNDSEN');
})
});
it('calls generateNavLinks with correct props', () => {
expect(spy).toHaveBeenCalledWith(AppConfig.navLinks);
......
......@@ -47,10 +47,10 @@ export class ProfilePage extends React.Component<ProfilePageProps> {
createEmptyTabMessage = (message: string) => {
return (
<div className="empty-tab-message">
<text>{ message }</text>
<label>{ message }</label>
</div>
);
}
};
generateTabInfo = () => {
const user = this.props.user;
......@@ -75,7 +75,7 @@ export class ProfilePage extends React.Component<ProfilePageProps> {
});
return tabInfo;
}
};
/* TODO: Add support to direct to 404 page for edgecase of someone typing in
or pasting in a bad url. This would be consistent with TableDetail page behavior */
......@@ -84,56 +84,69 @@ export class ProfilePage extends React.Component<ProfilePageProps> {
return (
<DocumentTitle title={ `${user.display_name} - Amundsen Profile` }>
<div className="container profile-page">
<Breadcrumb path='/' text='Search Results'/>
<div className="profile-header">
<div id="profile-avatar" className="profile-avatar">
{
// default Avatar looks a bit jarring -- intentionally not rendering if no display_name
user.display_name && user.display_name.length > 0 &&
<Avatar name={user.display_name} size={74} round={true} />
}
<div className="row">
<div className="col-xs-12 col-md-offset-1 col-md-10">
<Breadcrumb path='/' text='Search Results'/>
<div className="profile-header">
<div id="profile-avatar" className="profile-avatar">
{
// default Avatar looks a bit jarring -- intentionally not rendering if no display_name
user.display_name && user.display_name.length > 0 &&
<Avatar name={user.display_name} size={74} round={true} />
}
</div>
<div className="profile-details">
<div id="profile-title" className="profile-title">
<h1>{ user.display_name }</h1>
{
(!user.is_active) &&
<Flag caseType="sentenceCase" labelStyle="label-danger" text="Alumni"/>
}
</div>
{
user.role_name && user.team_name &&
<label id="user-role">{ `${user.role_name} on ${user.team_name}` }</label>
}
{
user.manager_fullname &&
<label id="user-manager">{ `Manager: ${user.manager_fullname}` }</label>
}
<div className="profile-icons">
{
user.is_active &&
<a id="slack-link" href={user.slack_id} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-slack'/>
<span>Slack</span>
</a>
}
{
user.is_active &&
<a id="email-link" href={`mailto:${user.email}`} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-mail'/>
<span>{ user.email }</span>
</a>
}
{
user.is_active && user.profile_url &&
<a id="profile-link" href={user.profile_url} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-users'/>
<span>Employee Profile</span>
</a>
}
{
user.github_username &&
<a id="github-link" href={`https://github.com/${user.github_username}`} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-github'/>
<span>Github</span>
</a>
}
</div>
</div>
</div>
<div className="profile-details">
<div id="profile-title" className="profile-title">
<h1>{ user.display_name }</h1>
{
(!user.is_active) &&
<Flag caseType="sentenceCase" labelStyle="label-danger" text="Alumni"/>
}
</div>
<text id="user-role">{ `${user.role_name} on ${user.team_name}` }</text>
<text id="user-manager">{ `Manager: ${user.manager_name}` }</text>
<div className="profile-icons">
{
user.is_active &&
<a id="slack-link" href={user.slack_url} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-slack'/>
<span>Slack</span>
</a>
}
{
user.is_active &&
<a id="email-link" href={`mailto:${user.email}`} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-mail'/>
<span>{ user.email }</span>
</a>
}
{
user.is_active &&
<a id="profile-link" href={user.profile_url} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-users'/>
<span>Employee Profile</span>
</a>
}
<a id="github-link" href={`https://github.com/${user.github_name}`} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-github'/>
<span>Github</span>
</a>
</div>
<div id="profile-tabs" className="profile-tabs">
<Tabs tabs={ this.generateTabInfo() } defaultTab='frequentUses_tab' />
</div>
</div>
<div id="profile-tabs" className="profile-tabs">
<Tabs tabs={ this.generateTabInfo() } defaultTab='frequentUses_tab' />
</div>
</div>
</div>
</DocumentTitle>
......@@ -145,7 +158,7 @@ export const mapStateToProps = (state: GlobalState) => {
return {
user: state.user.profileUser,
}
}
};
export const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ getUserById }, dispatch);
......
@import 'variables';
.profile-page {
margin-bottom: 48px;
}
.profile-header {
display: flex;
.profile-avatar {
margin-left: 24px;
margin-right: 24px;
}
.profile-details {
.profile-header {
display: flex;
flex-direction: column;
h1 {
margin-bottom: 0px;
margin-top: 0px;
.profile-avatar {
margin-left: 8px;
margin-right: 30px;
}
.profile-icons {
margin-top: 12px;
.profile-details {
display: flex;
flex-direction: column;
a {
margin-left: 8px;
margin-right: 8px;
h1 {
margin-bottom: 0;
margin-top: 0;
}
&:first-child {
margin-left: 0px;
}
&:last-child {
margin-right: 0px;
.profile-icons {
margin-top: 12px;
a {
margin-left: 8px;
margin-right: 8px;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
}
}
.profile-title {
display: flex;
margin-bottom: 8px;
.profile-title {
display: flex;
margin-bottom: 8px;
.flag {
height: min-content; // TODO: consider moving height into Flag component
font-size: 100%;
margin: auto auto auto 12px;
.flag {
height: min-content; // TODO: consider moving height into Flag component
font-size: 100%;
margin: auto auto auto 12px;
}
}
}
}
}
}
.profile-tabs {
margin-top: 48px;
}
.profile-tabs {
margin-top: 64px;
// TODO: consider moving logic for empty content into Tab component
.empty-tab-message {
color: $gray-light;
margin-top: 64px;
text-align: center;
// TODO: consider moving logic for empty content into Tab component
.empty-tab-message {
color: $gray-light;
margin-top: 64px;
text-align: center;
}
}
}
......@@ -14,18 +14,7 @@ import globalState from 'fixtures/globalState';
describe('ProfilePage', () => {
const setup = (propOverrides?: Partial<ProfilePageProps>) => {
const props: ProfilePageProps = {
user: {
user_id: 'test0',
display_name: 'Test User',
email: 'test@test.com',
github_name: 'githubName',
is_active: true,
manager_name: 'Test Manager',
profile_url: 'www.test.com',
role_name: 'Tester',
slack_url: 'www.slack.com',
team_name: 'QA',
},
user: globalState.user.profileUser,
getUserById: jest.fn(),
...propOverrides
};
......@@ -73,7 +62,7 @@ describe('ProfilePage', () => {
});
it('creates text with given message', () => {
expect(shallow(content).find('text').text()).toEqual('Empty message');
expect(shallow(content).find('label').text()).toEqual('Empty message');
});
});
......@@ -119,19 +108,13 @@ describe('ProfilePage', () => {
});
it('does not render Avatar if user.display_name is empty string', () => {
const userCopy = {
...globalState.user.profileUser,
display_name: "",
} ;
const wrapper = setup({
user: {
user_id: 'test0',
display_name: '',
email: 'test@test.com',
github_name: 'githubName',
is_active: true,
manager_name: 'Test Manager',
profile_url: 'www.test.com',
role_name: 'Tester',
slack_url: 'www.slack.com',
team_name: 'QA',
}
user: userCopy,
}).wrapper;
expect(wrapper.find('#profile-avatar').children().exists()).toBeFalsy();
});
......@@ -141,19 +124,12 @@ describe('ProfilePage', () => {
});
it('renders Flag with correct props if user not active', () => {
const userCopy = {
...globalState.user.profileUser,
is_active: false,
};
const wrapper = setup({
user: {
user_id: 'test0',
display_name: '',
email: 'test@test.com',
github_name: 'githubName',
is_active: false,
manager_name: 'Test Manager',
profile_url: 'www.test.com',
role_name: 'Tester',
slack_url: 'www.slack.com',
team_name: 'QA',
}
user: userCopy,
}).wrapper;
expect(wrapper.find('#profile-title').find(Flag).props()).toMatchObject({
caseType: 'sentenceCase',
......
......@@ -88,7 +88,7 @@ class SearchBar extends React.Component<SearchBarProps, SearchBarState> {
render() {
const subTextClass = `subtext ${this.state.subTextClassName}`;
return (
<div id="search-bar" className="col-xs-12 col-md-offset-1 col-md-10">
<div id="search-bar">
<form className="search-bar-form" onSubmit={ this.handleValueSubmit }>
<input
id="search-input"
......
......@@ -150,14 +150,12 @@ export class SearchPage extends React.Component<SearchPageProps, SearchPageState
paginationStartIndex: 0,
};
return (
<div className="col-xs-12 col-md-offset-1 col-md-10">
<div className="search-list-container">
<div className="popular-tables-header">
<label>{POPULAR_TABLES_LABEL}</label>
<InfoButton infoText={POPULAR_TABLES_INFO_TEXT}/>
</div>
<SearchList results={ this.props.popularTables } params={ searchListParams }/>
<div className="search-list-container">
<div className="popular-tables-header">
<label>{POPULAR_TABLES_LABEL}</label>
<InfoButton infoText={POPULAR_TABLES_INFO_TEXT}/>
</div>
<SearchList results={ this.props.popularTables } params={ searchListParams }/>
</div>
)
};
......@@ -173,7 +171,7 @@ export class SearchPage extends React.Component<SearchPageProps, SearchPageState
];
return (
<div className="col-xs-12 col-md-offset-1 col-md-10">
<div>
<TabsComponent
tabs={ tabConfig }
defaultTab={ ResourceType.table }
......@@ -242,9 +240,11 @@ export class SearchPage extends React.Component<SearchPageProps, SearchPageState
const innerContent = (
<div className="container search-page">
<div className="row">
<SearchBar handleValueSubmit={ this.onSearchBarSubmit } searchTerm={ searchTerm }/>
{ searchTerm.length > 0 && this.renderSearchResults() }
{ searchTerm.length === 0 && this.renderPopularTables() }
<div className="col-xs-12 col-md-offset-1 col-md-10">
<SearchBar handleValueSubmit={ this.onSearchBarSubmit } searchTerm={ searchTerm }/>
{ searchTerm.length > 0 && this.renderSearchResults() }
{ searchTerm.length === 0 && this.renderPopularTables() }
</div>
</div>
</div>
);
......
......@@ -457,17 +457,17 @@ describe('SearchPage', () => {
content = shallow(wrapper.instance().renderPopularTables());
});
it('renders correct label for content', () => {
expect(content.children().at(0).children().at(0).find('label').text()).toEqual(POPULAR_TABLES_LABEL);
expect(content.children().at(0).find('label').text()).toEqual(POPULAR_TABLES_LABEL);
});
it('renders InfoButton with correct props', () => {
expect(content.children().at(0).children().at(0).find(InfoButton).props()).toMatchObject({
expect(content.children().at(0).find(InfoButton).props()).toMatchObject({
infoText: POPULAR_TABLES_INFO_TEXT,
});
});
it('renders SearchList with correct props', () => {
expect(content.children().at(0).children().find(SearchList).props()).toMatchObject({
expect(content.children().find(SearchList).props()).toMatchObject({
results: props.popularTables,
params: {
source: POPULAR_TABLES_SOURCE_NAME,
......@@ -483,11 +483,11 @@ describe('SearchPage', () => {
const content = shallow(wrapper.instance().renderSearchResults());
const expectedTabConfig = [
{
title: `Tables (${ props.tables.total_results })`,
title: `${TABLE_RESOURCE_TITLE} (${ props.tables.total_results })`,
key: ResourceType.table,
content: wrapper.instance().getTabContent(props.tables, 'tables'),
content: wrapper.instance().getTabContent(props.tables, TABLE_RESOURCE_TITLE),
}
]
];
expect(content.find(TabsComponent).props()).toMatchObject({
activeKey: wrapper.state().selectedTab,
defaultTab: ResourceType.table,
......
......@@ -22,7 +22,7 @@ class EntityCardSection extends React.Component<EntityCardSectionProps, EntityCa
super(props);
this.state = {
readOnly: true,
}
};
this.editButton = React.createRef();
this.toggleEditMode = this.toggleEditMode.bind(this);
......
......@@ -27,8 +27,20 @@ export function getUserById(userId: string): GetUserRequest {
const defaultUser = {
user_id: '',
display_name: '',
email: '',
employee_type: '',
first_name: '',
full_name: '',
github_username: '',
is_active: true,
last_name: '',
manager_fullname: '',
profile_url: '',
role_name: '',
slack_id: '',
team_name: '',
user_id: '',
};
const initialState: UserReducerState = {
loggedInUser: defaultUser,
......
// Setting up different types for now so we can iterate faster as shared params change
export interface User {
user_id: string;
email: string;
employee_type: string;
display_name: string;
email?: string;
first_name?: string;
github_name?: string;
is_active?: boolean;
last_name?: string;
manager_name?: string;
profile_url?: string;
first_name: string;
full_name: string;
github_username: string;
is_active: boolean;
last_name: string;
manager_fullname: string;
profile_url: string;
role_name?: string;
slack_url?: string;
team_name?: string;
slack_id: string;
team_name: string;
user_id: string;
}
export type LoggedInUser = User & {};
......
......@@ -106,12 +106,36 @@ const globalState: GlobalState = {
},
user: {
loggedInUser: {
user_id: 'user0',
display_name: 'User Name',
display_name: 'firstname lastname',
email: 'test@test.com',
employee_type: 'fulltime',
first_name: 'firstname',
full_name: 'firstname lastname',
github_username: 'githubName',
is_active: true,
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',
},
profileUser: {
user_id: 'user1',
display_name: 'User1 Name',
display_name: 'firstname lastname',
email: 'test@test.com',
employee_type: 'fulltime',
first_name: 'firstname',
full_name: 'firstname lastname',
github_username: 'githubName',
is_active: true,
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',
},
},
};
......
......@@ -6,7 +6,7 @@ from http import HTTPStatus
from amundsen_application import create_app
from amundsen_application.api.metadata.v0 import \
TABLE_ENDPOINT, LAST_INDEXED_ENDPOINT, POPULAR_TABLES_ENDPOINT, TAGS_ENDPOINT
TABLE_ENDPOINT, LAST_INDEXED_ENDPOINT, POPULAR_TABLES_ENDPOINT, TAGS_ENDPOINT, USER_ENDPOINT
local_app = create_app('amundsen_application.config.LocalConfig')
......@@ -164,6 +164,36 @@ class MetadataTest(unittest.TestCase):
"tag_name": "tag_4"
}
]
self.mock_user = {
"email": "test@test.com",
"employee_type": "FTE",
"first_name": "Firstname",
"full_name": "Firstname Lastname",
"github_username": "githubusername",
"is_active": True,
"last_name": "Lastname",
"manager_fullname": "Manager Fullname",
"role_name": "SWE",
"slack_id": "slackuserid",
"team_name": "Amundsen",
"user_id": "testuserid",
}
self.expected_parsed_user = {
"display_name": "Firstname Lastname",
"email": "test@test.com",
"employee_type": "FTE",
"first_name": "Firstname",
"full_name": "Firstname Lastname",
"github_username": "githubusername",
"is_active": True,
"last_name": "Lastname",
"manager_fullname": "Manager Fullname",
"profile_url": "https://test-profile-url.com",
"role_name": "SWE",
"slack_id": "slackuserid",
"team_name": "Amundsen",
"user_id": "testuserid",
}
@responses.activate
def test_popular_tables_success(self) -> None:
......@@ -475,3 +505,29 @@ class MetadataTest(unittest.TestCase):
}
)
self.assertEquals(response.status_code, HTTPStatus.OK)
@responses.activate
def test_get_user_failure(self) -> None:
"""
Test get_user fails when no user_id is specified
"""
url = local_app.config['METADATASERVICE_BASE'] + USER_ENDPOINT + '/testuser'
responses.add(responses.GET, url, json=self.mock_user, status=HTTPStatus.OK)
with local_app.test_client() as test:
response = test.get('/api/metadata/v0/user')
self.assertEquals(response.status_code, HTTPStatus.INTERNAL_SERVER_ERROR)
@responses.activate
def test_get_user_success(self) -> None:
"""
Test get_user success
"""
url = local_app.config['METADATASERVICE_BASE'] + USER_ENDPOINT + '/testuser'
responses.add(responses.GET, url, json=self.mock_user, status=HTTPStatus.OK)
with local_app.test_client() as test:
response = test.get('/api/metadata/v0/user', query_string=dict(user_id='testuser'))
data = json.loads(response.data)
self.assertEquals(response.status_code, HTTPStatus.OK)
self.assertCountEqual(data.get('user'), self.expected_parsed_user)
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