diff --git a/muranodashboard/environments/api.py b/muranodashboard/environments/api.py index 12dd10334..bf06cd2b9 100644 --- a/muranodashboard/environments/api.py +++ b/muranodashboard/environments/api.py @@ -195,6 +195,12 @@ def environment_update(request, environment_id, name): return api.muranoclient(request).environments.update(environment_id, name) +def action_allowed(request, environment_id): + env = environment_get(request, environment_id) + status = getattr(env, 'status', None) + return status not in ('deploying',) + + def services_list(request, environment_id): def strip(msg, to=100): return '%s...' % msg[:to] if len(msg) > to else msg @@ -273,6 +279,17 @@ def service_get(request, environment_id, service_id): return service +def extract_actions_list(service): + actions_data = service['?'].get('_actions', {}) + return dict((action_id, action.get('name')) for (action_id, action) + in actions_data.iteritems() if action.get('enabled')) + + +def run_action(request, environment_id, action_id): + mc = api.muranoclient(request) + return mc.actions.call(environment_id, action_id) + + def deployments_list(request, environment_id): LOG.debug('Deployments::List') deployments = api.muranoclient(request).deployments.list(environment_id) diff --git a/muranodashboard/environments/tabs.py b/muranodashboard/environments/tabs.py index 96efe06c4..a692334e9 100644 --- a/muranodashboard/environments/tabs.py +++ b/muranodashboard/environments/tabs.py @@ -70,6 +70,22 @@ class OverviewTab(tabs.Tab): return {'service': detail_info} +class AppActionsTab(tabs.Tab): + name = _('Actions') + slug = '_actions' + template_name = 'services/_actions.html' + + def get_context_data(self, request): + data = self.tab_group.kwargs['service'] + return {'actions': api.extract_actions_list(data), + 'service_id': self.tab_group.kwargs['service_id'], + 'environment_id': self.tab_group.kwargs['environment_id']} + + def allowed(self, request): + environment_id = self.tab_group.kwargs['environment_id'] + return api.action_allowed(request, environment_id) + + class ServiceLogsTab(tabs.Tab): name = _("Logs") slug = "service_logs" @@ -193,7 +209,7 @@ class EnvironmentDetailsTabs(tabs.TabGroup): class ServicesTabs(tabs.TabGroup): slug = "services_details" - tabs = (OverviewTab, ServiceLogsTab, ) + tabs = (OverviewTab, AppActionsTab, ServiceLogsTab) class DeploymentDetailsTabs(tabs.TabGroup): diff --git a/muranodashboard/environments/urls.py b/muranodashboard/environments/urls.py index 20ff891f8..b2fb572a1 100644 --- a/muranodashboard/environments/urls.py +++ b/muranodashboard/environments/urls.py @@ -36,6 +36,11 @@ urlpatterns = urls.patterns( views.EnvironmentDetails.as_view(), name='services'), + urls.url(ENVIRONMENT_ID + + r'/(?P[^/]+)/actions/(?P[^/]+)$', + views.ApplicationActions.as_view(), + name='actions'), + urls.url(ENVIRONMENT_ID + r'/services/get_d3_data$', views.JSONView.as_view(), name='d3_data'), diff --git a/muranodashboard/environments/views.py b/muranodashboard/environments/views.py index f33380891..24eb1ac38 100644 --- a/muranodashboard/environments/views.py +++ b/muranodashboard/environments/views.py @@ -20,6 +20,7 @@ from django import http from django.utils.translation import ugettext_lazy as _ from django.views import generic from horizon import exceptions +from horizon import messages from horizon import tables from horizon import tabs from horizon import workflows @@ -75,6 +76,26 @@ class EnvironmentDetails(tabs.TabbedTableView): return context +class ApplicationActions(generic.View): + @staticmethod + def get(request, environment_id=None, service_id=None, action_id=None): + if api.action_allowed(request, environment_id): + api.run_action(request, environment_id, action_id) + service = api.service_get(request, environment_id, service_id) + action_name = api.extract_actions_list(service).get(action_id, '-') + component_name = getattr(service, 'name', '-') + msg = _("Action '{0}' was scheduled for component '{1}.").format( + action_name, component_name) + messages.success(request, msg) + else: + msg = _("There is some action being run in an environment") + messages.error(request, msg) + url = reverse('horizon:murano:environments:services', + args=(environment_id,)) + + return http.HttpResponseRedirect(url) + + class DetailServiceView(tabs.TabView): tab_group_class = env_tabs.ServicesTabs template_name = 'services/details.html' @@ -82,7 +103,7 @@ class DetailServiceView(tabs.TabView): def get_context_data(self, **kwargs): context = super(DetailServiceView, self).get_context_data(**kwargs) context["service"] = self.get_data() - context["service_name"] = self.service.name + context["service_name"] = getattr(self.service, 'name', '-') env = api.environment_get(self.request, self.environment_id) context["environment_name"] = env.name return context diff --git a/muranodashboard/templates/services/_actions.html b/muranodashboard/templates/services/_actions.html new file mode 100644 index 000000000..7cb48c981 --- /dev/null +++ b/muranodashboard/templates/services/_actions.html @@ -0,0 +1,18 @@ +{% load i18n %} +
+

{% trans "Component Actions" %}

+
+ +{% if actions%} + +{% else %} +
+
+

{% trans "Component does not provide any actions." %}

+
+
+{% endif %} diff --git a/muranodashboard/tests/functional/MockApp/Classes/mock_muranopl.yaml b/muranodashboard/tests/functional/MockApp/Classes/mock_muranopl.yaml index c0b9c16a5..f140cd973 100644 --- a/muranodashboard/tests/functional/MockApp/Classes/mock_muranopl.yaml +++ b/muranodashboard/tests/functional/MockApp/Classes/mock_muranopl.yaml @@ -1,4 +1,3 @@ -#Note: it is a fake application, it isn't intended to be deployed Namespaces: =: io.murano.apps std: io.murano @@ -6,3 +5,11 @@ Namespaces: Name: MockApp Extends: std:Application + +Properties: + name: + Contract: $.string().notNull() + +Workflow: + deploy: + Usage: Action diff --git a/muranodashboard/tests/functional/MockApp/UI/mock_ui.yaml b/muranodashboard/tests/functional/MockApp/UI/mock_ui.yaml index b7b188a37..2b40683db 100644 --- a/muranodashboard/tests/functional/MockApp/UI/mock_ui.yaml +++ b/muranodashboard/tests/functional/MockApp/UI/mock_ui.yaml @@ -54,12 +54,12 @@ Forms: - name: name type: string - label: String - Application Name + label: Application Name description: >- Requirements: Just A-Z, a-z, 0-9, dash and underline are allowed, min/max value are defined. minLength: 2 - maxLength: 5 + maxLength: 12 regexpValidator: '^[-\w]+$' errorMessages: invalid: Just letters, numbers, underscores and hyphens are allowed. diff --git a/muranodashboard/tests/functional/base.py b/muranodashboard/tests/functional/base.py index 68c137932..57211ba4a 100644 --- a/muranodashboard/tests/functional/base.py +++ b/muranodashboard/tests/functional/base.py @@ -336,3 +336,17 @@ class ApplicationTestCase(ImageTestCase): self.fill_field(by.By.ID, 'id_{0}'.format(param), value) self.driver.find_element_by_xpath(consts.InputSubmit).click() self.driver.refresh() + + def start_deploy(self, app_id, app_name='TestApp'): + self.go_to_submenu('Applications') + self.select_and_click_action_for_app('quick-add', app_id) + field_id = "{0}_0-name".format(app_id) + self.fill_field(by.By.ID, field_id, value=app_name) + self.driver.find_element_by_xpath(consts.ButtonSubmit).click() + self.driver.find_element_by_xpath(consts.InputSubmit).click() + self.select_from_list('osImage', self.image.name) + + self.driver.find_element_by_xpath(consts.InputSubmit).click() + + self.driver.find_element_by_css_selector( + '#services__action_deploy_env').click() diff --git a/muranodashboard/tests/functional/sanity_check.py b/muranodashboard/tests/functional/sanity_check.py index 2fb89ebe9..ccddb376e 100644 --- a/muranodashboard/tests/functional/sanity_check.py +++ b/muranodashboard/tests/functional/sanity_check.py @@ -625,3 +625,28 @@ class TestSuiteApplications(base.ApplicationTestCase): c.Status.format('Ready'), sec=90) self.check_element_on_page(by.By.XPATH, c.CellStatus.format('up')) + + def test_check_actions_tab(self): + """Test check that action tab in deployed application is available + and actions are display in the corresponding tab + + Scenario: + 1. Navigate Applications and click MockApp 'Quick Deploy' + 2. Click deploy + 3. Wait 'Ready' status + 4. Click on application + 5. Check that 'Actions' tab is present + 6. Click on 'Actions' tab + 7. Check that application's actions are present + """ + self.start_deploy(self.mockapp_id) + + self.check_element_on_page(by.By.XPATH, + c.Status.format('Ready'), + sec=90) + + self.driver.find_element_by_link_text('TestApp').click() + self.check_element_on_page(by.By.LINK_TEXT, 'Actions') + self.driver.find_element_by_link_text('Actions').click() + + self.check_element_on_page(by.By.LINK_TEXT, 'deploy') diff --git a/requirements.txt b/requirements.txt index bc87b8b14..94efed485 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ six>=1.7.0 PyYAML>=3.1.0 django-floppyforms>=1.1 yaql>=0.2.3,<0.3 -python-muranoclient>=0.5.4 +python-muranoclient>=0.5.5