Skip to content

Commit

Permalink
Jkmarx/add tools list landing page (#3122)
Browse files Browse the repository at this point in the history
* Add home module.

* Add chart.js.

* Update chart selection feature.

* Redirect user when clicking on chart.

* Add pointer and home css file and correct body height.

* Add unit tests and comments

* Fix name.

* Switch to object class due to phantomjs.

* Fix typo.

* Fix comments.

* Add toollist component.

* Populate tool list.

* Add workflow link.

* Different calls for getting with and without data_set_uuid.

* Add api unit test and more unit tests.

* Remove typos.

* Refactor.
  • Loading branch information
jkmarx committed Nov 29, 2018
1 parent 66a3996 commit 3a2cb79
Show file tree
Hide file tree
Showing 16 changed files with 343 additions and 83 deletions.
5 changes: 5 additions & 0 deletions refinery/tool_manager/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ class Meta:
class ToolDefinitionSerializer(serializers.ModelSerializer):
file_relationship = FileRelationshipSerializer()
parameters = ParameterSerializer(many=True, allow_null=True)
workflow = serializers.SerializerMethodField()

def get_workflow(self, tool):
if tool.workflow is not None:
return tool.workflow.uuid

class Meta:
model = ToolDefinition
Expand Down
36 changes: 17 additions & 19 deletions refinery/tool_manager/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,23 @@ def test_unallowed_http_verbs(self):
self.delete_response.data['detail'], 'Method "DELETE" not '
'allowed.')

def test_request_without_any_params_returns_all(self):
get_request = self.factory.get(self.tool_defs_url_root)
force_authenticate(get_request, self.user)
get_response = self.tool_defs_view(get_request)
serialized_data = ToolDefinitionSerializer(
ToolDefinition.objects.all(), many=True
).data
self.assertEqual(get_response.status_code, 200)
self.assertEqual(get_response.data, serialized_data)

def test_request_without_data_set_uuid_returns_all(self):
serialized_data = ToolDefinitionSerializer(
ToolDefinition.objects.all(), many=True
).data
self.assertEqual(self.get_response.status_code, 200)
self.assertEqual(self.get_response.data, serialized_data)

def test_for_proper_parameters_in_response(self):
"""ToolDefinitions for Workflows will have an extra field on their
parameter objects
Expand Down Expand Up @@ -710,25 +727,6 @@ def test_request_from_public_dataset_shows_vis_tools_only(self):
)
)

def test_no_query_params_in_get_yields_bad_request(self):
get_request = self.factory.get(self.tool_defs_url_root)
force_authenticate(get_request, self.user)
get_response = self.tool_defs_view(get_request)
self.assertEqual(get_response.status_code, 400)
self.assertIn("Must specify a DataSet UUID", get_response.content)

def test_bad_query_params_in_get_yields_bad_request(self):
get_request = self.factory.get(
"{}?coffee={}".format(
self.tool_defs_url_root,
self.dataset.uuid
)
)
force_authenticate(get_request, self.user)
get_response = self.tool_defs_view(get_request)
self.assertEqual(get_response.status_code, 400)
self.assertIn("Must specify a DataSet UUID", get_response.content)

def test_missing_dataset_in_get_yields_bad_request(self):
dataset_uuid = str(uuid.uuid4())

Expand Down
9 changes: 9 additions & 0 deletions refinery/tool_manager/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django_docker_engine.proxy import Proxy
from rest_framework.decorators import detail_route
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from core.models import DataSet
Expand Down Expand Up @@ -56,6 +57,14 @@ class ToolDefinitionsViewSet(ToolManagerViewSetBase):
lookup_field = 'uuid'
http_method_names = ['get']

def list(self, request):
if request.query_params.get('data_set_uuid'):
return super(ToolDefinitionsViewSet, self).list(request)

serializer = ToolDefinitionSerializer(ToolDefinition.objects.all(),
many=True)
return Response(serializer.data)

def get_queryset(self):
if self.request.user.has_perm('core.change_dataset', self.data_set):
return ToolDefinition.objects.all()
Expand Down
2 changes: 2 additions & 0 deletions refinery/ui/Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,8 @@ module.exports = function (grunt) {
'<%= cfg.basePath.ui.src %>/styles/treemap.less',
'<%= cfg.basePath.ui.compile %>/styles/dashboard.css':
'<%= cfg.basePath.ui.src %>/styles/dashboard.less',
'<%= cfg.basePath.ui.compile %>/styles/home.css':
'<%= cfg.basePath.ui.src %>/styles/home.less',
'<%= cfg.basePath.ui.compile %>/styles/provenance-visualization.css':
'<%= cfg.basePath.ui.src %>/styles/provenance-visualization.less',
'<%= cfg.basePath.ui.compile %>/styles/file-browser.css':
Expand Down
18 changes: 0 additions & 18 deletions refinery/ui/source/js/home/ctrls/home-ctrl.js

This file was deleted.

24 changes: 0 additions & 24 deletions refinery/ui/source/js/home/ctrls/home-ctrl.spec.js

This file was deleted.

35 changes: 35 additions & 0 deletions refinery/ui/source/js/home/ctrls/tool-list-ctrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Tool List Ctrl
* @namespace ToolListCtrl
* @desc Component controller for the tool list partial.
* @memberOf refineryApp.refineryHome
*/
(function () {
'use strict';
angular
.module('refineryHome')
.controller('ToolListCtrl', ToolListCtrl);

ToolListCtrl.$inject = ['toolListService'];

function ToolListCtrl (toolListService) {
var vm = this;
vm.toolList = toolListService.toolList;

activate();
/*
* -----------------------------------------------------------------------------
* Methods
* -----------------------------------------------------------------------------
*/
function activate () {
// tool list won't be updated often, so no need for api call
// if list is already populated
if (!vm.toolList.length) {
toolListService.getTools().then(function () {
vm.toolList = toolListService.toolList;
});
}
}
}
})();
65 changes: 65 additions & 0 deletions refinery/ui/source/js/home/ctrls/tool-list-ctrl.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
(function () {
'use strict';

describe('Controller: Tool List Ctrl', function () {
var ctrl;
var genCtrl;
var mockService;
var mockServiceResponse = false;
var scope;
var service;
var rootScope;

beforeEach(module('refineryApp'));
beforeEach(module('refineryHome'));
beforeEach(inject(function (
$rootScope,
$controller,
$httpBackend,
settings,
toolListService
) {
service = toolListService;
mockService = spyOn(service, 'getTools').and.callFake(function () {
return {
then: function () {
mockServiceResponse = true;
}
};
});
rootScope = $rootScope;
scope = rootScope.$new();
genCtrl = $controller;
ctrl = $controller('ToolListCtrl', {
$scope: scope
});
}));

it('Tool List Ctrl ctrl should exist', function () {
expect(ctrl).toBeDefined();
});

it('data variables should exist for views', function () {
expect(ctrl.toolList).toEqual([]);
});

it('should call service when initalized', function () {
scope.$apply();
expect(mockService).toHaveBeenCalled();
expect(mockServiceResponse).toEqual(true);
});

it('should not call service when list exists', function () {
mockServiceResponse = false;
mockService.calls.reset();
angular.copy([{ name: 'FastQC' }], service.toolList);
var newScope = rootScope.$new();
genCtrl('ToolListCtrl', {
$scope: newScope
});
newScope.$apply();
expect(mockService).not.toHaveBeenCalled();
expect(mockServiceResponse).toEqual(false);
});
});
})();
2 changes: 0 additions & 2 deletions refinery/ui/source/js/home/directives/home.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@
$templateCache,
$window
) {
// Parent component contains the input-group (child) component
$templateCache.put(
$window.getStaticUrl('partials/home/views/home.html'),
'<div id="home-main">/div>'
);

var scope = $rootScope.$new();
// Parent component
var template = '<rp-home></rp-home>';
directiveElement = $compile(template)(scope);
scope.$digest();
Expand Down
17 changes: 17 additions & 0 deletions refinery/ui/source/js/home/directives/tool-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Tool List Component
* @namespace rpToolList
* @desc Component to show a list of all the tools
* @memberOf refineryApp.refineryHome
*/
(function () {
'use strict';
angular
.module('refineryHome')
.component('rpToolList', {
controller: 'ToolListCtrl',
templateUrl: ['$window', function ($window) {
return $window.getStaticUrl('partials/home/partials/tool-list.html');
}]
});
})();
40 changes: 40 additions & 0 deletions refinery/ui/source/js/home/directives/tool-list.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
(function () {
'use strict';

describe('rpToolList component unit test', function () {
beforeEach(module('refineryApp'));
beforeEach(module('refineryHome'));

var directiveElement;

beforeEach(inject(function (
$compile,
$httpBackend,
$rootScope,
$templateCache,
$window,
settings
) {
// Mock api call due to ctrl activate method
$httpBackend
.whenGET(
settings.appRoot +
settings.refineryApiV2 + '/tool_definitions/?data_set_uuid=').respond(200, []);

$templateCache.put(
$window.getStaticUrl('partials/home/partials/tool-list.html'),
'<div id="tool-list">/div>'
);

var scope = $rootScope.$new();
var template = '<rp-tool-list></rp-tool-list>';
directiveElement = $compile(template)(scope);
scope.$digest();
}));

it('generates the appropriate HTML', function () {
expect(directiveElement.html()).toContain('tool-list');
expect(directiveElement.html()).toContain('</div>');
});
});
})();
36 changes: 17 additions & 19 deletions refinery/ui/source/js/home/partials/data-set-chart.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
<div class="row p-t-1">
<div class="col-md-4 col-md-offset-7">
<ui-select
append-to-body="true"
ng-model="$ctrl.selectedAttribute.select"
on-select="$ctrl.updateAttribute($item)"
theme="select2"
class="text-left p-l-1 p-b-1">
<ui-select-match placeholder="Select an attribute">
{{$select.selected.name }}
</ui-select-match>
<ui-select-choices
position="down"
repeat="attribute in $ctrl.attributes | orderBy:'name' track by $index">
<span ng-bind-html="attribute.name"></span>
</ui-select-choices>
</ui-select>
<canvas id="files-bar-chart"></canvas>
</div>
<div>
<ui-select
append-to-body="true"
ng-model="$ctrl.selectedAttribute.select"
on-select="$ctrl.updateAttribute($item)"
theme="select2"
class="text-left p-l-1 p-b-1">
<ui-select-match placeholder="Select an attribute">
{{$select.selected.name }}
</ui-select-match>
<ui-select-choices
position="down"
repeat="attribute in $ctrl.attributes | orderBy:'name' track by $index">
<span ng-bind-html="attribute.name"></span>
</ui-select-choices>
</ui-select>
<canvas id="files-bar-chart"></canvas>
</div>
21 changes: 21 additions & 0 deletions refinery/ui/source/js/home/partials/tool-list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div>
<div class="refinery-header">
<h3>
Analysis and Visualization Tools
</h3>
</div>
<ul class="list-unstyled">
<li ng-repeat="tool in $ctrl.toolList" class="p-b-1-2">
<i class="{{tool.tool_type|toolTypeIcon}} p-r-1-4" aria-hidden="true"></i>
<span ng-if="tool.tool_type=='WORKFLOW'">
<a ng-href="/workflows/{{tool.workflow}}">
{{ tool.name }}
</a>
</span>
<span ng-if="tool.tool_type!=='WORKFLOW'">
{{ tool.name }}
</span>
- {{ tool.description }}
</li>
</ul>
</div>

0 comments on commit 3a2cb79

Please sign in to comment.