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

warren830 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new d7ab23559 fix(kiro): align connection status and PostgreSQL dashboards 
(#9082)
d7ab23559 is described below

commit d7ab23559d08e41a59d6638fc7a7f5402c126954
Author: Warren Chen <[email protected]>
AuthorDate: Tue Sep 1 22:32:32 2026 +0800

    fix(kiro): align connection status and PostgreSQL dashboards (#9082)
    
    * fix(kiro): report successful connection tests
    
    Signed-off-by: warren <[email protected]>
    
    * fix(grafana): repair PostgreSQL Kiro queries
    
    Signed-off-by: warren <[email protected]>
    
    ---------
    
    Signed-off-by: warren <[email protected]>
---
 backend/plugins/kiro/api/test_connection.go        | 23 ++++++---
 backend/plugins/kiro/api/test_connection_test.go   | 56 ++++++++++++++++++++++
 grafana/dashboards/postgresql/kiro_executive.json  | 10 ++--
 grafana/dashboards/postgresql/kiro_logging.json    | 14 +++---
 .../dashboards/postgresql/kiro_user_report.json    |  8 ++--
 5 files changed, 89 insertions(+), 22 deletions(-)

diff --git a/backend/plugins/kiro/api/test_connection.go 
b/backend/plugins/kiro/api/test_connection.go
index 8df055e58..de9fea373 100644
--- a/backend/plugins/kiro/api/test_connection.go
+++ b/backend/plugins/kiro/api/test_connection.go
@@ -95,6 +95,11 @@ func TestExistingConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResource
 // discovered accounts and per-stream object counts makes the difference 
visible
 // before any scope is created.
 type ConnectionReport struct {
+       // Success and Message satisfy the shared Config UI connection-test 
contract.
+       // The detailed fields below remain available to explain an empty 
export.
+       Success bool   `json:"success"`
+       Message string `json:"message"`
+
        ReportBucket    string `json:"reportBucket"`
        PromptLogBucket string `json:"promptLogBucket"`
        // Accounts are the AWS account ids found under the report prefix. An 
empty
@@ -111,18 +116,24 @@ type ConnectionReport struct {
 // verifying a path - only whether it is zero.
 const connectionReportCountLimit = 500
 
+func newConnectionReport(connection *models.KiroConnection) *ConnectionReport {
+       return &ConnectionReport{
+               Success:         true,
+               Message:         "success",
+               ReportBucket:    connection.Bucket,
+               PromptLogBucket: connection.GetPromptLogBucket(),
+               Accounts:        []string{},
+               Streams:         []tasks.StreamCount{},
+       }
+}
+
 // buildConnectionReport probes the layout and summarizes what was found.
 //
 // Errors are folded into the report rather than returned: the connection 
itself
 // is already known to work at this point, and a discovery failure is more 
useful
 // shown as an empty result with a hint than as a failed request.
 func buildConnectionReport(connection *models.KiroConnection) 
*ConnectionReport {
-       report := &ConnectionReport{
-               ReportBucket:    connection.Bucket,
-               PromptLogBucket: connection.GetPromptLogBucket(),
-               Accounts:        []string{},
-               Streams:         []tasks.StreamCount{},
-       }
+       report := newConnectionReport(connection)
 
        discovery, err := tasks.NewDiscovery(connection)
        if err != nil {
diff --git a/backend/plugins/kiro/api/test_connection_test.go 
b/backend/plugins/kiro/api/test_connection_test.go
new file mode 100644
index 000000000..7b30ca366
--- /dev/null
+++ b/backend/plugins/kiro/api/test_connection_test.go
@@ -0,0 +1,56 @@
+/*
+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.
+*/
+
+package api
+
+import (
+       "encoding/json"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+
+       "github.com/apache/incubator-devlake/plugins/kiro/models"
+)
+
+func TestNewConnectionReportSatisfiesConfigUIContract(t *testing.T) {
+       connection := &models.KiroConnection{
+               KiroConn: models.KiroConn{
+                       Bucket:          "report-bucket",
+                       PromptLogBucket: "log-bucket",
+               },
+       }
+
+       report := newConnectionReport(connection)
+       assert.True(t, report.Success)
+       assert.Equal(t, "success", report.Message)
+       assert.Equal(t, "report-bucket", report.ReportBucket)
+       assert.Equal(t, "log-bucket", report.PromptLogBucket)
+       assert.NotNil(t, report.Accounts)
+       assert.NotNil(t, report.Streams)
+
+       body, err := json.Marshal(report)
+       require.NoError(t, err)
+       assert.JSONEq(t, `{
+               "success": true,
+               "message": "success",
+               "reportBucket": "report-bucket",
+               "promptLogBucket": "log-bucket",
+               "accounts": [],
+               "streams": []
+       }`, string(body))
+}
diff --git a/grafana/dashboards/postgresql/kiro_executive.json 
b/grafana/dashboards/postgresql/kiro_executive.json
index 9a13ba801..f1a8711be 100644
--- a/grafana/dashboards/postgresql/kiro_executive.json
+++ b/grafana/dashboards/postgresql/kiro_executive.json
@@ -126,7 +126,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT COUNT(DISTINCT user_id) AS \"WAU\" FROM 
lake._tool_kiro_chat_log WHERE timestamp >= NOW() - INTERVAL '7 DAY'",
+          "rawSql": "SELECT COUNT(DISTINCT user_id) AS \"WAU\" FROM 
_tool_kiro_chat_log WHERE timestamp >= NOW() - INTERVAL '7 DAY'",
           "refId": "A"
         }
       ],
@@ -322,7 +322,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT ROUND(CAST(CAST(COUNT(DISTINCT CASE WHEN 
has_steering = TRUE THEN user_id END) AS NUMERIC) / 
NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0) * 100 AS DECIMAL), 0) AS 
\"Steering %\" FROM lake._tool_kiro_chat_log WHERE $__timeFilter(timestamp)",
+          "rawSql": "SELECT ROUND(CAST(CAST(COUNT(DISTINCT CASE WHEN 
has_steering = TRUE THEN user_id END) AS NUMERIC) / 
NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0) * 100 AS DECIMAL), 0) AS 
\"Steering %\" FROM _tool_kiro_chat_log WHERE $__timeFilter(timestamp)",
           "refId": "A"
         }
       ],
@@ -433,7 +433,7 @@
           "editorMode": "code",
           "format": "time_series",
           "rawQuery": true,
-          "rawSql": "SELECT TO_DATE(yw || ' Monday', 'IYYYIW FMDay') AS time, 
COUNT(DISTINCT user_id) AS \"Active Users\" FROM (SELECT user_id, 
(EXTRACT(ISOYEAR FROM timestamp) * 100 + EXTRACT(WEEK FROM timestamp))::int AS 
yw FROM lake._tool_kiro_chat_log WHERE $__timeFilter(timestamp)) AS t GROUP BY 
\"yw\" ORDER BY time NULLS FIRST",
+          "rawSql": "SELECT TO_DATE(yw || ' Monday', 'IYYYIW FMDay') AS time, 
COUNT(DISTINCT user_id) AS \"Active Users\" FROM (SELECT user_id, 
(EXTRACT(ISOYEAR FROM timestamp) * 100 + EXTRACT(WEEK FROM timestamp))::int AS 
yw FROM _tool_kiro_chat_log WHERE $__timeFilter(timestamp)) AS t GROUP BY 
\"yw\" ORDER BY time NULLS FIRST",
           "refId": "A"
         }
       ],
@@ -642,7 +642,7 @@
           "editorMode": "code",
           "format": "time_series",
           "rawQuery": true,
-          "rawSql": "SELECT date AS time, SUM(SUM(credits_used)) OVER (ORDER 
BY date NULLS FIRST) AS \"Cumulative Credits\", (SELECT CAST(SUM(credits_used) 
AS NUMERIC) / NULLIF(COUNT(DISTINCT date), 0) * EXTRACT(DAY FROM 
CAST(CAST(DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH' - INTERVAL '1 
DAY' AS DATE) AS DATE)) FROM lake._tool_kiro_user_report WHERE YEAR(CAST(date 
AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = 
MONTH(CAST(CURRENT_DATE AS DATE))) AS [...]
+          "rawSql": "SELECT date AS time, SUM(SUM(credits_used)) OVER (ORDER 
BY date NULLS FIRST) AS \"Cumulative Credits\", (SELECT CAST(SUM(credits_used) 
AS NUMERIC) / NULLIF(COUNT(DISTINCT date), 0) * EXTRACT(DAY FROM 
CAST(CAST(DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH' - INTERVAL '1 
DAY' AS DATE) AS DATE)) FROM _tool_kiro_user_report WHERE EXTRACT(YEAR FROM 
date) = EXTRACT(YEAR FROM CURRENT_DATE) AND EXTRACT(MONTH FROM date) = 
EXTRACT(MONTH FROM CURRENT_DATE)) AS \"Projec [...]
           "refId": "A"
         }
       ],
@@ -710,7 +710,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", 
MAX(subscription_tier) AS \"Tier\", ROUND(SUM(credits_used), 1) AS \"Total 
Credits Used\", MAX(date) AS \"Last Activity\" FROM lake._tool_kiro_user_report 
WHERE $__timeFilter(date) AND subscription_tier = 'POWER' GROUP BY \"user_id\" 
HAVING MAX(date) < NOW() - INTERVAL '14 DAY' ORDER BY MAX(date) NULLS FIRST",
+          "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", 
MAX(subscription_tier) AS \"Tier\", ROUND(SUM(credits_used), 1) AS \"Total 
Credits Used\", MAX(date) AS \"Last Activity\" FROM _tool_kiro_user_report 
WHERE $__timeFilter(date) AND subscription_tier = 'POWER' GROUP BY \"user_id\" 
HAVING MAX(date) < NOW() - INTERVAL '14 DAY' ORDER BY MAX(date) NULLS FIRST",
           "refId": "A"
         }
       ],
diff --git a/grafana/dashboards/postgresql/kiro_logging.json 
b/grafana/dashboards/postgresql/kiro_logging.json
index c0b4396cf..0e3f36b0b 100644
--- a/grafana/dashboards/postgresql/kiro_logging.json
+++ b/grafana/dashboards/postgresql/kiro_logging.json
@@ -171,7 +171,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT LPAD(CAST(hour_of_day AS TEXT), 2, '0') AS 
\"Hour\", SUM(chat_count) AS \"Chat Events\", SUM(completion_count) AS 
\"Completion Events\" FROM (SELECT EXTRACT(HOUR FROM timestamp) AS hour_of_day, 
COUNT(*) AS chat_count, 0 AS completion_count FROM lake._tool_kiro_chat_log 
WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) UNION ALL 
SELECT EXTRACT(HOUR FROM timestamp) AS hour_of_day, 0 AS chat_count, COUNT(*) 
AS completion_count FROM lake._tool_k [...]
+          "rawSql": "SELECT LPAD(CAST(hour_of_day AS TEXT), 2, '0') AS 
\"Hour\", SUM(chat_count) AS \"Chat Events\", SUM(completion_count) AS 
\"Completion Events\" FROM (SELECT EXTRACT(HOUR FROM timestamp) AS hour_of_day, 
COUNT(*) AS chat_count, 0 AS completion_count FROM _tool_kiro_chat_log WHERE 
$__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) UNION ALL SELECT 
EXTRACT(HOUR FROM timestamp) AS hour_of_day, 0 AS chat_count, COUNT(*) AS 
completion_count FROM _tool_kiro_comple [...]
           "refId": "A"
         }
       ],
@@ -245,7 +245,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT CASE WHEN chat_trigger_type = '' OR 
chat_trigger_type IS NULL THEN '(unknown)' ELSE chat_trigger_type END AS 
\"Trigger Type\", COUNT(*) AS \"Events\" FROM lake._tool_kiro_chat_log WHERE 
$__timeFilter(timestamp) GROUP BY \"chat_trigger_type\" ORDER BY COUNT(*) DESC 
NULLS LAST",
+          "rawSql": "SELECT CASE WHEN chat_trigger_type = '' OR 
chat_trigger_type IS NULL THEN '(unknown)' ELSE chat_trigger_type END AS 
\"Trigger Type\", COUNT(*) AS \"Events\" FROM _tool_kiro_chat_log WHERE 
$__timeFilter(timestamp) GROUP BY \"chat_trigger_type\" ORDER BY COUNT(*) DESC 
NULLS LAST",
           "refId": "A"
         }
       ],
@@ -393,7 +393,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' 
ELSE file_extension END AS \"File Type\", COUNT(*) AS \"Completions\" FROM 
lake._tool_kiro_completion_log WHERE $__timeFilter(timestamp) GROUP BY 
\"file_extension\" ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15",
+          "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' 
ELSE file_extension END AS \"File Type\", COUNT(*) AS \"Completions\" FROM 
_tool_kiro_completion_log WHERE $__timeFilter(timestamp) GROUP BY 
\"file_extension\" ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15",
           "refId": "A"
         }
       ],
@@ -589,7 +589,7 @@
           "editorMode": "code",
           "format": "time_series",
           "rawQuery": true,
-          "rawSql": "SELECT time, SUM(chat) AS \"Chat Events\", 
SUM(completions) AS \"Completion Events\" FROM (SELECT CAST(timestamp AS DATE) 
AS time, COUNT(*) AS chat, 0 AS completions FROM lake._tool_kiro_chat_log WHERE 
$__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) UNION ALL SELECT 
CAST(timestamp AS DATE) AS time, 0 AS chat, COUNT(*) AS completions FROM 
lake._tool_kiro_completion_log WHERE $__timeFilter(timestamp) GROUP BY 
CAST(timestamp AS DATE)) AS combined GROUP BY time [...]
+          "rawSql": "SELECT time, SUM(chat) AS \"Chat Events\", 
SUM(completions) AS \"Completion Events\" FROM (SELECT CAST(timestamp AS DATE) 
AS time, COUNT(*) AS chat, 0 AS completions FROM _tool_kiro_chat_log WHERE 
$__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) UNION ALL SELECT 
CAST(timestamp AS DATE) AS time, 0 AS chat, COUNT(*) AS completions FROM 
_tool_kiro_completion_log WHERE $__timeFilter(timestamp) GROUP BY 
CAST(timestamp AS DATE)) AS combined GROUP BY time ORDER BY  [...]
           "refId": "A"
         }
       ],
@@ -731,7 +731,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 
END) AS \"Using Steering\", SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 
END) AS \"Using Spec Mode\", SUM(CASE WHEN has_steering = FALSE AND 
is_spec_mode = FALSE THEN 1 ELSE 0 END) AS \"Plain Chat\" FROM 
lake._tool_kiro_chat_log WHERE $__timeFilter(timestamp)",
+          "rawSql": "SELECT SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 
END) AS \"Using Steering\", SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 
END) AS \"Using Spec Mode\", SUM(CASE WHEN has_steering = FALSE AND 
is_spec_mode = FALSE THEN 1 ELSE 0 END) AS \"Plain Chat\" FROM 
_tool_kiro_chat_log WHERE $__timeFilter(timestamp)",
           "refId": "A"
         }
       ],
@@ -977,7 +977,7 @@
           "editorMode": "code",
           "format": "time_series",
           "rawQuery": true,
-          "rawSql": "SELECT CAST(timestamp AS DATE) AS time, 
AVG(prompt_length) AS \"Avg Prompt Length\", AVG(response_length) AS \"Avg 
Response Length\", MAX(prompt_length) AS \"Max Prompt Length\", 
MAX(response_length) AS \"Max Response Length\" FROM lake._tool_kiro_chat_log 
WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY 
CAST(timestamp AS DATE) NULLS FIRST",
+          "rawSql": "SELECT CAST(timestamp AS DATE) AS time, 
AVG(prompt_length) AS \"Avg Prompt Length\", AVG(response_length) AS \"Avg 
Response Length\", MAX(prompt_length) AS \"Max Prompt Length\", 
MAX(response_length) AS \"Max Response Length\" FROM _tool_kiro_chat_log WHERE 
$__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY 
CAST(timestamp AS DATE) NULLS FIRST",
           "refId": "A"
         }
       ],
@@ -1074,7 +1074,7 @@
           "editorMode": "code",
           "format": "time_series",
           "rawQuery": true,
-          "rawSql": "SELECT CAST(timestamp AS DATE) AS time, 
ROUND(AVG(left_context_length)) AS \"Avg Left Context\", 
ROUND(AVG(right_context_length)) AS \"Avg Right Context\", 
ROUND(AVG(left_context_length + right_context_length)) AS \"Avg Total Context\" 
FROM lake._tool_kiro_completion_log WHERE $__timeFilter(timestamp) GROUP BY 
CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST",
+          "rawSql": "SELECT CAST(timestamp AS DATE) AS time, 
ROUND(AVG(left_context_length)) AS \"Avg Left Context\", 
ROUND(AVG(right_context_length)) AS \"Avg Right Context\", 
ROUND(AVG(left_context_length + right_context_length)) AS \"Avg Total Context\" 
FROM _tool_kiro_completion_log WHERE $__timeFilter(timestamp) GROUP BY 
CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST",
           "refId": "A"
         }
       ],
diff --git a/grafana/dashboards/postgresql/kiro_user_report.json 
b/grafana/dashboards/postgresql/kiro_user_report.json
index 4f720f1aa..8f257a6a8 100644
--- a/grafana/dashboards/postgresql/kiro_user_report.json
+++ b/grafana/dashboards/postgresql/kiro_user_report.json
@@ -76,7 +76,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT SUM(credits_used) AS \"Total Credits Used\", 
COUNT(DISTINCT user_id) AS \"Active Users\", SUM(total_messages) AS \"Total 
Messages\", SUM(chat_conversations) AS \"Total Conversations\" FROM 
lake._tool_kiro_user_report WHERE $__timeFilter(date)",
+          "rawSql": "SELECT SUM(credits_used) AS \"Total Credits Used\", 
COUNT(DISTINCT user_id) AS \"Active Users\", SUM(total_messages) AS \"Total 
Messages\", SUM(chat_conversations) AS \"Total Conversations\" FROM 
_tool_kiro_user_report WHERE $__timeFilter(date)",
           "refId": "A"
         }
       ],
@@ -174,7 +174,7 @@
           "editorMode": "code",
           "format": "time_series",
           "rawQuery": true,
-          "rawSql": "SELECT date AS time, subscription_tier AS metric, 
SUM(credits_used) AS value FROM lake._tool_kiro_user_report WHERE 
$__timeFilter(date) GROUP BY \"date\", \"subscription_tier\" ORDER BY date 
NULLS FIRST",
+          "rawSql": "SELECT date AS time, subscription_tier AS metric, 
SUM(credits_used) AS value FROM _tool_kiro_user_report WHERE 
$__timeFilter(date) GROUP BY \"date\", \"subscription_tier\" ORDER BY date 
NULLS FIRST",
           "refId": "A"
         }
       ],
@@ -346,7 +346,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT subscription_tier AS \"Tier\", COUNT(DISTINCT 
user_id) AS \"Users\" FROM lake._tool_kiro_user_report WHERE 
$__timeFilter(date) AND NOT subscription_tier IS NULL AND subscription_tier <> 
'' GROUP BY \"subscription_tier\" ORDER BY COUNT(DISTINCT user_id) DESC NULLS 
LAST",
+          "rawSql": "SELECT subscription_tier AS \"Tier\", COUNT(DISTINCT 
user_id) AS \"Users\" FROM _tool_kiro_user_report WHERE $__timeFilter(date) AND 
NOT subscription_tier IS NULL AND subscription_tier <> '' GROUP BY 
\"subscription_tier\" ORDER BY COUNT(DISTINCT user_id) DESC NULLS LAST",
           "refId": "A"
         }
       ],
@@ -463,7 +463,7 @@
           "editorMode": "code",
           "format": "table",
           "rawQuery": true,
-          "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", 
subscription_tier AS \"Tier\", client_type AS \"Client\", SUM(credits_used) AS 
\"Credits Used\", SUM(total_messages) AS \"Messages\", SUM(chat_conversations) 
AS \"Conversations\", SUM(overage_credits_used) AS \"Overage Credits\", CASE 
WHEN MAX(CAST(overage_enabled AS BIGINT)) = 1 THEN 'Yes' ELSE 'No' END AS 
\"Overage\", MIN(date) AS \"First Activity\", MAX(date) AS \"Last Activity\" 
FROM lake._tool_kiro_user_re [...]
+          "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", 
subscription_tier AS \"Tier\", client_type AS \"Client\", SUM(credits_used) AS 
\"Credits Used\", SUM(total_messages) AS \"Messages\", SUM(chat_conversations) 
AS \"Conversations\", SUM(overage_credits_used) AS \"Overage Credits\", CASE 
WHEN BOOL_OR(overage_enabled) THEN 'Yes' ELSE 'No' END AS \"Overage\", 
MIN(date) AS \"First Activity\", MAX(date) AS \"Last Activity\" FROM 
_tool_kiro_user_report WHERE $__timeFil [...]
           "refId": "A"
         }
       ],

Reply via email to