From d7fb784a0a8919aa6e5934599c1cb335df0f162a Mon Sep 17 00:00:00 2001 From: Sylvester Chin Date: Thu, 28 Mar 2019 16:27:09 +0800 Subject: [PATCH] Subpolicy list (#15) * Create endpoint for retrieving subpolicy list * Move all string constants into a separate file * Implement dropdown menu for selecting subpolicies * Combine defaults fetching endpoint into one * Create dropdown menu for selection of ensembler * Use URL to form addresses * Convert axios calls to use async/await * Settle rebase issues --- app/endpoints.go | 9 +++++ app/router.go | 1 + policy/ensemble.go | 6 ++-- policy/ensemble_test.go | 10 +++--- policy/subpolicy/subpolicy.go | 9 ++--- types/constants.go | 13 ++++++++ ui/src/App.jsx | 35 ++++++++++++-------- ui/src/actions/index.js | 7 ++++ ui/src/components/PolicySummary.jsx | 11 ++++-- ui/src/components/StatusSwitch.jsx | 12 +++++-- ui/src/components/Subpolicy.jsx | 13 ++++++-- ui/src/containers/App.js | 4 +++ ui/src/containers/PolicySummary.js | 3 +- ui/src/containers/Subpolicy.js | 3 +- ui/src/reducers/index.js | 23 ++++++++++--- ui/tests/actions/actions.test.js | 5 +-- ui/tests/components/ManagedResources.test.js | 7 ---- ui/tests/components/NomadParameters.test.js | 2 +- ui/tests/reducers/reducers.test.js | 7 +--- 19 files changed, 126 insertions(+), 54 deletions(-) create mode 100644 types/constants.go diff --git a/app/endpoints.go b/app/endpoints.go index f74d2ba..441c981 100644 --- a/app/endpoints.go +++ b/app/endpoints.go @@ -4,6 +4,7 @@ import ( "github.com/datagovsg/nomad-parametric-autoscaler/logging" "github.com/datagovsg/nomad-parametric-autoscaler/policy" "github.com/datagovsg/nomad-parametric-autoscaler/resources" + "github.com/datagovsg/nomad-parametric-autoscaler/types" "github.com/gin-gonic/gin" ) @@ -24,6 +25,14 @@ func (ep *endpoints) GetResourceStatus(c *gin.Context) { c.JSON(200, ep.wp.policy.GetResourceStatus()) } +// GetSubpolicyList returns list of possible subpolicies +func (ep *endpoints) GetPredefinedFeatures(c *gin.Context) { + c.JSON(200, gin.H{ + "subpolicies": types.SubpolicyList, + "ensemblers": types.EnsemblerList, + }) +} + // UpdatePolicy parses body and builds a new policy to replace // existing policy // returns 200 if policy is well-formed and replaces existing successfully diff --git a/app/router.go b/app/router.go index 1773405..c04ae32 100644 --- a/app/router.go +++ b/app/router.go @@ -32,6 +32,7 @@ func NewRouter(ep *endpoints) *gin.Engine { // Core endpoints router.GET("/state", ep.GetPolicy) router.GET("/status", ep.GetResourceStatus) + router.GET("/predefined", ep.GetPredefinedFeatures) router.POST("/update", ep.UpdatePolicy) // Helper endpoints diff --git a/policy/ensemble.go b/policy/ensemble.go index 7cb0091..266130c 100644 --- a/policy/ensemble.go +++ b/policy/ensemble.go @@ -2,6 +2,8 @@ package policy import ( "fmt" + + "github.com/datagovsg/nomad-parametric-autoscaler/types" ) /* @@ -18,11 +20,11 @@ type Ensembler interface { // GetEnsembler matches name and returns an ensembler func GetEnsembler(name string) (Ensembler, error) { switch name { - case "Conservative": + case types.EnsembleConservative: return ConservativeEnsembling{ name: name, }, nil - case "Average": + case types.EnsembleAverage: return AverageEnsembling{ name: name, }, nil diff --git a/policy/ensemble_test.go b/policy/ensemble_test.go index 611c69c..65d6ef0 100644 --- a/policy/ensemble_test.go +++ b/policy/ensemble_test.go @@ -2,26 +2,28 @@ package policy import ( "testing" + + "github.com/datagovsg/nomad-parametric-autoscaler/types" ) func TestEnsemblerFetching(t *testing.T) { - es, err := GetEnsembler("Conservative") + es, err := GetEnsembler(types.EnsembleConservative) if err != nil { t.Errorf("Expected keyword `Conservative` to be valid") } - if es != (ConservativeEnsembling{name: "Conservative"}) { + if es != (ConservativeEnsembling{name: types.EnsembleConservative}) { t.Errorf("ConservativeEnsembling struct to be fetched") } - es, err = GetEnsembler("Average") + es, err = GetEnsembler(types.EnsembleAverage) if err != nil { t.Errorf("Expected keyword `Average` to be valid") } - if es != (AverageEnsembling{name: "Average"}) { + if es != (AverageEnsembling{name: types.EnsembleAverage}) { t.Errorf("AverageEnsembling struct to be fetched") } diff --git a/policy/subpolicy/subpolicy.go b/policy/subpolicy/subpolicy.go index 1e2b725..d5800c2 100644 --- a/policy/subpolicy/subpolicy.go +++ b/policy/subpolicy/subpolicy.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/datagovsg/nomad-parametric-autoscaler/resources" + "github.com/datagovsg/nomad-parametric-autoscaler/types" ) // ScalingMagnitude needs a way better name @@ -29,13 +30,13 @@ type GenericSubPolicy struct { // CreateSpecificSubpolicy checks name of GSP and creates the actual policy func CreateSpecificSubpolicy(gsp GenericSubPolicy, mr []resources.Resource) (SubPolicy, error) { switch gsp.Name { - case "CoreRatio": + case types.CoreRatio: sp, err := NewCoreRatioSubpolicy(gsp.Name, mr, gsp.Metadata) if err != nil { return nil, err } return sp, nil - case "OfficeHour": + case types.OfficeHour: sp, err := NewOfficeHourSubPolicy(gsp.Name, mr, gsp.Metadata) if err != nil { return nil, err @@ -50,9 +51,9 @@ func CreateSpecificSubpolicy(gsp GenericSubPolicy, mr []resources.Resource) (Sub // of scaling methods func determineNewDesiredLevel(cur int, sm ScalingMagnitude) int { switch sm.ChangeType { - case "multiply": + case types.ChangeTypeMultiply: return int(float64(cur) * sm.ChangeValue) - case "until": + case types.ChangeTypeUntil: return int(sm.ChangeValue) default: return cur diff --git a/types/constants.go b/types/constants.go new file mode 100644 index 0000000..7c0bd53 --- /dev/null +++ b/types/constants.go @@ -0,0 +1,13 @@ +package types + +const CoreRatio = "CoreRatio" +const OfficeHour = "OfficeHour" + +const ChangeTypeMultiply = "multiply" +const ChangeTypeUntil = "until" + +const EnsembleConservative = "Conservative" +const EnsembleAverage = "Average" + +var SubpolicyList = []string{CoreRatio, OfficeHour} +var EnsemblerList = []string{EnsembleConservative, EnsembleAverage} diff --git a/ui/src/App.jsx b/ui/src/App.jsx index 0ceab7c..9f06449 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -24,21 +24,30 @@ class App extends Component { this.refreshState(); } - refreshState() { - const reqUrl = new URL( + async refreshState() { + const predefinedUrl = new URL( + "/predefined", + window.config.env.REACT_APP_NOPAS_ENDPOINT + ); + const stateUrl = new URL( "/state", window.config.env.REACT_APP_NOPAS_ENDPOINT ); - axios - .get(reqUrl) - .then(response => { - const newState = serverToUIConversion(response.data); - newState && this.props.refreshState(newState); - }) - .catch(function(error) { - alert(error); - }); + let firstResponse = await axios.get(predefinedUrl); + if (firstResponse.err) { + alert(firstResponse.err); + return; + } + + this.props.updatePossibleDefaultsList(firstResponse.data); + let secondResponse = await axios.get(stateUrl); + if (secondResponse.err) { + alert(secondResponse.err); + } else { + const newState = serverToUIConversion(secondResponse.data); + this.props.refreshState(newState); + } } sendUpdate() { @@ -75,8 +84,8 @@ class App extends Component { } App.propTypes = { - refreshState: PropTypes.func, - state: PropTypes.object + refreshState: PropTypes.func.isRequired, + state: PropTypes.object.isRequired }; export default App; diff --git a/ui/src/actions/index.js b/ui/src/actions/index.js index dea2313..2a309b2 100644 --- a/ui/src/actions/index.js +++ b/ui/src/actions/index.js @@ -21,6 +21,8 @@ export const UPDATE_SUBPOLICY_RESOURCE = "UPDATE_SUBPOLICY_RESOURCE"; export const UPDATE_SP_META = "UPDATE_SP_META"; export const DELETE_SUBPOLICY = "DELETE_SUBPOLICY"; +export const UPDATE_POSSIBLE_DEFAULTS_LIST = "UPDATE_POSSIBLE_DEFAULTS_LIST"; + /* * action creators */ @@ -101,3 +103,8 @@ export const updateMeta = change => ({ type: UPDATE_SP_META, change: change }); + +export const updatePossibleDefaultsList = change => ({ + type: UPDATE_POSSIBLE_DEFAULTS_LIST, + change: change +}); diff --git a/ui/src/components/PolicySummary.jsx b/ui/src/components/PolicySummary.jsx index 58bff8a..8cb21f0 100644 --- a/ui/src/components/PolicySummary.jsx +++ b/ui/src/components/PolicySummary.jsx @@ -2,7 +2,7 @@ import React from "react"; import PropTypes from "prop-types"; import TextField from "@material-ui/core/TextField"; import { Paper, Card, CardContent } from "../../node_modules/@material-ui/core"; - +import MenuItem from "@material-ui/core/MenuItem"; import ResourceGroup from "../containers/ResourceGroup"; import SubpolicyGroup from "../containers/SubpolicyGroup"; @@ -26,12 +26,18 @@ const PolicySummary = props => { + > + {props.possibleEnsemblerList && + props.possibleEnsemblerList.map(pe => ( + {pe} + ))} + @@ -43,6 +49,7 @@ const PolicySummary = props => { PolicySummary.propTypes = { frequency: PropTypes.string.isRequired, + possibleEnsemblerList: PropTypes.arrayOf(PropTypes.string).isRequired, updateCheckingFrequency: PropTypes.func.isRequired, ensembler: PropTypes.string.isRequired, updateEnsembler: PropTypes.func.isRequired diff --git a/ui/src/components/StatusSwitch.jsx b/ui/src/components/StatusSwitch.jsx index 1201c59..938d372 100644 --- a/ui/src/components/StatusSwitch.jsx +++ b/ui/src/components/StatusSwitch.jsx @@ -17,9 +17,17 @@ class StatusSwitch extends React.Component { handleChange(event) { if (event.target.checked) { - axios.put(`${window.config.env.REACT_APP_NOPAS_ENDPOINT}/resume`); + const resumeUrl = new URL( + "/resume", + window.config.env.REACT_APP_NOPAS_ENDPOINT + ); + axios.put(resumeUrl); } else { - axios.put(`${window.config.env.REACT_APP_NOPAS_ENDPOINT}/pause`); + const pauseUrl = new URL( + "/pause", + window.config.env.REACT_APP_NOPAS_ENDPOINT + ); + axios.put(pauseUrl); } this.setState({ diff --git a/ui/src/components/Subpolicy.jsx b/ui/src/components/Subpolicy.jsx index 3060877..e7326b4 100644 --- a/ui/src/components/Subpolicy.jsx +++ b/ui/src/components/Subpolicy.jsx @@ -4,11 +4,11 @@ import TextField from "@material-ui/core/TextField"; import { Card, CardContent, CardHeader } from "@material-ui/core"; import Fab from "@material-ui/core/Fab"; import DeleteIcon from "@material-ui/icons/Delete"; - +import MenuItem from "@material-ui/core/MenuItem"; import ManagedResources from "../containers/ManagedResources"; const Subpolicy = props => { - const { id, name, resources, metadata } = props; + const { id, name, resources, metadata, possibleSubpolicyList } = props; const updateField = event => { props.updateMeta({ id: id, value: event.target.value }); }; @@ -28,12 +28,18 @@ const Subpolicy = props => { + > + {possibleSubpolicyList && + possibleSubpolicyList.map(ps => ( + {ps} + ))} + { return { @@ -11,6 +12,9 @@ const mapDispatchToProps = dispatch => { return { refreshState: event => { dispatch({ type: "UPDATE_STATE", change: event }); + }, + updatePossibleDefaultsList: event => { + dispatch(updatePossibleDefaultsList(event)); } }; }; diff --git a/ui/src/containers/PolicySummary.js b/ui/src/containers/PolicySummary.js index 132e30b..9e20856 100644 --- a/ui/src/containers/PolicySummary.js +++ b/ui/src/containers/PolicySummary.js @@ -5,7 +5,8 @@ import { updateCheckingFrequency, updateEnsembler } from "../actions"; const mapStateToProps = state => { return { frequency: state.policy.CheckingFreq, - ensembler: state.policy.Ensembler + ensembler: state.policy.Ensembler, + possibleEnsemblerList: state.defaultsList.ensemblers }; }; diff --git a/ui/src/containers/Subpolicy.js b/ui/src/containers/Subpolicy.js index 56cee89..75ff02a 100644 --- a/ui/src/containers/Subpolicy.js +++ b/ui/src/containers/Subpolicy.js @@ -11,7 +11,8 @@ const mapStateToProps = (state, ownProps) => { return { name: sp.Name, metadata: sp.Metadata, - resources: sp.ManagedResources + resources: sp.ManagedResources, + possibleSubpolicyList: state.defaultsList.subpolicies }; }; diff --git a/ui/src/reducers/index.js b/ui/src/reducers/index.js index 9840c59..146eb32 100644 --- a/ui/src/reducers/index.js +++ b/ui/src/reducers/index.js @@ -20,13 +20,18 @@ import { UPDATE_SP_RESOURCE, UPDATE_SUBPOLICY_RESOURCE, UPDATE_SP_META, - DELETE_SUBPOLICY + DELETE_SUBPOLICY, + UPDATE_POSSIBLE_DEFAULTS_LIST } from "../actions"; -// TODO: change to fetch fn that gets from nopas backend +export const possibleDefaults = { + subpolicies: ["CoreRatio"], + ensemblers: ["Conservative"] +} + export const initialState = { CheckingFreq: "1m", - Ensembler: "conservative", + Ensembler: "Conservative", Resources: { uuid1: { Name: "Sample", @@ -296,4 +301,14 @@ export const policy = (state = initialState, action) => { } }; -export const rootReducer = combineReducers({ policy }); +export const defaultsList = (state = possibleDefaults, action) => { + switch (action.type) { + case UPDATE_POSSIBLE_DEFAULTS_LIST: + return action.change; + default: + return state; + } +}; + + +export const rootReducer = combineReducers({ policy, defaultsList }); diff --git a/ui/tests/actions/actions.test.js b/ui/tests/actions/actions.test.js index df01140..b214393 100644 --- a/ui/tests/actions/actions.test.js +++ b/ui/tests/actions/actions.test.js @@ -14,10 +14,7 @@ import { UPDATE_SUBPOLICY_NAME, UPDATE_SUBPOLICY_RESOURCE, UPDATE_SP_META, - DELETE_SUBPOLICY -} from "../../src/actions"; - -import { + DELETE_SUBPOLICY, updateNomadParameters, updateNumericNomadParameters, updateEnsembler, diff --git a/ui/tests/components/ManagedResources.test.js b/ui/tests/components/ManagedResources.test.js index 0b3ab56..2ad6af1 100644 --- a/ui/tests/components/ManagedResources.test.js +++ b/ui/tests/components/ManagedResources.test.js @@ -1,17 +1,10 @@ import React from "react"; import Adapter from "enzyme-adapter-react-16"; import { shallow, configure } from "enzyme"; -import configureStore from "redux-mock-store"; //ES6 modules import Fab from "@material-ui/core/Fab"; import TextField from "@material-ui/core/TextField"; import ManagedResources from "../../src/components/ManagedResources"; -const middlewares = []; -const mockStore = configureStore(middlewares); - -const initialState = {}; -const store = mockStore(initialState); - function shallowSetup() { const props = { id: "test", diff --git a/ui/tests/components/NomadParameters.test.js b/ui/tests/components/NomadParameters.test.js index a5cd267..acabf60 100644 --- a/ui/tests/components/NomadParameters.test.js +++ b/ui/tests/components/NomadParameters.test.js @@ -1,8 +1,8 @@ import React from "react"; import Adapter from "enzyme-adapter-react-16"; import { shallow, configure } from "enzyme"; -import NomadParameters from "../../src/components/NomadParameters"; import TextField from "@material-ui/core/TextField"; +import NomadParameters from "../../src/components/NomadParameters"; function shallowSetup() { const props = { diff --git a/ui/tests/reducers/reducers.test.js b/ui/tests/reducers/reducers.test.js index e6c7933..5743410 100644 --- a/ui/tests/reducers/reducers.test.js +++ b/ui/tests/reducers/reducers.test.js @@ -3,18 +3,13 @@ import { UPDATE_ENSEMBLER, CREATE_RESOURCE, DELETE_RESOURCE, - UPDATE_RESOURCE_NAME, UPDATE_RESOURCE_FIELD, UPDATE_RESOURCE_NUMERIC_FIELD, UPDATE_NOMAD_PARAM, UPDATE_EC2_PARAM, UPDATE_NOMAD_NUMERIC_PARAM, UPDATE_EC2_NUMERIC_PARAM, - CREATE_SUBPOLICY, - UPDATE_SUBPOLICY_NAME, - UPDATE_SUBPOLICY_RESOURCE, - UPDATE_SP_META, - DELETE_SUBPOLICY + CREATE_SUBPOLICY } from "../../src/actions"; import { initialState, policy } from "../../src/reducers";