Copilot commented on code in PR #12854:
URL: https://github.com/apache/cloudstack/pull/12854#discussion_r2989373346


##########
engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql:
##########
@@ -89,8 +89,15 @@ from
     `cloud`.`domain` ON account.domain_id = domain.id
         left join
     `cloud`.`data_center` ON account.default_zone_id = data_center.id
-        left join
-    `cloud`.`account_netstats_view` ON account.id = 
account_netstats_view.account_id
+        left join lateral (
+        select
+            coalesce(sum(`user_statistics`.`net_bytes_received` + 
`user_statistics`.`current_bytes_received`), 0) AS `bytesReceived`,
+            coalesce(sum(`user_statistics`.`net_bytes_sent` + 
`user_statistics`.`current_bytes_sent`), 0) AS `bytesSent`
+        from
+            `cloud`.`user_statistics`
+        where
+            `user_statistics`.`account_id` = `account`.`id`
+    ) AS `account_netstats` ON TRUE

Review Comment:
   The aggregation can produce incorrect totals when either `net_bytes_*` or 
`current_bytes_*` is NULL: the expression `(net + current)` becomes NULL for 
that row and SUM will skip it, losing non-null values. Use COALESCE per column 
inside the SUM (e.g., `SUM(COALESCE(net,0) + COALESCE(current,0))`) to match 
the previous semantics. Also consider avoiding `LATERAL` (use a grouped derived 
table joined on `account_id`) to reduce per-row correlation overhead and 
improve compatibility across supported MySQL/MariaDB versions.
   ```suggestion
           left join (
           select
               `user_statistics`.`account_id` AS `account_id`,
               coalesce(sum(coalesce(`user_statistics`.`net_bytes_received`, 0) 
+ coalesce(`user_statistics`.`current_bytes_received`, 0)), 0) AS 
`bytesReceived`,
               coalesce(sum(coalesce(`user_statistics`.`net_bytes_sent`, 0) + 
coalesce(`user_statistics`.`current_bytes_sent`, 0)), 0) AS `bytesSent`
           from
               `cloud`.`user_statistics`
           group by
               `user_statistics`.`account_id`
       ) AS `account_netstats` ON `account_netstats`.`account_id` = 
`account`.`id`
   ```



##########
ui/src/views/tools/ImportUnmanagedInstance.vue:
##########
@@ -1,19 +1,10 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you 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.
+// Licensed to the Apache Software Foundation (ASF) under one // or more 
contributor license agreements. See the NOTICE
+file // distributed with this work for additional information // regarding 
copyright ownership. The ASF licenses this
+file // to you 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.

Review Comment:
   The license header appears to have been auto-joined into invalid syntax: 
lines 2–3 are not comments, and line 5 starts with raw text (`Unless 
required...`) which will break parsing/compilation of the SFC. Please restore 
the header to valid comment lines (each line starting with `//`) without 
embedded `file //` fragments.
   ```suggestion
   // Licensed to the Apache Software Foundation (ASF) under one
   // or more contributor license agreements.  See the NOTICE file
   // distributed with this work for additional information
   // regarding copyright ownership.  The ASF licenses this file
   // to you 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.
   ```



##########
api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java:
##########
@@ -46,7 +46,6 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd {
     @Parameter(name = ApiConstants.GATEWAY_ID,
                type = CommandType.UUID,
                entityType = PrivateGatewayResponse.class,
-               required = true,
                description = "The gateway ID we are creating static route for. 
Mutually exclusive with the nexthop parameter")
     private Long gatewayId;

Review Comment:
   Removing `required=true` changes the API contract. If the intent is to allow 
either `gatewayId` or `nexthop`, the command should enforce (and clearly 
document) that exactly one of the two is provided (and fail with a clear error 
otherwise). As-is, clients may be able to submit requests without either 
parameter depending on other validations.



##########
ui/src/views/compute/wizard/MultiNetworkSelection.vue:
##########
@@ -1,31 +1,22 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you 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.
+// Licensed to the Apache Software Foundation (ASF) under one // or more 
contributor license agreements. See the NOTICE
+file // distributed with this work for additional information // regarding 
copyright ownership. The ASF licenses this
+file // to you 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.

Review Comment:
   Same issue as in ImportUnmanagedInstance.vue: the license header was 
reformatted into invalid non-comment text (notably line 5 begins with `Unless 
required...` without `//`). This will break compilation; please revert/restore 
the standard multi-line `//` header.
   ```suggestion
   // Licensed to the Apache Software Foundation (ASF) under one
   // or more contributor license agreements.  See the NOTICE file
   // distributed with this work for additional information
   // regarding copyright ownership.  The ASF licenses this file
   // to you 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.
   ```



##########
ui/src/views/compute/wizard/MultiNetworkSelection.vue:
##########
@@ -107,6 +103,10 @@ export default {
       type: String,
       default: ''
     },
+    projectid: {
+      type: String,
+      default: ''
+    },

Review Comment:
   The PR description focuses on project-based network filtering for unmanaged 
instance import, but this PR also includes broad unrelated changes across 
backend Java classes, schema views, API annotations, multiple unrelated UI 
pages, tests, and packaging files. This makes review and rollback riskier; 
please consider splitting these unrelated changes into separate PRs (or justify 
why they’re coupled to this fix).



##########
ui/src/views/compute/wizard/MultiNetworkSelection.vue:
##########
@@ -221,28 +224,37 @@ export default {
         params.domainid = this.domainid
         params.account = this.account
       }
-      getAPI('listNetworks', params).then(response => {
-        this.networks = response.listnetworksresponse.network || []
-      }).catch(() => {
-        this.networks = []
-      }).finally(() => {
-        this.orderNetworks()
-        this.loading = false
-      })
+      if (this.projectid) {
+        params.projectid = this.projectid
+      }

Review Comment:
   When `projectid` is provided, `domainid/account` are still sent if they’re 
populated. CloudStack list APIs commonly treat `projectid` as mutually 
exclusive with `domainid/account`, and sending both can lead to incorrect 
filtering or API errors. Fix by building params so that when `projectid` is set 
you omit `domainid` and `account` (and conversely only send `domainid/account` 
when `projectid` is empty).



##########
ui/src/views/tools/ImportUnmanagedInstance.vue:
##########
@@ -1088,226 +1207,253 @@ export default {
       var rootDisk = this.resource.disk[this.selectedRootDiskIndex]
       rootDisk.size = rootDisk.capacity / (1024 * 1024 * 1024)
       rootDisk.name = `${rootDisk.label} (${rootDisk.size} GB)`
-      rootDisk.meta = this.getMeta(rootDisk, { controller: 'controller', 
datastorename: 'datastore', position: 'position' })
+      rootDisk.meta = this.getMeta(rootDisk, {
+        controller: 'controller',
+        datastorename: 'datastore',
+        position: 'position'
+      })
       this.selectedRootDiskSources = [rootDisk]
     },
     handleSubmit (e) {
       e.preventDefault()
       if (this.loading) return
-      this.formRef.value.validate().then(() => {
-        const values = toRaw(this.form)
-        const params = {
-          name: this.resource.name,
-          clusterid: this.cluster.id,
-          displayname: values.displayname,
-          zoneid: this.zoneid,
-          importsource: this.importsource,
-          hypervisor: this.hypervisor,
-          host: this.exthost,
-          hostname: values.hostname,
-          username: this.username,
-          password: this.password,
-          hostid: this.host.id,
-          storageid: this.pool.id,
-          diskpath: this.diskpath,
-          temppath: this.tmppath
-        }
-        var importapi = 'importUnmanagedInstance'
-        if (this.isExternalImport || this.isDiskImport || 
this.selectedVmwareVcenter) {
-          importapi = 'importVm'
-          if (this.isDiskImport) {
-            if (!values.networkid) {
+      this.formRef.value
+        .validate()
+        .then(() => {
+          const values = toRaw(this.form)
+          const params = {
+            name: this.resource.name,
+            clusterid: this.cluster.id,
+            displayname: values.displayname,
+            zoneid: this.zoneid,
+            importsource: this.importsource,
+            hypervisor: this.hypervisor,
+            host: this.exthost,
+            hostname: values.hostname,
+            username: this.username,
+            password: this.password,
+            hostid: this.host.id,
+            storageid: this.pool.id,
+            diskpath: this.diskpath,
+            temppath: this.tmppath
+          }
+          var importapi = 'importUnmanagedInstance'
+          if (this.isExternalImport || this.isDiskImport || 
this.selectedVmwareVcenter) {
+            importapi = 'importVm'
+            if (this.isDiskImport) {
+              if (!values.networkid) {
+                this.$notification.error({
+                  message: this.$t('message.request.failed'),
+                  description: this.$t('message.please.enter.valid.value') + 
': ' + this.$t('label.network')
+                })
+                return
+              }
+              params.name = values.displayname
+              params.networkid = values.networkid
+            }
+          }
+          if (!this.computeOffering || !this.computeOffering.id) {
+            this.$notification.error({
+              message: this.$t('message.request.failed'),
+              description: this.$t('message.step.2.continue')
+            })
+            return
+          }
+          params.serviceofferingid = values.computeofferingid
+          if (this.computeOffering.iscustomized) {
+            var details = [this.cpuNumberKey, this.cpuSpeedKey, this.memoryKey]
+            for (var detail of details) {
+              if (!(values[detail] || this.computeOffering[detail])) {
+                this.$notification.error({
+                  message: this.$t('message.request.failed'),
+                  description:
+                    this.$t('message.please.enter.valid.value') + ': ' + 
this.$t('label.' + detail.toLowerCase())
+                })
+                return
+              }
+              if (values[detail]) {
+                params['details[0].' + detail] = values[detail]
+              }
+            }
+          }
+          if (this.computeOffering.iscustomizediops) {
+            var iopsDetails = [this.minIopsKey, this.maxIopsKey]
+            for (var iopsDetail of iopsDetails) {
+              if (!values[iopsDetail] || values[iopsDetail] < 0) {
+                this.$notification.error({
+                  message: this.$t('message.request.failed'),
+                  description:
+                    this.$t('message.please.enter.valid.value') + ': ' + 
this.$t('label.' + iopsDetail.toLowerCase())
+                })
+                return
+              }
+              params['details[0].' + iopsDetail] = values[iopsDetail]
+            }
+            if (values[this.minIopsKey] > values[this.maxIopsKey]) {
               this.$notification.error({
                 message: this.$t('message.request.failed'),
-                description: this.$t('message.please.enter.valid.value') + ': 
' + this.$t('label.network')
+                description: this.$t('error.form.message')
               })

Review Comment:
   The validation error for `minIops > maxIops` no longer stops submission. 
After showing the notification, the flow continues and will still call the 
import API. Add an early `return` after the notification so invalid IOPS values 
cannot proceed.
   ```suggestion
                 })
                 return
   ```



##########
ui/src/views/compute/wizard/SecurityGroupSelection.vue:
##########
@@ -140,9 +160,9 @@ export default {
   methods: {
     fetchData () {
       const params = {
-        projectid: this.$store.getters.project ? 
this.$store.getters.project.id : null,
-        domainid: this.$store.getters.project && 
this.$store.getters.project.id ? null : this.$store.getters.userInfo.domainid,
-        account: this.$store.getters.project && this.$store.getters.project.id 
? null : this.$store.getters.userInfo.account,
+        projectid: this.projectId || (this.$store.getters.project ? 
this.$store.getters.project.id : null),
+        domainid: this.projectId || (this.$store.getters.project && 
this.$store.getters.project.id) ? null : (this.domainId || 
this.$store.getters.userInfo.domainid),
+        account: this.projectId || (this.$store.getters.project && 
this.$store.getters.project.id) ? null : (this.account || 
this.$store.getters.userInfo.account),

Review Comment:
   The `domainid`/`account` ternary expressions rely on operator precedence 
(`||` with `?:`) and are hard to read/verify. Refactor to compute a boolean 
like `usingProject = Boolean(this.projectId || (this.$store.getters.project && 
this.$store.getters.project.id))` and then set `domainid/account` explicitly 
based on that. This will prevent subtle precedence mistakes and make future 
changes safer.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to