From 790a89b1edfe11768f9339a39dad85439757ce38 Mon Sep 17 00:00:00 2001 From: josemigallas Date: Fri, 29 Jan 2021 16:57:29 +0100 Subject: [PATCH] adds autosubmit feature and renames component --- .../api/application_plans_controller.rb | 2 +- app/controllers/api/plans_base_controller.rb | 10 ++ app/helpers/api/application_plans_helper.rb | 21 ++-- app/javascript/packs/default_plan_selector.js | 10 +- .../components/DefaultPlanSelect.jsx | 119 ++++++++++++++++++ .../components/DefaultPlanSelect.scss | 5 + .../components/DefaultPlanSelectWrapper.jsx | 8 ++ .../components/DefaultPlanSelector.jsx | 80 ------------ .../components/DefaultPlanSelectorWrapper.jsx | 8 -- .../components/PlanSelectOptionObject.jsx | 7 +- app/javascript/src/Applications/index.js | 4 +- app/javascript/src/utilities/ajax.js | 12 ++ app/views/api/plans/_default_plan.html.slim | 3 +- .../master/providers/plans/_widget.html.slim | 2 +- .../components/DefaultPlanSelect.spec.jsx | 17 +++ 15 files changed, 203 insertions(+), 105 deletions(-) create mode 100644 app/javascript/src/Applications/components/DefaultPlanSelect.jsx create mode 100644 app/javascript/src/Applications/components/DefaultPlanSelect.scss create mode 100644 app/javascript/src/Applications/components/DefaultPlanSelectWrapper.jsx delete mode 100644 app/javascript/src/Applications/components/DefaultPlanSelector.jsx delete mode 100644 app/javascript/src/Applications/components/DefaultPlanSelectorWrapper.jsx create mode 100644 app/javascript/src/utilities/ajax.js create mode 100644 spec/javascripts/Applications/components/DefaultPlanSelect.spec.jsx diff --git a/app/controllers/api/application_plans_controller.rb b/app/controllers/api/application_plans_controller.rb index 2cf7515299..f13ea69118 100644 --- a/app/controllers/api/application_plans_controller.rb +++ b/app/controllers/api/application_plans_controller.rb @@ -34,7 +34,7 @@ def destroy end def masterize - generic_masterize_plan(@service, :default_application_plan) + new_masterize_plan(@service, :default_application_plan) end protected diff --git a/app/controllers/api/plans_base_controller.rb b/app/controllers/api/plans_base_controller.rb index 3ca23ad9fc..901dbb8503 100644 --- a/app/controllers/api/plans_base_controller.rb +++ b/app/controllers/api/plans_base_controller.rb @@ -112,6 +112,16 @@ def plans_index_path protected + def new_masterize_plan(issuer, assoc) + if @plan.nil? || issuer.send(assoc) == @plan + issuer.send("#{assoc}=", nil) + else + issuer.send("#{assoc}=", @plan) + end + + issuer.save! + end + def generic_masterize_plan(issuer, assoc) masterize_plan do if @plan.nil? || issuer.send(assoc) == @plan diff --git a/app/helpers/api/application_plans_helper.rb b/app/helpers/api/application_plans_helper.rb index e641a1729f..b6cc2c206b 100644 --- a/app/helpers/api/application_plans_helper.rb +++ b/app/helpers/api/application_plans_helper.rb @@ -2,15 +2,22 @@ module Api::ApplicationPlansHelper - def application_plans_data - @plans.not_custom - .alphabetically - .to_json(root: false, only: %i[id name]) + def default_plan_data(service, plans) + { + 'current-service': service.to_json(root: false, only: %i[id name]), + 'application-plans': application_plans_data(plans), + 'current-plan': default_application_plan_data(service) + } end - def current_plan_id_data - # -1 is the ID of the "select none" option in DefaultPlanSelector.jsx - @service.default_application_plan.try!(:id) || -1 + def application_plans_data(plans) + plans.not_custom + .alphabetically + .to_json(root: false, only: %i[id name]) + end + + def default_application_plan_data(service) + service.default_application_plan&.to_json(root: false, only: %i[id name]) || nil.to_json end end diff --git a/app/javascript/packs/default_plan_selector.js b/app/javascript/packs/default_plan_selector.js index 9c7318a10b..0cb5abd987 100644 --- a/app/javascript/packs/default_plan_selector.js +++ b/app/javascript/packs/default_plan_selector.js @@ -1,13 +1,15 @@ -import { DefaultPlanSelectorWrapper } from 'Applications' +import { DefaultPlanSelectWrapper } from 'Applications' import { safeFromJsonString } from 'utilities/json-utils' document.addEventListener('DOMContentLoaded', () => { const { dataset } = document.getElementById('default_plan') + const currentService = safeFromJsonString(dataset.currentService) const plans = safeFromJsonString(dataset.applicationPlans) - const currentPlanId = Number(dataset.currentPlanId) + const currentPlan = safeFromJsonString(dataset.currentPlan) ?? undefined - DefaultPlanSelectorWrapper({ + DefaultPlanSelectWrapper({ + currentService, plans, - currentPlanId + currentPlan }, 'default_plan') }) diff --git a/app/javascript/src/Applications/components/DefaultPlanSelect.jsx b/app/javascript/src/Applications/components/DefaultPlanSelect.jsx new file mode 100644 index 0000000000..a32908052c --- /dev/null +++ b/app/javascript/src/Applications/components/DefaultPlanSelect.jsx @@ -0,0 +1,119 @@ +// @flow + +import React, { useState, useEffect } from 'react' +import * as ajax from 'utilities/ajax' + +import { + Form, + FormGroup, + PageSection, + PageSectionVariants, + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core' +import { PlanSelectOptionObject } from 'Applications' + +import './DefaultPlanSelect.scss' + +type Service = { + id: number, + name: string, +} + +type ApplicationPlan = { + id: number, + name: string +} + +type Props = { + currentService: Service, + currentPlan?: ApplicationPlan, + plans: ApplicationPlan[] +} + +const NO_DEFAULT_PLAN: ApplicationPlan = { id: -1, name: '(No default plan)' } + +// TODO: prevent reload screen when pressing Enter + +const DefaultPlanSelect = ({ currentService, currentPlan = NO_DEFAULT_PLAN, plans }: Props) => { + const [selection, setSelection] = useState(new PlanSelectOptionObject(currentPlan)) + const [isExpanded, setIsExpanded] = useState(false) + const [isLoading, setIsLoading] = useState(false) + + const onSelect = (_e, newPlan: PlanSelectOptionObject) => { + setSelection(newPlan) + setIsExpanded(false) + setIsLoading(true) + + const body = newPlan.id >= 0 ? new URLSearchParams({ id: newPlan.id.toString() }) : undefined + + // making request... + ajax.post(`/apiconfig/services/${currentService.id}/application_plans/masterize`, body) + .then(data => { + console.log(data) + if (data.ok) { + $.flash('Default plan was updated') + } else { + $.flash.error('Plan could not be updated') + } + }) + .catch(err => { + console.error(err) + $.flash.error('An error ocurred. Please try again later.') + }) + .finally(() => setIsLoading(false)) + } + + const onClear = () => { + setSelection(null) + } + + useEffect(() => { + console.log(setIsLoading) + // console.log('Setting new default: ' + String(selection)) + // setIsLoading(true) + + // setTimeout(() => { + // setIsLoading(false) + // }, 500) + }, [selection]) + + const options = [NO_DEFAULT_PLAN, ...plans] + .filter(p => p.id !== currentPlan.id) // Don't show the current default plan + .map(p => new PlanSelectOptionObject(p)) + + return ( + +
e.preventDefault()}> + + + +
+
+ ) +} + +export { DefaultPlanSelect } diff --git a/app/javascript/src/Applications/components/DefaultPlanSelect.scss b/app/javascript/src/Applications/components/DefaultPlanSelect.scss new file mode 100644 index 0000000000..c21f0c7e9b --- /dev/null +++ b/app/javascript/src/Applications/components/DefaultPlanSelect.scss @@ -0,0 +1,5 @@ +#default-plan-selector { + .pf-c-select__toggle.pf-m-disabled input { + background-color: var(--pf-global--disabled-color--300); + } +} diff --git a/app/javascript/src/Applications/components/DefaultPlanSelectWrapper.jsx b/app/javascript/src/Applications/components/DefaultPlanSelectWrapper.jsx new file mode 100644 index 0000000000..d9e143db80 --- /dev/null +++ b/app/javascript/src/Applications/components/DefaultPlanSelectWrapper.jsx @@ -0,0 +1,8 @@ +import React from 'react' + +import { DefaultPlanSelect } from 'Applications' +import { createReactWrapper } from 'utilities/createReactWrapper' + +const DefaultPlanSelectWrapper = (props, containerId) => createReactWrapper(, containerId) + +export { DefaultPlanSelectWrapper } diff --git a/app/javascript/src/Applications/components/DefaultPlanSelector.jsx b/app/javascript/src/Applications/components/DefaultPlanSelector.jsx deleted file mode 100644 index b7422acc28..0000000000 --- a/app/javascript/src/Applications/components/DefaultPlanSelector.jsx +++ /dev/null @@ -1,80 +0,0 @@ -// @flow - -import React, { useState, useEffect } from 'react' - -import { - Form, - FormGroup, - PageSection, - PageSectionVariants, - Select, - SelectOption, - SelectVariant -} from '@patternfly/react-core' -import { PlanSelectOptionObject } from 'Applications' - -type Props = { - currentPlanId: number, - plans: Array<{ - id: number, - name: string - }> -} - -const DefaultPlanSelector = ({ currentPlanId, plans }: Props) => { - const [selection, setSelection] = useState(() => { - if (currentPlanId) { - const plan = plans.find(p => p.id === currentPlanId) - return plan ? new PlanSelectOptionObject(plan) : undefined - } - }) - const [isExpanded, setIsExpanded] = useState(false) - const [isLoading, setIsLoading] = useState(false) - - const onSelect = (_e, option: PlanSelectOptionObject) => { - setSelection(option) - setIsExpanded(false) - } - - const onClear = () => { - setSelection(undefined) - // TODO: focus input - } - - useEffect(() => { - console.log(selection) - // TODO: When plan changes, send a post request - console.log(setIsLoading) - }, [selection]) - - const options = [{ id: -1, name: '(Select none)' }, ...plans].map(p => new PlanSelectOptionObject(p)) - - return ( - -
- - - -
-
- ) -} - -export { DefaultPlanSelector } diff --git a/app/javascript/src/Applications/components/DefaultPlanSelectorWrapper.jsx b/app/javascript/src/Applications/components/DefaultPlanSelectorWrapper.jsx deleted file mode 100644 index 1ef2bd5a94..0000000000 --- a/app/javascript/src/Applications/components/DefaultPlanSelectorWrapper.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' - -import { DefaultPlanSelector } from 'Applications' -import { createReactWrapper } from 'utilities/createReactWrapper' - -const DefaultPlanSelectorWrapper = (props, containerId) => createReactWrapper(, containerId) - -export { DefaultPlanSelectorWrapper } diff --git a/app/javascript/src/Applications/components/PlanSelectOptionObject.jsx b/app/javascript/src/Applications/components/PlanSelectOptionObject.jsx index 1751e21dbd..2766f65bef 100644 --- a/app/javascript/src/Applications/components/PlanSelectOptionObject.jsx +++ b/app/javascript/src/Applications/components/PlanSelectOptionObject.jsx @@ -1,11 +1,16 @@ +// @flow + import { SelectOptionObject } from '@patternfly/react-core' type Plan = { - id: string, + id: number, name: string } class PlanSelectOptionObject implements SelectOptionObject { + id: number + name: string + constructor (plan: Plan) { this.id = plan.id this.name = plan.name diff --git a/app/javascript/src/Applications/index.js b/app/javascript/src/Applications/index.js index 00ccc54f5e..1cb5c4026b 100644 --- a/app/javascript/src/Applications/index.js +++ b/app/javascript/src/Applications/index.js @@ -1,5 +1,5 @@ // @flow export * from 'Applications/components/PlanSelectOptionObject' -export * from 'Applications/components/DefaultPlanSelector' -export * from 'Applications/components/DefaultPlanSelectorWrapper' +export * from 'Applications/components/DefaultPlanSelect' +export * from 'Applications/components/DefaultPlanSelectWrapper' diff --git a/app/javascript/src/utilities/ajax.js b/app/javascript/src/utilities/ajax.js new file mode 100644 index 0000000000..dd81667ee1 --- /dev/null +++ b/app/javascript/src/utilities/ajax.js @@ -0,0 +1,12 @@ +// @flow + +const post = (url: string, body?: URLSearchParams) => fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content + }, + body +}) + +export { post } diff --git a/app/views/api/plans/_default_plan.html.slim b/app/views/api/plans/_default_plan.html.slim index 35a79d43ef..02442d0ba9 100644 --- a/app/views/api/plans/_default_plan.html.slim +++ b/app/views/api/plans/_default_plan.html.slim @@ -1,2 +1,3 @@ -div id='default_plan' data-application-plans=(application_plans_data()) data-current-plan-id=(current_plan_id_data()) +h1 New +div id='default_plan' data=default_plan_data(@service, @plans) = javascript_pack_tag 'default_plan_selector' diff --git a/app/views/master/providers/plans/_widget.html.slim b/app/views/master/providers/plans/_widget.html.slim index 93afef58ce..90bca330c6 100644 --- a/app/views/master/providers/plans/_widget.html.slim +++ b/app/views/master/providers/plans/_widget.html.slim @@ -20,7 +20,7 @@ div.dashboard_card.round#provider-change-plan br = form_tag edit_master_provider_plan_path(provider), :method => :get, :remote => true, :class => 'formtastic colorbox' do fieldset.inputs - legend Change Plan + legend Change Plan PEPE ol li = select_tag :plan_id, options_from_collection_for_select(plans, :id, :name), required: true, include_blank: true diff --git a/spec/javascripts/Applications/components/DefaultPlanSelect.spec.jsx b/spec/javascripts/Applications/components/DefaultPlanSelect.spec.jsx new file mode 100644 index 0000000000..aa377b821f --- /dev/null +++ b/spec/javascripts/Applications/components/DefaultPlanSelect.spec.jsx @@ -0,0 +1,17 @@ +// @flow + +import React from 'react' + +import { DefaultPlanSelect } from 'Applications' +import { mount } from 'enzyme' + +const props = { + currentService: { id: 0, name: 'Le Service' }, + currentPlan: undefined, + plans: [] +} + +it('should render', () => { + const wrapper = mount() + expect(wrapper.exists()).toBe(true) +})