From 07c32fe7627a1e5df5512ec7ad550c0b84f9a671 Mon Sep 17 00:00:00 2001 From: MelsHyrule Date: Mon, 31 Oct 2022 12:13:01 -0400 Subject: [PATCH] Timeline UI Conversion --- .../timelines/options.rb | 2 + .../timeline-options-simple.schema.js | 141 + .../timeline-options/timeline-options.jsx | 105 + app/javascript/oldjs/controllers/index.js | 1 - .../timeline/timeline_options_controller.js | 85 - app/javascript/oldjs/miq_timeline.js | 1 + .../packs/component-definitions-common.js | 2 + .../timeline-options-form.spec.js.snap | 5432 +++++++++++++++++ .../spec/timeline-options-form/sample-data.js | 144 + .../timeline-options-form.spec.js | 59 + app/views/layouts/_tl_options.html.haml | 110 - app/views/layouts/_tl_show.html.haml | 7 +- .../timeline_options_controller_spec.js | 77 - 13 files changed, 5887 insertions(+), 279 deletions(-) create mode 100644 app/javascript/components/timeline-options/timeline-options-simple.schema.js create mode 100644 app/javascript/components/timeline-options/timeline-options.jsx delete mode 100644 app/javascript/oldjs/controllers/timeline/timeline_options_controller.js create mode 100644 app/javascript/spec/timeline-options-form/__snapshots__/timeline-options-form.spec.js.snap create mode 100644 app/javascript/spec/timeline-options-form/sample-data.js create mode 100644 app/javascript/spec/timeline-options-form/timeline-options-form.spec.js delete mode 100644 app/views/layouts/_tl_options.html.haml delete mode 100644 spec/javascripts/controllers/timeline/timeline_options_controller_spec.js diff --git a/app/controllers/application_controller/timelines/options.rb b/app/controllers/application_controller/timelines/options.rb index 5220cbf0410..7c5ad88d988 100644 --- a/app/controllers/application_controller/timelines/options.rb +++ b/app/controllers/application_controller/timelines/options.rb @@ -34,6 +34,8 @@ def update_from_params(params) self.levels = params[:tl_levels]&.map(&:to_sym) || group_levels self.categories = {} params.fetch(:tl_categories, []).each do |category_display_name| + next if category_display_name == "Other" + group_data = event_groups[events[category_display_name]] category = { :display_name => category_display_name, diff --git a/app/javascript/components/timeline-options/timeline-options-simple.schema.js b/app/javascript/components/timeline-options/timeline-options-simple.schema.js new file mode 100644 index 00000000000..297ff920786 --- /dev/null +++ b/app/javascript/components/timeline-options/timeline-options-simple.schema.js @@ -0,0 +1,141 @@ +import { componentTypes, validatorTypes } from '@@ddf'; + +const createSchemaSimple = ( + timelineEvents, managementGroupNames, managementGroupLevels, policyGroupNames, policyGroupLevels +) => ({ + fields: [ + { + component: componentTypes.SUB_FORM, + title: __('Options'), + id: 'options', + name: 'options', + fields: [ + { + component: componentTypes.SELECT, + id: 'timelineEvents', + name: 'timelineEvents', + label: __('Event Types'), + validate: [{ type: validatorTypes.REQUIRED }], + isRequired: true, + includeEmpty: true, + options: timelineEvents, + }, + /// /////////////////// + // Managment Events // + /// /////////////////// + { + component: componentTypes.SELECT, + id: 'managementGroupNames', + name: 'managementGroupNames', + label: __('Category Managements'), + options: managementGroupNames, + isMulti: true, + isSearchable: true, + isClearable: true, + simpleValue: true, + validate: [{ type: validatorTypes.REQUIRED }], + isRequired: true, + condition: { + and: [ + { + when: 'timelineEvents', + is: 'EmsEvent', + }, + ], + }, + }, + { + component: componentTypes.SELECT, + id: 'managementGroupLevels', + name: 'managementGroupLevels', + label: __('Levels Management'), + options: managementGroupLevels, + isMulti: true, + isSearchable: true, + isClearable: true, + simpleValue: true, + validate: [{ type: validatorTypes.REQUIRED }], + isRequired: true, + condition: { + and: [ + { + when: 'timelineEvents', + is: 'EmsEvent', + }, + ], + }, + }, + /// /////////////// + // Policy Events // + /// /////////////// + { + component: componentTypes.SELECT, + id: 'policyGroupNames', + name: 'policyGroupNames', + label: __('Category Policy'), + options: policyGroupNames, + isMulti: true, + isSearchable: true, + isClearable: true, + simpleValue: true, + validate: [{ type: validatorTypes.REQUIRED }], + isRequired: true, + condition: { + and: [ + { + when: 'timelineEvents', + is: 'MiqEvent', + }, + ], + }, + }, + { + component: componentTypes.RADIO, + label: __('Event Result'), + name: 'policyGroupLevels', + id: 'policyGroupLevels', + validate: [{ type: validatorTypes.REQUIRED }], + isRequired: true, + options: policyGroupLevels, + condition: { + and: [ + { + when: 'timelineEvents', + is: 'MiqEvent', + }, + ], + }, + }, + ], + }, + /// ////// + // Time // + /// ////// + { + component: componentTypes.SUB_FORM, + title: __('Dates Range'), + id: 'datesRange', + name: 'datesRange', + fields: [ + { + component: 'date-picker', + id: 'startDate', + name: 'startDate', + label: __('Start Date'), + validate: [{ type: validatorTypes.REQUIRED }], + isRequired: true, + }, + { + component: 'date-picker', + id: 'endDate', + name: 'endDate', + label: __('End Date'), + validate: [{ type: validatorTypes.REQUIRED }], + isRequired: true, + }, + ], + }, + ], +}); + +export default createSchemaSimple; diff --git a/app/javascript/components/timeline-options/timeline-options.jsx b/app/javascript/components/timeline-options/timeline-options.jsx new file mode 100644 index 00000000000..836094e4a59 --- /dev/null +++ b/app/javascript/components/timeline-options/timeline-options.jsx @@ -0,0 +1,105 @@ +import React, { useState, useEffect } from 'react'; +import MiqFormRenderer from '@@ddf'; +import PropTypes from 'prop-types'; +import createSchemaSimple from './timeline-options-simple.schema'; +import mapper from '../../forms/mappers/componentMapper'; + +const TimelineOptions = ({ url }) => { + const [{ + isLoading, timelineEvents, managementGroupNames, managementGroupLevels, policyGroupNames, policyGroupLevels, + }, setState] = useState({ + isLoading: true, + }); + + useEffect(() => { + if (isLoading) { + API.options(`/api/event_streams`).then((dropdownValues) => { + const data = dropdownValues.data.timeline_events; + const managementGroupNames = []; const managementGroupLevels = []; const policyGroupNames = []; const + policyGroupLevels = []; + const timelineEvents = [ + { label: data.EmsEvent.description, value: 'EmsEvent' }, + { label: data.MiqEvent.description, value: 'MiqEvent' }, + ]; + + // Management Events + Object.entries(data.EmsEvent.group_names).forEach((entry) => { + const [key, value] = entry; + managementGroupNames.push({ label: value, value }); + }); + Object.entries(data.EmsEvent.group_levels).forEach((entry) => { + const [key, value] = entry; + managementGroupLevels.push({ label: value, value: key }); + }); + + // Policy Events + Object.entries(data.MiqEvent.group_names).forEach((entry) => { + const [key, value] = entry; + policyGroupNames.push({ label: value, value: key }); + }); + // NOTE: data.MiqEvent.group_levels does not have the expected `Both` option + policyGroupLevels.push({ label: 'Success', value: 'success' }); + policyGroupLevels.push({ label: 'Failure', value: 'failure' }); + policyGroupLevels.push({ label: 'Both', value: 'both' }); + + // TODO: is there a way to make the above more elegant/shorter? + // NOTE: group_names for MiqEvents and MiqEvents includes the 'Other' option, + // this did not exist in previous versions of the timeline + setState((state) => ({ + ...state, + isLoading: false, + timelineEvents, + managementGroupNames, + managementGroupLevels, + policyGroupNames, + policyGroupLevels, + })); + }); + } + }); + + const onSubmit = (values) => { + miqSparkleOn(); + const show = values.timelineEvents === 'EmsEvent' ? 'timeline' : 'policy_timeline'; + const categories = values.timelineEvents === 'EmsEvent' ? values.managementGroupNames : values.policyGroupNames; + const vmData = { + tl_show: show, + tl_categories: categories, + tl_levels: values.managementGroupLevels ? values.managementGroupLevels : [], + tl_result: values.policyGroupLevels ? values.policyGroupLevels : 'success', + }; + window.ManageIQ.calendar.calDateFrom = values.startDate; + window.ManageIQ.calendar.calDateTo = values.endDate; + window.miqJqueryRequest(url, { beforeSend: true, data: vmData }); + }; + + const onReset = () => { + setState((state) => ({ + ...state, + })); + }; + + return !isLoading && ( + <> + + + ); +}; + +TimelineOptions.propTypes = { + url: PropTypes.string, +}; + +TimelineOptions.defaultProps = { + url: '', +}; + +export default TimelineOptions; diff --git a/app/javascript/oldjs/controllers/index.js b/app/javascript/oldjs/controllers/index.js index caa8711bd7e..196cd26008d 100644 --- a/app/javascript/oldjs/controllers/index.js +++ b/app/javascript/oldjs/controllers/index.js @@ -18,6 +18,5 @@ require('./ops/pglogical_replication_form_controller.js'); require('./playbook-reusable-code-mixin.js'); require('./reconfigure/reconfigure_form_controller.js'); require('./schedule/schedule_form_controller.js'); -require('./timeline/timeline_options_controller.js'); require('./tree_view_controller.js'); require('./vm_cloud/vm_cloud_live_migrate_form_controller.js'); diff --git a/app/javascript/oldjs/controllers/timeline/timeline_options_controller.js b/app/javascript/oldjs/controllers/timeline/timeline_options_controller.js deleted file mode 100644 index f6a9b7e09e5..00000000000 --- a/app/javascript/oldjs/controllers/timeline/timeline_options_controller.js +++ /dev/null @@ -1,85 +0,0 @@ -ManageIQ.angular.app.controller('timelineOptionsController', ['miqService', 'url', 'categories', '$scope', function(miqService, url, categories, $scope) { - var vm = this; - var init = function() { - vm.reportModel = { - tl_show: 'timeline', - tl_categories: ['Power Activity'], - tl_timerange: 'weeks', - tl_timepivot: 'ending', - tl_result: 'success', - tl_range_count: 1, - tl_date: new Date(ManageIQ.calendar.calDateTo), - }; - - vm.afterGet = true; - vm.dateOptions = { - autoclose: true, - todayHighlight: true, - orientation: 'bottom', - }; - ManageIQ.angular.scope = $scope; - vm.availableCategories = categories; - }; - - vm.clearLevelsIfNotSelected = function() { - if (vm.reportModel.tl_categories.length === 0) { - vm.reportModel.tl_levels = undefined; - } - }; - - vm.eventTypeUpdated = function() { - vm.reportModel.tl_categories = []; - }; - - vm.countDecrement = function() { - if (vm.reportModel.tl_range_count > 1) { - vm.reportModel.tl_range_count--; - } - }; - - vm.countIncrement = function() { - vm.reportModel.tl_range_count++; - }; - - vm.applyButtonClicked = function() { - if (vm.reportModel.tl_categories.length === 0) { - return; - } - - // process selections - if (vm.reportModel.tl_timerange === 'days') { - vm.reportModel.tl_typ = 'Hourly'; - vm.reportModel.tl_days = vm.reportModel.tl_range_count; - } else { - vm.reportModel.tl_typ = 'Daily'; - if (vm.reportModel.tl_timerange === 'weeks') { - vm.reportModel.tl_days = vm.reportModel.tl_range_count * 7; - } else { - vm.reportModel.tl_days = vm.reportModel.tl_range_count * 30; - } - } - - var selectedDay = moment(vm.reportModel.tl_date); - var startDay = selectedDay.clone(); - var endDay = selectedDay.clone(); - - if (vm.reportModel.tl_timepivot === 'starting') { - endDay.add(vm.reportModel.tl_days, 'days').toDate(); - vm.reportModel.miq_date = endDay.format('MM/DD/YYYY'); - } else if (vm.reportModel.tl_timepivot === 'centered') { - var enddays = Math.ceil(vm.reportModel.tl_days / 2); - startDay.subtract(enddays, 'days').toDate(); - endDay.add(enddays, 'days').toDate(); - vm.reportModel.miq_date = endDay.format('MM/DD/YYYY'); - } else if (vm.reportModel.tl_timepivot === 'ending') { - startDay.subtract(vm.reportModel.tl_days, 'days'); - vm.reportModel.miq_date = endDay.format('MM/DD/YYYY'); - } - ManageIQ.calendar.calDateFrom = startDay.toDate(); - ManageIQ.calendar.calDateTo = endDay.toDate(); - miqService.sparkleOn(); - miqService.miqAsyncAjaxButton(url, miqService.serializeModel(vm.reportModel)); - }; - - init(); -}]); diff --git a/app/javascript/oldjs/miq_timeline.js b/app/javascript/oldjs/miq_timeline.js index e23c7d76103..30c5c66e48f 100644 --- a/app/javascript/oldjs/miq_timeline.js +++ b/app/javascript/oldjs/miq_timeline.js @@ -171,6 +171,7 @@ }; })(ManageIQ); +// TODO: Convert this file to React window.miqInitTimeline = function(json) { if (!json) { return; diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js index 8398c2e9426..762fa49380c 100644 --- a/app/javascript/packs/component-definitions-common.js +++ b/app/javascript/packs/component-definitions-common.js @@ -109,6 +109,7 @@ import TableListViewWrapper from '../react/table_list_view_wrapper'; import TaggingWrapperConnected from '../components/taggingWrapper'; import { TagView } from '../tagging'; import TenantQuotaForm from '../components/tenant-quota-form'; +import TimelineOptions from '../components/timeline-options/timeline-options'; import ToastList from '../components/toast-list/toast-list'; import VmEditForm from '../components/vm-edit-form'; import TextualSummaryWrapper from '../react/textual_summary_wrapper'; @@ -254,6 +255,7 @@ ManageIQ.component.addReact('TagView', TagView); ManageIQ.component.addReact('TaggingWrapperConnected', TaggingWrapperConnected); ManageIQ.component.addReact('TenantQuotaForm', TenantQuotaForm); ManageIQ.component.addReact('TextualSummaryWrapper', TextualSummaryWrapper); +ManageIQ.component.addReact('TimelineOptions', TimelineOptions); ManageIQ.component.addReact('TimeProfileReportsTable', TimeProfileReportsTable); ManageIQ.component.addReact('TimeProfileTable', TimeProfileTable); ManageIQ.component.addReact('ToastList', ToastList); diff --git a/app/javascript/spec/timeline-options-form/__snapshots__/timeline-options-form.spec.js.snap b/app/javascript/spec/timeline-options-form/__snapshots__/timeline-options-form.spec.js.snap new file mode 100644 index 00000000000..5a66e54e8dc --- /dev/null +++ b/app/javascript/spec/timeline-options-form/__snapshots__/timeline-options-form.spec.js.snap @@ -0,0 +1,5432 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Show Timeline Options form component should render form 1`] = ` + + + + + + + , + , + , + ] + } + schema={ + Object { + "fields": Array [ + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "select", + "id": "timelineEvents", + "includeEmpty": true, + "isRequired": true, + "label": "Event Types", + "name": "timelineEvents", + "options": Array [ + Object { + "label": "Management Events", + "value": "EmsEvent", + }, + Object { + "label": "Policy Events", + "value": "MiqEvent", + }, + ], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "EmsEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "managementGroupNames", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Category Managements", + "name": "managementGroupNames", + "options": Array [ + Object { + "label": "Other", + "value": "Other", + }, + Object { + "label": "Creation/Addition", + "value": "Creation/Addition", + }, + Object { + "label": "Configuration/Reconfiguration", + "value": "Configuration/Reconfiguration", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "EmsEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "managementGroupLevels", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Levels Management", + "name": "managementGroupLevels", + "options": Array [ + Object { + "label": "Critical", + "value": "critical", + }, + Object { + "label": "Detail", + "value": "detail", + }, + Object { + "label": "Warning", + "value": "warning", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "MiqEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "policyGroupNames", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Category Policy", + "name": "policyGroupNames", + "options": Array [ + Object { + "label": "Other", + "value": "other", + }, + Object { + "label": "Provider Operation", + "value": "ems_operations", + }, + Object { + "label": "Host Operation", + "value": "host_operations", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "radio", + "condition": Object { + "and": Array [ + Object { + "is": "MiqEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "policyGroupLevels", + "isRequired": true, + "label": "Event Result", + "name": "policyGroupLevels", + "options": Array [ + Object { + "label": "Success", + "value": "success", + }, + Object { + "label": "Failure", + "value": "failure", + }, + Object { + "label": "Both", + "value": "both", + }, + ], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "options", + "name": "options", + "title": "Options", + }, + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "date-picker", + "id": "startDate", + "isRequired": true, + "label": "Start Date", + "name": "startDate", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "date-picker", + "id": "endDate", + "isRequired": true, + "label": "End Date", + "name": "endDate", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "datesRange", + "name": "datesRange", + "title": "Dates Range", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + > + , + , + , + ] + } + formWrapperProps={ + Object { + "className": "form-react", + } + } + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "select", + "id": "timelineEvents", + "includeEmpty": true, + "isRequired": true, + "label": "Event Types", + "name": "timelineEvents", + "options": Array [ + Object { + "label": "Management Events", + "value": "EmsEvent", + }, + Object { + "label": "Policy Events", + "value": "MiqEvent", + }, + ], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "EmsEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "managementGroupNames", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Category Managements", + "name": "managementGroupNames", + "options": Array [ + Object { + "label": "Other", + "value": "Other", + }, + Object { + "label": "Creation/Addition", + "value": "Creation/Addition", + }, + Object { + "label": "Configuration/Reconfiguration", + "value": "Configuration/Reconfiguration", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "EmsEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "managementGroupLevels", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Levels Management", + "name": "managementGroupLevels", + "options": Array [ + Object { + "label": "Critical", + "value": "critical", + }, + Object { + "label": "Detail", + "value": "detail", + }, + Object { + "label": "Warning", + "value": "warning", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "MiqEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "policyGroupNames", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Category Policy", + "name": "policyGroupNames", + "options": Array [ + Object { + "label": "Other", + "value": "other", + }, + Object { + "label": "Provider Operation", + "value": "ems_operations", + }, + Object { + "label": "Host Operation", + "value": "host_operations", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "radio", + "condition": Object { + "and": Array [ + Object { + "is": "MiqEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "policyGroupLevels", + "isRequired": true, + "label": "Event Result", + "name": "policyGroupLevels", + "options": Array [ + Object { + "label": "Success", + "value": "success", + }, + Object { + "label": "Failure", + "value": "failure", + }, + Object { + "label": "Both", + "value": "both", + }, + ], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "options", + "name": "options", + "title": "Options", + }, + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "date-picker", + "id": "startDate", + "isRequired": true, + "label": "Start Date", + "name": "startDate", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "date-picker", + "id": "endDate", + "isRequired": true, + "label": "End Date", + "name": "endDate", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "datesRange", + "name": "datesRange", + "title": "Dates Range", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + showFormControls={true} + submitLabel="Save" + > + , + , + , + ] + } + formWrapperProps={ + Object { + "className": "form-react", + } + } + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "select", + "id": "timelineEvents", + "includeEmpty": true, + "isRequired": true, + "label": "Event Types", + "name": "timelineEvents", + "options": Array [ + Object { + "label": "Management Events", + "value": "EmsEvent", + }, + Object { + "label": "Policy Events", + "value": "MiqEvent", + }, + ], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "EmsEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "managementGroupNames", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Category Managements", + "name": "managementGroupNames", + "options": Array [ + Object { + "label": "Other", + "value": "Other", + }, + Object { + "label": "Creation/Addition", + "value": "Creation/Addition", + }, + Object { + "label": "Configuration/Reconfiguration", + "value": "Configuration/Reconfiguration", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "EmsEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "managementGroupLevels", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Levels Management", + "name": "managementGroupLevels", + "options": Array [ + Object { + "label": "Critical", + "value": "critical", + }, + Object { + "label": "Detail", + "value": "detail", + }, + Object { + "label": "Warning", + "value": "warning", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "MiqEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "policyGroupNames", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Category Policy", + "name": "policyGroupNames", + "options": Array [ + Object { + "label": "Other", + "value": "other", + }, + Object { + "label": "Provider Operation", + "value": "ems_operations", + }, + Object { + "label": "Host Operation", + "value": "host_operations", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "radio", + "condition": Object { + "and": Array [ + Object { + "is": "MiqEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "policyGroupLevels", + "isRequired": true, + "label": "Event Result", + "name": "policyGroupLevels", + "options": Array [ + Object { + "label": "Success", + "value": "success", + }, + Object { + "label": "Failure", + "value": "failure", + }, + Object { + "label": "Both", + "value": "both", + }, + ], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "options", + "name": "options", + "title": "Options", + }, + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "date-picker", + "id": "startDate", + "isRequired": true, + "label": "Start Date", + "name": "startDate", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "date-picker", + "id": "endDate", + "isRequired": true, + "label": "End Date", + "name": "endDate", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "datesRange", + "name": "datesRange", + "title": "Dates Range", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + showFormControls={true} + submitLabel="Save" + > +
+ + + + + + + +
+
+

+ Options +

+
+ + + + + + Event Types + + } + loadOptionsChangeCounter={1} + loadingMessage="Loading..." + name="timelineEvents" + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + options={ + Array [ + Object { + "label": "", + "value": undefined, + }, + Object { + "label": "Management Events", + "value": "EmsEvent", + }, + Object { + "label": "Policy Events", + "value": "MiqEvent", + }, + ] + } + placeholder="" + pluckSingleValue={true} + simpleValue={false} + value="" + > + + Event Types + + } + name="timelineEvents" + noOptionsMessage={[Function]} + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + onInputChange={[Function]} + options={ + Array [ + Object { + "label": "", + "value": undefined, + }, + Object { + "label": "Management Events", + "value": "EmsEvent", + }, + Object { + "label": "Policy Events", + "value": "MiqEvent", + }, + ] + } + placeholder="" + value="" + > + + + + + + + + + + + + +
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ Dates Range +

+
+ + + + +
+ +
+
+ + Start Date + + } + onChange={[Function]} + onClick={[Function]} + pattern="\\\\d{1,2}\\\\/\\\\d{1,2}\\\\/\\\\d{4}" + type="text" + > +
+ +
+ + +
+
+
+
+
+
+ +
+
+
+
+
+ + + + +
+ +
+
+ + End Date + + } + onChange={[Function]} + onClick={[Function]} + pattern="\\\\d{1,2}\\\\/\\\\d{1,2}\\\\/\\\\d{4}" + type="text" + > +
+ +
+ + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + , + , + , + ] + } + formSpyProps={ + Object { + "active": undefined, + "dirty": false, + "dirtyFields": Object {}, + "dirtyFieldsSinceLastSubmit": Object {}, + "dirtySinceLastSubmit": false, + "error": undefined, + "errors": Object { + "endDate": "Required", + "startDate": "Required", + "timelineEvents": "Required", + }, + "form": Object { + "batch": [Function], + "blur": [Function], + "change": [Function], + "destroyOnUnregister": false, + "focus": [Function], + "getFieldState": [Function], + "getRegisteredFields": [Function], + "getState": [Function], + "initialize": [Function], + "isValidationPaused": [Function], + "mutators": Object { + "concat": [Function], + "insert": [Function], + "move": [Function], + "pop": [Function], + "push": [Function], + "remove": [Function], + "removeBatch": [Function], + "shift": [Function], + "swap": [Function], + "unshift": [Function], + "update": [Function], + }, + "pauseValidation": [Function], + "registerField": [Function], + "reset": [Function], + "resetFieldState": [Function], + "restart": [Function], + "resumeValidation": [Function], + "setConfig": [Function], + "submit": [Function], + "subscribe": [Function], + }, + "hasSubmitErrors": false, + "hasValidationErrors": true, + "initialValues": Object {}, + "invalid": true, + "modified": Object { + "endDate": false, + "startDate": false, + "timelineEvents": false, + }, + "modifiedSinceLastSubmit": false, + "pristine": true, + "submitError": undefined, + "submitErrors": undefined, + "submitFailed": false, + "submitSucceeded": false, + "submitting": false, + "touched": Object { + "endDate": false, + "startDate": false, + "timelineEvents": false, + }, + "valid": false, + "validating": false, + "values": Object {}, + "visited": Object { + "endDate": false, + "startDate": false, + "timelineEvents": false, + }, + } + } + onReset={[Function]} + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "select", + "id": "timelineEvents", + "includeEmpty": true, + "isRequired": true, + "label": "Event Types", + "name": "timelineEvents", + "options": Array [ + Object { + "label": "Management Events", + "value": "EmsEvent", + }, + Object { + "label": "Policy Events", + "value": "MiqEvent", + }, + ], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "EmsEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "managementGroupNames", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Category Managements", + "name": "managementGroupNames", + "options": Array [ + Object { + "label": "Other", + "value": "Other", + }, + Object { + "label": "Creation/Addition", + "value": "Creation/Addition", + }, + Object { + "label": "Configuration/Reconfiguration", + "value": "Configuration/Reconfiguration", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "EmsEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "managementGroupLevels", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Levels Management", + "name": "managementGroupLevels", + "options": Array [ + Object { + "label": "Critical", + "value": "critical", + }, + Object { + "label": "Detail", + "value": "detail", + }, + Object { + "label": "Warning", + "value": "warning", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "select", + "condition": Object { + "and": Array [ + Object { + "is": "MiqEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "policyGroupNames", + "isClearable": true, + "isMulti": true, + "isRequired": true, + "isSearchable": true, + "label": "Category Policy", + "name": "policyGroupNames", + "options": Array [ + Object { + "label": "Other", + "value": "other", + }, + Object { + "label": "Provider Operation", + "value": "ems_operations", + }, + Object { + "label": "Host Operation", + "value": "host_operations", + }, + ], + "simpleValue": true, + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "radio", + "condition": Object { + "and": Array [ + Object { + "is": "MiqEvent", + "when": "timelineEvents", + }, + ], + }, + "id": "policyGroupLevels", + "isRequired": true, + "label": "Event Result", + "name": "policyGroupLevels", + "options": Array [ + Object { + "label": "Success", + "value": "success", + }, + Object { + "label": "Failure", + "value": "failure", + }, + Object { + "label": "Both", + "value": "both", + }, + ], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "options", + "name": "options", + "title": "Options", + }, + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "date-picker", + "id": "startDate", + "isRequired": true, + "label": "Start Date", + "name": "startDate", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "date-picker", + "id": "endDate", + "isRequired": true, + "label": "End Date", + "name": "endDate", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "datesRange", + "name": "datesRange", + "title": "Dates Range", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + submitLabel="Save" + > + + +
+ + + + + + +
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+`; diff --git a/app/javascript/spec/timeline-options-form/sample-data.js b/app/javascript/spec/timeline-options-form/sample-data.js new file mode 100644 index 00000000000..168ac9343d9 --- /dev/null +++ b/app/javascript/spec/timeline-options-form/sample-data.js @@ -0,0 +1,144 @@ +export const sampleReponse = { + attributes: [ + 'availability_zone_id', + 'chain_id', + 'container_group_id', + 'container_group_name', + 'container_id', + 'container_name', + 'container_namespace', + 'container_node_id', + 'container_node_name', + 'container_replicator_id', + 'container_replicator_name', + 'created_on', + 'dest_ems_cluster_id', + 'dest_ems_cluster_name', + 'dest_ems_cluster_uid', + 'dest_host_id', + 'dest_host_name', + 'dest_vm_ems_ref', + 'dest_vm_location', + 'dest_vm_name', + 'dest_vm_or_template_id', + 'ems_cluster_id', + 'ems_cluster_name', + 'ems_cluster_uid', + 'ems_id', + 'ems_ref', + 'event_type', + 'full_data', + 'generating_ems_id', + 'group_id', + 'host_id', + 'host_name', + 'id', + 'is_task', + 'message', + 'physical_chassis_id', + 'physical_server_id', + 'physical_storage_id', + 'physical_storage_name', + 'physical_switch_id', + 'source', + 'target_id', + 'target_type', + 'tenant_id', + 'timestamp', + 'type', + 'user_id', + 'username', + 'vm_ems_ref', + 'vm_location', + 'vm_name', + 'vm_or_template_id', + ], + virtual_attributes: [ + 'group', + 'group_level', + 'group_name', + 'href_slug', + 'region_description', + 'region_number', + ], + relationships: [ + 'availability_zone', + 'container_group', + 'container_node', + 'container_replicator', + 'dest_host', + 'dest_miq_template', + 'dest_vm', + 'dest_vm_or_template', + 'ext_management_system', + 'generating_ems', + 'host', + 'miq_template', + 'physical_chassis', + 'physical_server', + 'physical_storage', + 'physical_switch', + 'service', + 'target', + 'vm', + 'vm_or_template', + ], + subcollections: [], + data: { + timeline_events: { + EmsEvent: { + description: 'Management Events', + group_names: { + other: 'Other', + addition: 'Creation/Addition', + configuration: 'Configuration/Reconfiguration', + }, + group_levels: { + critical: 'Critical', + detail: 'Detail', + warning: 'Warning', + }, + }, + MiqEvent: { + description: 'Policy Events', + group_names: { + other: 'Other', + ems_operations: 'Provider Operation', + host_operations: 'Host Operation', + }, + group_levels: { + success: 'Success', + failure: 'Failure', + detail: 'Detail', + }, + }, + }, + }, +}; + +export const sampleSubmitPressedValues = { + timeline_events: 'EmsEvent', + managementGroupNames: [ + 'Configuration/Reconfiguration', + ], + managementGroupLevels: [ + 'critical', + ], + startDate: [ + '2022-09-07T04:00:00.000Z', + ], + endDate: [ + '2022-09-28T04:00:00.000Z', + ], +}; + +export const sampleVmData = { + tl_show: 'timeline', + tl_categories: [ + 'Configuration/Reconfiguration', + ], + tl_levels: [ + 'critical', + ], + tl_result: 'success', +}; diff --git a/app/javascript/spec/timeline-options-form/timeline-options-form.spec.js b/app/javascript/spec/timeline-options-form/timeline-options-form.spec.js new file mode 100644 index 00000000000..46ef9339baf --- /dev/null +++ b/app/javascript/spec/timeline-options-form/timeline-options-form.spec.js @@ -0,0 +1,59 @@ +import React from 'react'; +import toJson from 'enzyme-to-json'; +import fetchMock from 'fetch-mock'; +import { act } from 'react-dom/test-utils'; +import TimelineOptions from '../../components/timeline-options/timeline-options'; +import { sampleReponse, sampleSubmitPressedValues, sampleVmData } from './sample-data'; +import { mount, shallow } from '../helpers/mountForm'; +import { Button } from 'carbon-components-react'; +import '../../oldjs/miq_application' // for miqJqueryRequest + +describe('Show Timeline Options form component', () => { + let submitSpy; + + beforeEach(() => { + fetchMock + .once('/api/event_streams', sampleReponse); + submitSpy = jest.spyOn(window, 'miqJqueryRequest'); + }); + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + submitSpy.mockRestore(); + }); + + /* + * Render Form + */ + + it('should render form', async(done) => { + let wrapper; + await act(async() => { + wrapper = mount(); + }); + setImmediate(() => { + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot(); + done(); + }); + }); + + /* + * Submit Logic + */ + + it('should not submit values when form is empty', async(done) => { + let wrapper; + await act(async() => { + wrapper = mount(); + }); + setImmediate(() => { + wrapper.update(); + expect(wrapper.find(Button)).toHaveLength(2); + wrapper.find(Button).first().simulate('click'); + expect(submitSpy).toHaveBeenCalledTimes(0); + done(); + }); + }); +}); diff --git a/app/views/layouts/_tl_options.html.haml b/app/views/layouts/_tl_options.html.haml deleted file mode 100644 index 776942861d1..00000000000 --- a/app/views/layouts/_tl_options.html.haml +++ /dev/null @@ -1,110 +0,0 @@ -- url = url_for_only_path(:action => 'tl_chooser', :id => @record.id) - -#tl_options_div - %form#form_div{:name => 'angularForm', - 'ng-controller' => 'timelineOptionsController as vm', - 'ng-show' => 'vm.afterGet', - 'miq-form' => true, - 'model' => 'vm.reportModel', - 'model-copy' => 'vm.modelCopy', - :novalidate => true} - - if @timeline - %h3 - = _("Options") - .toolbar-pf - .row - .col-sm-12.toolbar-pf-actions.timeline-toolbar - .form-group - %div{'class' => 'timeline-filterbar'} - = select_tag("tl_show", - options_for_select(accessible_select_event_types, nil), - 'ng-model' => 'vm.reportModel.tl_show', - "selectpicker-for-select-tag" => '', - :class => 'selectpicker', - 'ng-change' => 'vm.eventTypeUpdated()') - %span{'ng-if' => 'vm.reportModel.tl_show === "timeline"'} - = select_tag("tl_category_management", - options_for_select(@tl_options.management.events.keys.sort, nil), - 'ng-model' => 'vm.reportModel.tl_categories', - 'ng-change' => 'vm.clearLevelsIfNotSelected()', - 'title' => _('No category selected'), - "selectpicker-for-select-tag" => '', - 'data-live-search' => 'true', - 'data-actions-box' => 'true', - 'data-select-all-text' => _('Select All'), - 'data-deselect-all-text' => _('Deselect All'), - 'multiple' => '', - :class => 'selectpicker category-selector') - %span{'ng-if' => 'vm.reportModel.tl_show === "timeline"'} - = select_tag("tl_levels_management", - options_for_select(@tl_options.management.levels_text_and_value.sort, nil), - 'ng-model' => 'vm.reportModel.tl_levels', - 'title' => _('No severity selected'), - 'ng-disabled' => 'vm.reportModel.tl_categories.length === 0', - "selectpicker-for-select-tag" => '', - 'data-live-search' => 'true', - 'data-actions-box' => 'true', - 'data-select-all-text' => _('Select All'), - 'data-deselect-all-text' => _('Deselect All'), - 'multiple' => '', - :class => 'selectpicker category-selector') - %span{'ng-if' => 'vm.reportModel.tl_show !== "timeline"'} - = select_tag("tl_category_policy", - options_for_select(@tl_options.policy.events.keys.sort, nil), - 'ng-model' => 'vm.reportModel.tl_categories', - "selectpicker-for-select-tag" => '', - 'data-live-search' => 'true', - 'data-actions-box' => 'true', - 'data-select-all-text' => _('Select All'), - 'data-deselect-all-text' => _('Deselect All'), - 'multiple' => '', - :class => 'selectpicker category-selector') - %span{'ng-if' => 'vm.reportModel.tl_show !== "timeline"', 'class' => 'timeline-option'} - %label.radio - %input{"ng-model" => "vm.reportModel.tl_result", :type => "radio", :value => "success"} - = _("Successful Events") - %label.radio - %input{"ng-model" => "vm.reportModel.tl_result", :type => "radio", :value => "failure"} - = _("Failed Events") - %label.radio - %input{"ng-model" => "vm.reportModel.tl_result", :type => "radio", :value => "both"} - = _("Both") - %div{'class' => 'timeline-date-box'} - .input-group.bootstrap-touchspin.timeline-stepper.pull-left - %span.input-group-btn - %button.btn.btn-default.bootstrap-touchspin-down{"ng-click" => "vm.countDecrement()", :type => "button"} - - %input.timeline-range-input.bootstrap-touchspin.form-control{"ng-model" => "vm.reportModel.tl_range_count", - :readonly => "readonly", - :type => "text"} - %span.input-group-btn - %button.btn.btn-default.bootstrap-touchspin-up{"ng-click" => "vm.countIncrement()", :type => "button"} + - = select_tag("tl_range", - options_for_select([[_("Days"), "days"], - [_("Weeks"), "weeks"], - [_("Months"), "months"]], nil), - 'ng-model' => 'vm.reportModel.tl_timerange', - "selectpicker-for-select-tag" => '', - :class => 'selectpicker pull-left') - = select_tag("tl_timepivot", - options_for_select([[_("centered"), "centered"], - [_("starting"), "starting"], - [_("ending"), "ending"]], nil), - 'ng-model' => 'vm.reportModel.tl_timepivot', - "selectpicker-for-select-tag" => '', - :class => 'selectpicker pull-left timeline-pivot-input') - %input{'pf-datepicker' => '', - 'options' => 'vm.dateOptions', - 'date' => 'vm.reportModel.tl_date', - 'class' => 'pull-righto timeline-date-input'} - %div - .btn.btn-default{'ng-click' => 'vm.applyButtonClicked()', - 'ng-disabled' => 'vm.reportModel.tl_categories.length === 0', - 'class' => 'timeline-apply'} - = _("Apply") - = hidden_field_tag(:ignore_form_changes) - -:javascript - ManageIQ.angular.app.value('recordId', '#{@record.id}'); - ManageIQ.angular.app.value('url', '#{url}'); - ManageIQ.angular.app.value('categories', '#{@tl_options.management.events.keys.sort}'); - miq_bootstrap('#form_div'); diff --git a/app/views/layouts/_tl_show.html.haml b/app/views/layouts/_tl_show.html.haml index 8f9101cdc3e..bdad01f5ad7 100644 --- a/app/views/layouts/_tl_show.html.haml +++ b/app/views/layouts/_tl_show.html.haml @@ -1,12 +1,7 @@ --# Create from/to date JS vars to limit calendar selection range -:javascript - ManageIQ.calendar.calDateFrom = new Date(#{@tl_options.date.start}); - ManageIQ.calendar.calDateTo = new Date(#{@tl_options.date.end}); - %h1 = @title = render :partial => "layouts/flash_msg" -= render :partial => "layouts/tl_options" += react 'TimelineOptions', {:url => url_for_only_path(:action => 'tl_chooser', :id => @record.id)} = render :partial => 'layouts/tl_detail' = _("* Dates/Times on this page are based on time zone: %{time_zone}.") % {:time_zone => session[:user_tz]} diff --git a/spec/javascripts/controllers/timeline/timeline_options_controller_spec.js b/spec/javascripts/controllers/timeline/timeline_options_controller_spec.js deleted file mode 100644 index 68ec56d3533..00000000000 --- a/spec/javascripts/controllers/timeline/timeline_options_controller_spec.js +++ /dev/null @@ -1,77 +0,0 @@ -describe('timelineOptionsController', function() { - var $scope, $controller, $httpBackend, miqService; - - beforeEach(module('ManageIQ')); - - beforeEach(inject(function(_$httpBackend_, $rootScope, _$controller_, _miqService_) { - miqService = _miqService_; - spyOn(miqService, 'showButtons'); - spyOn(miqService, 'hideButtons'); - spyOn(miqService, 'buildCalendar'); - spyOn(miqService, 'miqAjaxButton'); - spyOn(miqService, 'sparkleOn'); - spyOn(miqService, 'sparkleOff'); - $scope = $rootScope.$new(); - spyOn($scope, '$broadcast'); - $httpBackend = _$httpBackend_; - $controller = _$controller_('timelineOptionsController', { - $scope: $scope, - keyPairFormId: 'new', - url: '/host/tl_chooser', - categories: [], - miqService: miqService - }); - })); - - afterEach(function() { - $httpBackend.verifyNoOutstandingExpectation(); - $httpBackend.verifyNoOutstandingRequest(); - }); - - describe('count increment', function() { - it('should increment the count', function() { - $controller.countIncrement(); - expect($controller.reportModel.tl_range_count).toBe(2); - }); - - it('should decrement the count', function() { - $controller.reportModel.tl_range_count = 10; - $controller.countDecrement(); - expect($controller.reportModel.tl_range_count).toBe(9); - }); - }); - - describe('options update', function() { - it('should update the type to be hourly', function() { - $controller.reportModel.tl_timerange = 'days'; - $controller.applyButtonClicked(); - expect($controller.reportModel.tl_typ).toBe('Hourly'); - }); - - it('should update the type to be daily', function() { - $controller.reportModel.tl_timerange = 'weeks'; - $controller.applyButtonClicked(); - expect($controller.reportModel.tl_typ).toBe('Daily'); - }); - - it('should update the miq_date correctly', function() { - $controller.reportModel.tl_timerange = 'days'; - $controller.reportModel.tl_range_count = 10; - var timeLineDate = new Date('2016-04-01') - $controller.reportModel.tl_date = new Date(timeLineDate.getTime() + (timeLineDate.getTimezoneOffset() * 60000)); - $controller.reportModel.tl_timepivot = 'starting'; - $controller.applyButtonClicked(); - expect($controller.reportModel.miq_date).toBe('04/11/2016'); - }); - - it('should update the miq_date correctly based on centered', function() { - $controller.reportModel.tl_timerange = 'days'; - $controller.reportModel.tl_range_count = 10; - var timeLineDate = new Date('2016-04-01') - $controller.reportModel.tl_date = new Date(timeLineDate.getTime() + (timeLineDate.getTimezoneOffset() * 60000)); - $controller.reportModel.tl_timepivot = 'centered'; - $controller.applyButtonClicked(); - expect($controller.reportModel.miq_date).toBe('04/06/2016'); - }); - }); -});