This is an automated email from the ASF dual-hosted git repository.

zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 257af6a  Added the ability to change a server capability name  (#5605)
257af6a is described below

commit 257af6ad3dce237f7f113c0a6ef460248f5a8917
Author: Rima Shah <[email protected]>
AuthorDate: Wed Mar 10 16:03:44 2021 -0700

    Added the ability to change a server capability name  (#5605)
    
    * Added new PUT route to update name for server capabilities
    
    * Updated TP to make name field on server capabilities page mutable
    Added DB migration script for cascading PK to other associated tables.
    
    * Updated TP end-to-end test case.
    
    * Updated documentation
    
    * Added TO API-v4 test case for update server capability
    
    * Updated changelo
    
    * Updated TO-API test case.
    
    * Updated TO API/v4 test-1
    
    * Resolved server capability url for servers and DS.
    
    * removed extra break line and added to check no two users can modify the 
name at the same time.
    
    * Added checks to avoid panic
    
    * Updated test case and added check for LastUpdatedTime
    
    * Removed commented line.
    
    * Updated error message.
    
    * Addressed review comment.
    
    * Updated server server capability test case.
---
 CHANGELOG.md                                       |  1 +
 docs/source/api/v4/server_capabilities.rst         | 60 ++++++++++++++++++++
 ...0300000000_server_capability_update_cascade.sql | 25 +++++++++
 .../testing/api/v4/servercapabilities_test.go      | 42 ++++++++++++++
 .../testing/api/v4/serverservercapability_test.go  | 64 ++++++++++++++++++++++
 traffic_ops/traffic_ops_golang/routing/routes.go   |  1 +
 .../servercapability/servercapability.go           | 55 +++++++++++++++++++
 traffic_ops/v4-client/servercapability.go          | 11 ++++
 traffic_portal/app/src/app.js                      |  4 +-
 .../app/src/common/api/ServerCapabilityService.js  | 16 +++++-
 .../FormServerCapabilityController.js              |  4 +-
 .../FormEditServerCapabilityController.js}         | 17 ++++--
 .../form/serverCapability/{view => edit}/index.js  |  2 +-
 .../form.serverCapability.tpl.html                 |  5 +-
 .../new/FormNewServerCapabilityController.js       |  5 +-
 .../TableServerCapabilitiesController.js           | 24 ++++----
 .../table.serverCapabilities.tpl.html              |  2 +-
 .../serverCapabilities/deliveryServices/index.js   |  6 +-
 .../serverCapabilities/{view => edit}/index.js     |  8 +--
 .../private/serverCapabilities/servers/index.js    |  6 +-
 .../test/end_to_end/serverCapabilities/pageData.js |  1 +
 .../serverCapabilities/server-capabilities-spec.js |  8 ++-
 22 files changed, 327 insertions(+), 40 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 703e8dd..7eb8d47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ## [unreleased]
 ### Added
+- Traffic Ops/Traffic Portal: 
[#5479](https://github.com/apache/trafficcontrol/issues/5479) - Added the 
ability to change a server capability name
 - Traffic Ops: [#3577](https://github.com/apache/trafficcontrol/issues/3577) - 
Added a query param (server host_name or ID) for servercheck API
 - Traffic Portal: 
[#5318](https://github.com/apache/trafficcontrol/issues/5318) - Rename server 
columns for IPv4 address fields.
 - Traffic Portal: 
[#5361](https://github.com/apache/trafficcontrol/issues/5361) - Added the 
ability to change the name of a topology.
diff --git a/docs/source/api/v4/server_capabilities.rst 
b/docs/source/api/v4/server_capabilities.rst
index fa605cf..3a732ae 100644
--- a/docs/source/api/v4/server_capabilities.rst
+++ b/docs/source/api/v4/server_capabilities.rst
@@ -135,6 +135,66 @@ Response Structure
                }
        }
 
+``PUT``
+========
+Update an existing :term:`Server Capability`.
+
+:Auth. Required: Yes
+:Roles Required: "admin" or "operations"
+:Response Type:  Object
+
+Request Structure
+-----------------
+:name: The name of the :term:`Server Capability`
+
+.. code-block:: http
+       :caption: Request Example
+
+       PUT /api/4.0/server_capabilities/edit?name=RAM HTTP/1.1
+       Host: trafficops.infra.ciab.test
+       User-Agent: curl/7.47.0
+       Accept: */*
+       Cookie: mojolicious=...
+       Content-Length: 15
+       Content-Type: application/json
+
+       {
+               "name": "HDD"
+       }
+
+Response Structure
+------------------
+:name:        The name of this :term:`Server Capability`
+:lastUpdated: The date and time at which this :term:`Server Capability` was 
last updated, in ISO-like format
+
+.. code-block:: http
+       :caption: Response Example
+
+       HTTP/1.1 200 OK
+       Access-Control-Allow-Credentials: true
+       Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, 
Accept, Set-Cookie, Cookie
+       Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE
+       Access-Control-Allow-Origin: *
+       Content-Type: application/json
+       Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 
GMT; Max-Age=3600; HttpOnly
+       Whole-Content-Sha512: 
ysdopC//JQI79BRUa61s6M2HzHxYHpo5RdcuauOoqCYxiVOoUhNZfOVydVkv8zDN2qA374XKnym4kWj3VzQIXg==
+       X-Server-Name: traffic_ops_golang/
+       Date: Wed, 03 March 2021 21:22:08 GMT
+       Content-Length: 137
+
+       {
+               "alerts": [
+                       {
+                               "text": "server capability was updated.",
+                               "level": "success"
+                       }
+               ],
+               "response": {
+                       "name": "HDD",
+                       "lastUpdated": "2021-03-03 21:22:08+00"
+               }
+       }
+
 ``DELETE``
 ==========
 Deletes a specific :term:`Server Capability`.
diff --git 
a/traffic_ops/app/db/migrations/2021030300000000_server_capability_update_cascade.sql
 
b/traffic_ops/app/db/migrations/2021030300000000_server_capability_update_cascade.sql
new file mode 100644
index 0000000..d2e0b55
--- /dev/null
+++ 
b/traffic_ops/app/db/migrations/2021030300000000_server_capability_update_cascade.sql
@@ -0,0 +1,25 @@
+/*
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+        http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+-- +goose Up
+-- SQL in section 'Up' is executed when this migration is applied
+ALTER TABLE public.server_server_capability DROP CONSTRAINT 
fk_server_capability;
+ALTER TABLE public.deliveryservices_required_capability DROP CONSTRAINT 
fk_required_capability;
+ALTER TABLE public.server_server_capability ADD CONSTRAINT 
fk_server_capability FOREIGN KEY (server_capability) REFERENCES 
server_capability(name) ON UPDATE CASCADE ON DELETE RESTRICT;
+ALTER TABLE public.deliveryservices_required_capability ADD CONSTRAINT 
fk_required_capability FOREIGN KEY (required_capability) REFERENCES 
server_capability(name) ON UPDATE CASCADE ON DELETE RESTRICT;
+
+-- +goose Down
+-- SQL section 'Down' is executed when this migration is rolled back
+ALTER TABLE public.server_server_capability DROP CONSTRAINT 
fk_server_capability;
+ALTER TABLE public.deliveryservices_required_capability DROP CONSTRAINT 
fk_required_capability;
+ALTER TABLE public.server_server_capability ADD CONSTRAINT 
fk_server_capability FOREIGN KEY (server_capability) REFERENCES 
server_capability(name) ON DELETE RESTRICT;
+ALTER TABLE public.deliveryservices_required_capability ADD CONSTRAINT 
fk_required_capability FOREIGN KEY (required_capability) REFERENCES 
server_capability(name) ON DELETE RESTRICT;
diff --git a/traffic_ops/testing/api/v4/servercapabilities_test.go 
b/traffic_ops/testing/api/v4/servercapabilities_test.go
index a0bcdeb..6ad6b1d 100644
--- a/traffic_ops/testing/api/v4/servercapabilities_test.go
+++ b/traffic_ops/testing/api/v4/servercapabilities_test.go
@@ -28,6 +28,7 @@ func TestServerCapabilities(t *testing.T) {
                SortTestServerCapabilities(t)
                GetTestServerCapabilities(t)
                ValidationTestServerCapabilities(t)
+               UpdateTestServerCapabilities(t)
        })
 }
 
@@ -89,6 +90,47 @@ func ValidationTestServerCapabilities(t *testing.T) {
        }
 }
 
+func UpdateTestServerCapabilities(t *testing.T) {
+       var header http.Header
+
+       // Get server capability name and edit it to a new name
+       resp, _, err := TOSession.GetServerCapabilitiesWithHdr(header)
+       if err != nil {
+               t.Fatalf("Expected no error, but got %v", err.Error())
+       }
+       if len(resp) == 0 {
+               t.Fatalf("no server capability in response, quitting")
+       }
+       origName := resp[0].Name
+       newSCName := "sc-test"
+       resp[0].Name = newSCName
+
+       // Update server capability with new name
+       updateResponse, _, err := 
TOSession.UpdateServerCapabilityByName(origName, &resp[0])
+       if err != nil {
+               t.Errorf("cannot PUT server capability: %v - %v", err, 
updateResponse)
+       }
+
+       // Get updated name
+       getResp, _, err := TOSession.GetServerCapabilityWithHdr(newSCName, 
header)
+       if err != nil {
+               t.Fatalf("Expected no error, but %v", err.Error())
+       }
+       if getResp == nil {
+               t.Fatalf("no server capability in response, quitting")
+       }
+       if getResp.Name != newSCName {
+               t.Errorf("failed to update server capability name, expected: %v 
but got: %v", newSCName, updateResponse.Name)
+       }
+
+       // Set everything back as it was for further testing.
+       resp[0].Name = origName
+       r, _, err := TOSession.UpdateServerCapabilityByName(newSCName, &resp[0])
+       if err != nil {
+               t.Errorf("cannot PUT seerver capability: %v - %v", err, r)
+       }
+}
+
 func DeleteTestServerCapabilities(t *testing.T) {
 
        for _, sc := range testData.ServerCapabilities {
diff --git a/traffic_ops/testing/api/v4/serverservercapability_test.go 
b/traffic_ops/testing/api/v4/serverservercapability_test.go
index 3cc7475..b8e7d25 100644
--- a/traffic_ops/testing/api/v4/serverservercapability_test.go
+++ b/traffic_ops/testing/api/v4/serverservercapability_test.go
@@ -34,6 +34,7 @@ func TestServerServerCapabilities(t *testing.T) {
                GetTestServerServerCapabilitiesIMS(t)
                GetTestServerServerCapabilities(t)
                GetDeliveryServiceServersWithCapabilities(t)
+               UpdateTestServerServerCapabilities(t)
        })
 }
 
@@ -222,6 +223,69 @@ func GetTestServerServerCapabilities(t *testing.T) {
        }
 }
 
+func UpdateTestServerServerCapabilities(t *testing.T) {
+       var header http.Header
+
+       // Get server capability name and edit it to a new name
+       resp, _, err := TOSession.GetServerCapabilitiesWithHdr(header)
+       if err != nil {
+               t.Fatalf("Expected no error, but got %v", err.Error())
+       }
+       if len(resp) == 0 {
+               t.Fatal("no server capability in response, quitting")
+       }
+       origName := resp[0].Name
+       newSCName := "sc-test"
+       resp[0].Name = newSCName
+
+       // Get all servers related to original sever capability name
+       servOrigResp, _, err := 
TOSession.GetServerServerCapabilitiesWithHdr(nil, nil, &origName, nil)
+       if err != nil {
+               t.Fatalf("cannot GET server capabilities assigned to servers by 
server capability name %v: %v", origName, err)
+       }
+       if len(servOrigResp) == 0 {
+               t.Fatalf("no servers associated with server capability name: 
%v", origName)
+       }
+       mapOrigServ := make(map[string]string)
+       for _, s := range servOrigResp {
+               mapOrigServ[*s.Server] = *s.ServerCapability
+       }
+
+       // Update server capability with new name
+       updateResponse, _, err := 
TOSession.UpdateServerCapabilityByName(origName, &resp[0])
+       if err != nil {
+               t.Errorf("cannot PUT server capability: %v - %v", err, 
updateResponse)
+       }
+
+       //To check whether the primary key change trickled down to server table
+       servUpdatedResp, _, err := 
TOSession.GetServerServerCapabilitiesWithHdr(nil, nil, &newSCName, nil)
+       if err != nil {
+               t.Fatalf("cannot GET server capabilities assigned to servers by 
server capability name %v: %v", newSCName, err)
+       }
+       if len(servUpdatedResp) == 0 {
+               t.Fatalf("no server associated with server capability name:%v", 
newSCName)
+       }
+       if len(servOrigResp) != len(servUpdatedResp) {
+               t.Fatalf("length of servers for a given server capability name 
is different, expected: %v-%v, got: %v-%v", origName, len(servOrigResp), 
newSCName, len(servUpdatedResp))
+       }
+       for _, s := range servUpdatedResp {
+               if newSCName != *s.ServerCapability {
+                       t.Errorf("GET server server capabilities by server 
capability returned non-matching server capability: %v", *s.ServerCapability)
+               }
+               _, ok := mapOrigServ[*s.Server]
+               if !ok {
+                       t.Fatalf("server capability name change didn't trickle 
to server: %v", *s.Server)
+               }
+       }
+
+       // Set everything back as it was for further testing.
+       resp[0].Name = origName
+       r, _, err := TOSession.UpdateServerCapabilityByName(newSCName, &resp[0])
+       if err != nil {
+               t.Errorf("cannot PUT seerver capability: %v - %v", err, r)
+       }
+}
+
 func DeleteTestServerServerCapabilities(t *testing.T) {
        // Get Server Capabilities to delete them
        sscs, _, err := TOSession.GetServerServerCapabilitiesWithHdr(nil, nil, 
nil, nil)
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index f7794b7..d416637 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -326,6 +326,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
                //Server Capability
                {api.Version{4, 0}, http.MethodGet, `server_capabilities$`, 
api.ReadHandler(&servercapability.TOServerCapability{}), 
auth.PrivLevelReadOnly, Authenticated, nil, 4104073913},
                {api.Version{4, 0}, http.MethodPost, `server_capabilities$`, 
api.CreateHandler(&servercapability.TOServerCapability{}), 
auth.PrivLevelOperations, Authenticated, nil, 40744707083},
+               {api.Version{4, 0}, http.MethodPut, `server_capabilities$`, 
api.UpdateHandler(&servercapability.TOServerCapability{}), 
auth.PrivLevelOperations, Authenticated, nil, 42543770109},
                {api.Version{4, 0}, http.MethodDelete, `server_capabilities$`, 
api.DeleteHandler(&servercapability.TOServerCapability{}), 
auth.PrivLevelOperations, Authenticated, nil, 4364150383},
 
                //Server Server Capabilities: CRUD
diff --git 
a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go 
b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
index b16235c..fc9d843 100644
--- a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
+++ b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
@@ -20,6 +20,11 @@ package servercapability
  */
 
 import (
+       "database/sql"
+       "errors"
+       "fmt"
+
+       "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
        "github.com/apache/trafficcontrol/lib/go-util"
@@ -33,6 +38,7 @@ import (
 
 type TOServerCapability struct {
        api.APIInfoImpl `json:"-"`
+       RequestedName   string
        tc.ServerCapability
 }
 
@@ -60,6 +66,15 @@ FROM
 `
 }
 
+func (v *TOServerCapability) updateQuery() string {
+       return `
+UPDATE server_capability sc SET
+       name = $1
+WHERE sc.name = $2
+RETURNING sc.name, sc.last_updated
+`
+}
+
 func (v *TOServerCapability) DeleteQuery() string {
        return `
 DELETE FROM server_capability WHERE name=:name
@@ -82,6 +97,7 @@ func (v TOServerCapability) GetKeys() 
(map[string]interface{}, bool) {
 }
 
 func (v *TOServerCapability) SetKeys(keys map[string]interface{}) {
+       v.RequestedName = v.Name
        v.Name, _ = keys["name"].(string)
 }
 
@@ -105,6 +121,45 @@ func (v *TOServerCapability) Read(h http.Header, useIMS 
bool) ([]interface{}, er
        api.DefaultSort(v.APIInfo(), "name")
        return api.GenericRead(h, v, useIMS)
 }
+
+func (v *TOServerCapability) Update(h http.Header) (error, error, int) {
+       sc, userErr, sysErr, errCode, _ := v.Read(h, false)
+       if userErr != nil || sysErr != nil {
+               return userErr, sysErr, errCode
+       }
+       if len(sc) != 1 {
+               return fmt.Errorf("cannot find exactly one server capability 
with the query string provided"), nil, http.StatusBadRequest
+       }
+
+       // check if the name was being updated by someone else
+       var existingLastUpdated *tc.TimeNoMod
+       q := `SELECT last_updated FROM server_capability WHERE name = $1`
+       if err := v.ReqInfo.Tx.QueryRow(q, v.Name).Scan(&existingLastUpdated); 
err != nil {
+               if err == sql.ErrNoRows {
+                       return errors.New("server capability was not found"), 
nil, http.StatusNotFound
+               }
+               return nil, errors.New("server capability update: querying: " + 
err.Error()), http.StatusInternalServerError
+       }
+       if !api.IsUnmodified(h, existingLastUpdated.Time) {
+               return errors.New("the resource has been modified since the 
time specified by the request headers"), nil, http.StatusPreconditionFailed
+       }
+
+       // udpate server capability name
+       rows, err := v.ReqInfo.Tx.Query(v.updateQuery(), v.RequestedName, 
v.Name)
+       if err != nil {
+               return nil, fmt.Errorf("server capability update: error setting 
the name for server capability %v: %v", v.Name, err.Error()), 
http.StatusInternalServerError
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       for rows.Next() {
+               err = rows.Scan(&v.Name, &v.LastUpdated)
+               if err != nil {
+                       return api.ParseDBError(err)
+               }
+       }
+       return nil, nil, http.StatusOK
+}
+
 func (v *TOServerCapability) SelectMaxLastUpdatedQuery(where, orderBy, 
pagination, tableName string) string {
        return `SELECT max(t) from (
                SELECT max(sc.last_updated) as t from server_capability sc ` + 
where + orderBy + pagination +
diff --git a/traffic_ops/v4-client/servercapability.go 
b/traffic_ops/v4-client/servercapability.go
index 49864cb..aaf5dab 100644
--- a/traffic_ops/v4-client/servercapability.go
+++ b/traffic_ops/v4-client/servercapability.go
@@ -69,6 +69,17 @@ func (to *Session) GetServerCapability(name string) 
(*tc.ServerCapability, tocli
        return to.GetServerCapabilityWithHdr(name, nil)
 }
 
+// UpdateServerCapabilityByName updates a Server Capability by name.
+func (to *Session) UpdateServerCapabilityByName(name string, sc 
*tc.ServerCapability) (*tc.ServerCapability, toclientlib.ReqInf, error) {
+       route := fmt.Sprintf("%s?name=%s", APIServerCapabilities, 
url.QueryEscape(name))
+       var data tc.ServerCapability
+       reqInf, err := to.put(route, sc, nil, &data)
+       if err != nil {
+               return nil, reqInf, err
+       }
+       return &data, reqInf, nil
+}
+
 // DeleteServerCapability deletes the given server capability by name.
 func (to *Session) DeleteServerCapability(name string) (tc.Alerts, 
toclientlib.ReqInf, error) {
        reqUrl := fmt.Sprintf("%s?name=%s", APIServerCapabilities, 
url.QueryEscape(name))
diff --git a/traffic_portal/app/src/app.js b/traffic_portal/app/src/app.js
index fb57a75..0e47f22 100644
--- a/traffic_portal/app/src/app.js
+++ b/traffic_portal/app/src/app.js
@@ -186,7 +186,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./modules/private/serverCapabilities/list').name,
         require('./modules/private/serverCapabilities/new').name,
         require('./modules/private/serverCapabilities/servers').name,
-        require('./modules/private/serverCapabilities/view').name,
+        require('./modules/private/serverCapabilities/edit').name,
         require('./modules/private/servers').name,
         require('./modules/private/servers/capabilities').name,
         require('./modules/private/servers/deliveryServices').name,
@@ -325,7 +325,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./common/modules/form/role/new').name,
         require('./common/modules/form/serverCapability').name,
         require('./common/modules/form/serverCapability/new').name,
-        require('./common/modules/form/serverCapability/view').name,
+        require('./common/modules/form/serverCapability/edit').name,
         require('./common/modules/form/server').name,
         require('./common/modules/form/server/edit').name,
         require('./common/modules/form/server/new').name,
diff --git a/traffic_portal/app/src/common/api/ServerCapabilityService.js 
b/traffic_portal/app/src/common/api/ServerCapabilityService.js
index dfdcad9..2f1793f 100644
--- a/traffic_portal/app/src/common/api/ServerCapabilityService.js
+++ b/traffic_portal/app/src/common/api/ServerCapabilityService.js
@@ -68,6 +68,18 @@ var ServerCapabilityService = function($http, ENV, 
locationUtils, messageModel)
                );
        };
 
+       this.updateServerCapability = function(currentName, serverCapability) {
+               return $http.put(ENV.api['root'] + 'server_capabilities', 
serverCapability, {params: {"name": currentName}}).then(
+                       function(result) {
+                               return result;
+                       },
+                       function(err) {
+                               messageModel.setMessages(err.data.alerts, 
false);
+                               throw err;
+                       }
+               );
+       };
+
        this.getServerCapabilityServers = function(capabilityName) {
                return $http.get(ENV.api['root'] + 
'server_server_capabilities', { params: { serverCapability: capabilityName } 
}).then(
                        function (result) {
@@ -76,7 +88,7 @@ var ServerCapabilityService = function($http, ENV, 
locationUtils, messageModel)
                        function (err) {
                                throw err;
                        }
-               )
+               );
        };
 
        this.getServerCapabilityDeliveryServices = function(capabilityName) {
@@ -87,7 +99,7 @@ var ServerCapabilityService = function($http, ENV, 
locationUtils, messageModel)
                        function (err) {
                                throw err;
                        }
-               )
+               );
        };
 
 };
diff --git 
a/traffic_portal/app/src/common/modules/form/serverCapability/FormServerCapabilityController.js
 
b/traffic_portal/app/src/common/modules/form/serverCapability/FormServerCapabilityController.js
index 9810512..efc2bdc 100644
--- 
a/traffic_portal/app/src/common/modules/form/serverCapability/FormServerCapabilityController.js
+++ 
b/traffic_portal/app/src/common/modules/form/serverCapability/FormServerCapabilityController.js
@@ -22,11 +22,11 @@ var FormServerCapabilityController = 
function(serverCapability, $scope, $locatio
        $scope.serverCapability = serverCapability;
 
        $scope.viewServers = function() {
-               $location.path($location.path() + '/servers');
+               $location.path('/server-capabilities/servers');
        };
 
        $scope.viewDeliveryServices = function() {
-               $location.path($location.path() + '/delivery-services');
+               $location.path('/server-capabilities/delivery-services');
        };
 
        $scope.navigateToPath = locationUtils.navigateToPath;
diff --git 
a/traffic_portal/app/src/common/modules/form/serverCapability/view/FormViewServerCapabilityController.js
 
b/traffic_portal/app/src/common/modules/form/serverCapability/edit/FormEditServerCapabilityController.js
similarity index 74%
rename from 
traffic_portal/app/src/common/modules/form/serverCapability/view/FormViewServerCapabilityController.js
rename to 
traffic_portal/app/src/common/modules/form/serverCapability/edit/FormEditServerCapabilityController.js
index 8a8eaf8..275e3bd 100644
--- 
a/traffic_portal/app/src/common/modules/form/serverCapability/view/FormViewServerCapabilityController.js
+++ 
b/traffic_portal/app/src/common/modules/form/serverCapability/edit/FormEditServerCapabilityController.js
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-var FormViewServerCapabilityController = function(serverCapability, $scope, 
$controller, $uibModal, $anchorScroll, locationUtils, serverCapabilityService) {
+var FormEditServerCapabilityController = function(serverCapability, $scope, 
$controller, $uibModal, $anchorScroll, locationUtils, messageModel, 
serverCapabilityService) {
 
        // extends the FormServerCapabilityController to inherit common methods
        angular.extend(this, $controller('FormServerCapabilityController', { 
serverCapability: serverCapability, $scope: $scope }));
@@ -32,7 +32,8 @@ var FormViewServerCapabilityController = 
function(serverCapability, $scope, $con
        $scope.serverCapabilityName = serverCapability.name;
 
        $scope.settings = {
-               isNew: false
+               isNew: false,
+               saveLabel: 'Update'
        };
 
        $scope.confirmDelete = function(serverCapability) {
@@ -55,7 +56,15 @@ var FormViewServerCapabilityController = 
function(serverCapability, $scope, $con
                });
        };
 
+       $scope.save = function(currentName, serverCapability) {
+               serverCapabilityService.updateServerCapability(currentName, 
serverCapability).
+                       then(function(result) {
+                               messageModel.setMessages(result.data.alerts, 
currentName !== serverCapability.name);
+                               
locationUtils.navigateToPath('/server-capabilities/edit?name=' + 
result.data.response.name);
+                       });
+       };
+
 };
 
-FormViewServerCapabilityController.$inject = ['serverCapability', '$scope', 
'$controller', '$uibModal', '$anchorScroll', 'locationUtils', 
'serverCapabilityService'];
-module.exports = FormViewServerCapabilityController;
+FormEditServerCapabilityController.$inject = ['serverCapability', '$scope', 
'$controller', '$uibModal', '$anchorScroll', 'locationUtils', 'messageModel', 
'serverCapabilityService'];
+module.exports = FormEditServerCapabilityController;
diff --git 
a/traffic_portal/app/src/common/modules/form/serverCapability/view/index.js 
b/traffic_portal/app/src/common/modules/form/serverCapability/edit/index.js
similarity index 89%
rename from 
traffic_portal/app/src/common/modules/form/serverCapability/view/index.js
rename to 
traffic_portal/app/src/common/modules/form/serverCapability/edit/index.js
index 06edc8f..cce2495 100644
--- a/traffic_portal/app/src/common/modules/form/serverCapability/view/index.js
+++ b/traffic_portal/app/src/common/modules/form/serverCapability/edit/index.js
@@ -18,4 +18,4 @@
  */
 
 module.exports = angular.module('trafficPortal.form.serverCapability.view', [])
-       .controller('FormViewServerCapabilityController', 
require('./FormViewServerCapabilityController'));
+       .controller('FormEditServerCapabilityController', 
require('./FormEditServerCapabilityController'));
diff --git 
a/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html
 
b/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html
index 823a8f2..3bd82b9 100644
--- 
a/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html
+++ 
b/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html
@@ -35,7 +35,7 @@ under the License.
             <div class="form-group" ng-class="{'has-error': 
hasError(serverCapabilityForm.name), 'has-feedback': 
hasError(serverCapabilityForm.name)}">
                 <label for="name" class="control-label col-md-2 col-sm-2 
col-xs-12">Name *</label>
                 <div class="col-md-10 col-sm-10 col-xs-12">
-                    <input id="name" name="name" type="text" 
class="form-control" ng-disabled="!settings.isNew" 
ng-model="serverCapability.name" required pattern="^[a-zA-Z0-9_-]+$" autofocus>
+                    <input id="name" name="name" type="text" 
class="form-control" ng-model="serverCapability.name" required 
pattern="^[a-zA-Z0-9_-]+$" autofocus>
                     <small class="input-error" 
ng-show="hasPropertyError(serverCapabilityForm.name, 
'required')">Required</small>
                     <small class="input-error" 
ng-show="hasPropertyError(serverCapabilityForm.name, 'pattern')">Must be 
alphamumeric with no spaces. Dashes and underscores also allowed.</small>
                     <span ng-show="hasError(serverCapabilityForm.name)" 
class="form-control-feedback"><i class="fa fa-times"></i></span>
@@ -43,7 +43,8 @@ under the License.
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-danger" 
ng-if="!settings.isNew" 
ng-click="confirmDelete(serverCapability)">Delete</button>
-                <button type="button" class="btn btn-success" 
ng-if="settings.isNew" ng-disabled="serverCapabilityForm.$pristine || 
serverCapabilityForm.$invalid" ng-click="save(serverCapability)">Create</button>
+                <button type="button" class="btn btn-success" 
ng-if="settings.isNew" ng-disabled="serverCapabilityForm.$pristine || 
serverCapabilityForm.$invalid" 
ng-click="save(serverCapability)">{{settings.saveLabel}}</button>
+                <button type="button" class="btn btn-success" 
ng-if="!settings.isNew" ng-disabled="serverCapabilityForm.$pristine || 
serverCapabilityForm.$invalid" ng-click="save(serverCapabilityName, 
serverCapability)">{{settings.saveLabel}}</button>
             </div>
         </form>
     </div>
diff --git 
a/traffic_portal/app/src/common/modules/form/serverCapability/new/FormNewServerCapabilityController.js
 
b/traffic_portal/app/src/common/modules/form/serverCapability/new/FormNewServerCapabilityController.js
index bea2b03..e017ba9 100644
--- 
a/traffic_portal/app/src/common/modules/form/serverCapability/new/FormNewServerCapabilityController.js
+++ 
b/traffic_portal/app/src/common/modules/form/serverCapability/new/FormNewServerCapabilityController.js
@@ -25,11 +25,12 @@ var FormNewServerCapabilityController = 
function(serverCapability, $scope, $cont
        $scope.serverCapabilityName = 'New';
 
        $scope.settings = {
-               isNew: true
+               isNew: true,
+               saveLabel: 'Create'
        };
 
        $scope.save = function(serverCapability) {
-               serverCapabilityService.createServerCapability(serverCapability)
+               
serverCapabilityService.createServerCapability(serverCapability);
        };
 
 };
diff --git 
a/traffic_portal/app/src/common/modules/table/serverCapabilities/TableServerCapabilitiesController.js
 
b/traffic_portal/app/src/common/modules/table/serverCapabilities/TableServerCapabilitiesController.js
index d7d7907..997665d 100644
--- 
a/traffic_portal/app/src/common/modules/table/serverCapabilities/TableServerCapabilitiesController.js
+++ 
b/traffic_portal/app/src/common/modules/table/serverCapabilities/TableServerCapabilitiesController.js
@@ -19,6 +19,14 @@
 
 var TableServerCapabilitiesController = function(serverCapabilities, $scope, 
$state, $uibModal, $window, locationUtils, serverCapabilityService, 
messageModel) {
 
+       var deleteServerCapability = function(serverCapability) {
+               
serverCapabilityService.deleteServerCapability(serverCapability.name)
+                       .then(function(result) {
+                               messageModel.setMessages(result.alerts, false);
+                               $scope.refresh();
+                       });
+       };
+
        var confirmDelete = function(serverCapability) {
                var params = {
                        title: 'Delete Server Capability: ' + 
serverCapability.name,
@@ -39,14 +47,6 @@ var TableServerCapabilitiesController = 
function(serverCapabilities, $scope, $st
                });
        };
 
-       var deleteServerCapability = function(serverCapability) {
-               
serverCapabilityService.deleteServerCapability(serverCapability.name)
-                       .then(function(result) {
-                               messageModel.setMessages(result.alerts, false);
-                               $scope.refresh();
-                       });
-       };
-
        $scope.serverCapabilities = serverCapabilities;
 
        $scope.contextMenuItems = [
@@ -58,9 +58,9 @@ var TableServerCapabilitiesController = 
function(serverCapabilities, $scope, $st
                },
                null, // Dividier
                {
-                       text: 'View',
+                       text: 'Edit',
                        click: function ($itemScope) {
-                               $scope.viewServerCapability($itemScope.sc.name);
+                               $scope.editServerCapability($itemScope.sc.name);
                        }
                },
                {
@@ -88,8 +88,8 @@ var TableServerCapabilitiesController = 
function(serverCapabilities, $scope, $st
                locationUtils.navigateToPath('/server-capabilities/new');
        };
 
-       $scope.viewServerCapability = function(name) {
-               locationUtils.navigateToPath('/server-capabilities/' + name);
+       $scope.editServerCapability = function(name) {
+               locationUtils.navigateToPath('/server-capabilities/edit?name=' 
+ name);
        };
 
        $scope.refresh = function() {
diff --git 
a/traffic_portal/app/src/common/modules/table/serverCapabilities/table.serverCapabilities.tpl.html
 
b/traffic_portal/app/src/common/modules/table/serverCapabilities/table.serverCapabilities.tpl.html
index 2d94004..130cd30 100644
--- 
a/traffic_portal/app/src/common/modules/table/serverCapabilities/table.serverCapabilities.tpl.html
+++ 
b/traffic_portal/app/src/common/modules/table/serverCapabilities/table.serverCapabilities.tpl.html
@@ -37,7 +37,7 @@ under the License.
             </tr>
             </thead>
             <tbody>
-            <tr ng-click="viewServerCapability(sc.name)" ng-repeat="sc in 
::serverCapabilities" context-menu="contextMenuItems">
+            <tr ng-click="editServerCapability(sc.name)" ng-repeat="sc in 
::serverCapabilities" context-menu="contextMenuItems">
                 <td name="name" 
data-search="^{{::sc.name}}$">{{::sc.name}}</td>
             </tr>
             </tbody>
diff --git 
a/traffic_portal/app/src/modules/private/serverCapabilities/deliveryServices/index.js
 
b/traffic_portal/app/src/modules/private/serverCapabilities/deliveryServices/index.js
index a77f05d..988ca96 100644
--- 
a/traffic_portal/app/src/modules/private/serverCapabilities/deliveryServices/index.js
+++ 
b/traffic_portal/app/src/modules/private/serverCapabilities/deliveryServices/index.js
@@ -21,17 +21,17 @@ module.exports = 
angular.module('trafficPortal.private.serverCapabilities.delive
        .config(function($stateProvider, $urlRouterProvider) {
                $stateProvider
                        
.state('trafficPortal.private.serverCapabilities.deliveryServices', {
-                               url: '/{serverCapability}/delivery-services',
+                               url: '/delivery-services?name',
                                views: {
                                        serverCapabilitiesContent: {
                                                templateUrl: 
'common/modules/table/serverCapabilityDeliveryServices/table.serverCapabilityDeliveryServices.tpl.html',
                                                controller: 
'TableServerCapabilityDeliveryServicesController',
                                                resolve: {
                                                        serverCapability: 
function($stateParams, serverCapabilityService) {
-                                                               return 
serverCapabilityService.getServerCapability($stateParams.serverCapability);
+                                                               return 
serverCapabilityService.getServerCapability($stateParams.name);
                                                        },
                                                        deliveryServices: 
function($stateParams, serverCapabilityService) {
-                                                               return 
serverCapabilityService.getServerCapabilityDeliveryServices($stateParams.serverCapability);
+                                                               return 
serverCapabilityService.getServerCapabilityDeliveryServices($stateParams.name);
                                                        }
                                                }
                                        }
diff --git 
a/traffic_portal/app/src/modules/private/serverCapabilities/view/index.js 
b/traffic_portal/app/src/modules/private/serverCapabilities/edit/index.js
similarity index 88%
rename from 
traffic_portal/app/src/modules/private/serverCapabilities/view/index.js
rename to 
traffic_portal/app/src/modules/private/serverCapabilities/edit/index.js
index 2e71427..61e19b0 100644
--- a/traffic_portal/app/src/modules/private/serverCapabilities/view/index.js
+++ b/traffic_portal/app/src/modules/private/serverCapabilities/edit/index.js
@@ -20,15 +20,15 @@
 module.exports = 
angular.module('trafficPortal.private.serverCapabilities.view', [])
        .config(function($stateProvider, $urlRouterProvider) {
                $stateProvider
-                       .state('trafficPortal.private.serverCapabilities.view', 
{
-                               url: '/{serverCapability}',
+                       .state('trafficPortal.private.serverCapabilities.edit', 
{
+                               url: '/edit?name',
                                views: {
                                        serverCapabilitiesContent: {
                                                templateUrl: 
'common/modules/form/serverCapability/form.serverCapability.tpl.html',
-                                               controller: 
'FormViewServerCapabilityController',
+                                               controller: 
'FormEditServerCapabilityController',
                                                resolve: {
                                                        serverCapability: 
function($stateParams, serverCapabilityService) {
-                                                               return 
serverCapabilityService.getServerCapability($stateParams.serverCapability);
+                                                               return 
serverCapabilityService.getServerCapability($stateParams.name);
                                                        }
                                                }
                                        }
diff --git 
a/traffic_portal/app/src/modules/private/serverCapabilities/servers/index.js 
b/traffic_portal/app/src/modules/private/serverCapabilities/servers/index.js
index 51e578e..83cf046 100644
--- a/traffic_portal/app/src/modules/private/serverCapabilities/servers/index.js
+++ b/traffic_portal/app/src/modules/private/serverCapabilities/servers/index.js
@@ -21,17 +21,17 @@ module.exports = 
angular.module('trafficPortal.private.serverCapabilities.server
        .config(function($stateProvider, $urlRouterProvider) {
                $stateProvider
                        
.state('trafficPortal.private.serverCapabilities.servers', {
-                               url: '/{serverCapability}/servers',
+                               url: '/servers?name',
                                views: {
                                        serverCapabilitiesContent: {
                                                templateUrl: 
'common/modules/table/serverCapabilityServers/table.serverCapabilityServers.tpl.html',
                                                controller: 
'TableServerCapabilityServersController',
                                                resolve: {
                                                        serverCapability: 
function($stateParams, serverCapabilityService) {
-                                                               return 
serverCapabilityService.getServerCapability($stateParams.serverCapability);
+                                                               return 
serverCapabilityService.getServerCapability($stateParams.name);
                                                        },
                                                        servers: 
function($stateParams, serverCapabilityService) {
-                                                               return 
serverCapabilityService.getServerCapabilityServers($stateParams.serverCapability);
+                                                               return 
serverCapabilityService.getServerCapabilityServers($stateParams.name);
                                                        }
                                                }
                                        }
diff --git a/traffic_portal/test/end_to_end/serverCapabilities/pageData.js 
b/traffic_portal/test/end_to_end/serverCapabilities/pageData.js
index 359941b..3e69548 100644
--- a/traffic_portal/test/end_to_end/serverCapabilities/pageData.js
+++ b/traffic_portal/test/end_to_end/serverCapabilities/pageData.js
@@ -20,6 +20,7 @@
 module.exports = function(){
        this.name=element(by.name('name'));
        this.createButton=element(by.buttonText('Create'));
+       this.updateButton=element(by.buttonText('Update'));
        this.deleteButton=element(by.buttonText('Delete'));
        
this.searchFilter=element(by.id('serverCapabilitiesTable_filter')).element(by.css('label
 input'));
        this.confirmWithNameInput=element(by.name('confirmWithNameInput'));
diff --git 
a/traffic_portal/test/end_to_end/serverCapabilities/server-capabilities-spec.js 
b/traffic_portal/test/end_to_end/serverCapabilities/server-capabilities-spec.js
index c6c5868..1f11a5b 100644
--- 
a/traffic_portal/test/end_to_end/serverCapabilities/server-capabilities-spec.js
+++ 
b/traffic_portal/test/end_to_end/serverCapabilities/server-capabilities-spec.js
@@ -53,8 +53,8 @@ describe('Traffic Portal Server Capabilities Test Suite', 
function() {
                
expect(browser.getCurrentUrl().then(commonFunctions.urlPath)).toEqual(commonFunctions.urlPath(browser.baseUrl)+"#!/server-capabilities");
        });
 
-       it('should view the new server capability', function() {
-               console.log('Viewing the new server capability: ' + 
myNewServerCap.name);
+       it('should edit an existing server capability', function() {
+               console.log('Editing an existing server capability: ' + 
myNewServerCap.name);
                pageData.searchFilter.sendKeys(myNewServerCap.name);
                element.all(by.repeater('sc in 
::serverCapabilities')).filter(function(row){
                        return 
row.element(by.name('name')).getText().then(function(val){
@@ -62,6 +62,10 @@ describe('Traffic Portal Server Capabilities Test Suite', 
function() {
                        });
                }).get(0).click();
                expect(pageData.name.getText() === myNewServerCap.name);
+               expect(pageData.updateButton.isEnabled()).toBe(false);
+               pageData.name.sendKeys("-updated");
+               expect(pageData.updateButton.isEnabled()).toBe(true);
+               expect(pageData.name.getText() === 
myNewServerCap.name+"-updated");
        });
 
 });

Reply via email to