1+ package com .testsigma .addons .web ;
2+
3+ import com .fasterxml .jackson .databind .JsonNode ;
4+ import com .fasterxml .jackson .databind .ObjectMapper ;
5+ import com .fasterxml .jackson .databind .node .ArrayNode ;
6+ import com .fasterxml .jackson .databind .node .ObjectNode ;
7+ import com .testsigma .sdk .ApplicationType ;
8+ import com .testsigma .sdk .Result ;
9+ import com .testsigma .sdk .WebAction ;
10+ import com .testsigma .sdk .annotation .Action ;
11+ import com .testsigma .sdk .annotation .TestData ;
12+ import io .restassured .RestAssured ;
13+ import io .restassured .response .Response ;
14+ import lombok .Data ;
15+ import lombok .EqualsAndHashCode ;
16+ import org .apache .commons .lang3 .exception .ExceptionUtils ;
17+
18+ import java .util .ArrayList ;
19+ import java .util .Iterator ;
20+ import java .util .List ;
21+ import java .util .NoSuchElementException ;
22+
23+ @ Data
24+ @ EqualsAndHashCode (callSuper = false )
25+ @ Action (
26+ actionText = "Create or update row row-name in test data profile test-data-id with columns-data (Eg:{\" Column1\" : \" value1\" , \" Column2\" : \" value2\" } using api key api-key" ,
27+ description = "Creates or updates a row in a Testsigma Test Data Profile using a JSON object of column-value pairs. New columns are also created automatically if they do not already exist in the profile." ,
28+ applicationType = ApplicationType .WEB
29+ )
30+ public class TdpRowColumnCreatorUpdaterAction extends WebAction {
31+
32+ private static final String TESTSIGMA_API_BASE_URL = "https://app.testsigma.com/api/v1/test_data/" ;
33+
34+ @ TestData (reference = "test-data-id" )
35+ private com .testsigma .sdk .TestData testDataId ;
36+
37+ @ TestData (reference = "row-name" )
38+ private com .testsigma .sdk .TestData rowName ;
39+
40+ // JSON string: {"Column1": "value1", "Column2": "value2"}
41+ @ TestData (reference = "columns-data" )
42+ private com .testsigma .sdk .TestData columnsData ;
43+
44+ @ TestData (reference = "api-key" )
45+ private com .testsigma .sdk .TestData apiKey ;
46+
47+ @ Override
48+ public Result execute () throws NoSuchElementException {
49+ try {
50+ String tdpId = testDataId .getValue ().toString ().trim ();
51+ String row = rowName .getValue ().toString ().trim ();
52+ String rawJson = columnsData .getValue ().toString ().trim ();
53+ String token = apiKey .getValue ().toString ().trim ();
54+ String apiUrl = TESTSIGMA_API_BASE_URL + tdpId ;
55+
56+ logger .info ("===== INPUT =====" );
57+ logger .info ("TDP ID: " + tdpId );
58+ logger .info ("Row: " + row );
59+ logger .info ("Columns Data: " + rawJson );
60+
61+ ObjectMapper mapper = new ObjectMapper ();
62+ JsonNode inputData = mapper .readTree (rawJson );
63+ if (!inputData .isObject ()) {
64+ setErrorMessage ("columns-data must be a JSON object, e.g. {\" Column1\" : \" value1\" }" );
65+ return Result .FAILED ;
66+ }
67+
68+ // ===== GET =====
69+ logger .info ("\n ===== GET REQUEST =====" );
70+ Response getResponse = RestAssured .given ()
71+ .baseUri (apiUrl )
72+ .header ("Authorization" , "Bearer " + token )
73+ .header ("Accept" , "application/json" )
74+ .header ("Content-Type" , "application/json;charset=UTF-8" )
75+ .get ();
76+
77+ logger .info ("GET Status: " + getResponse .getStatusCode ());
78+ logger .info ("GET Body: " + getResponse .getBody ().asString ());
79+
80+ if (getResponse .getStatusCode () != 200 ) {
81+ setErrorMessage ("GET failed: " + getResponse .getBody ().asString ());
82+ return Result .FAILED ;
83+ }
84+
85+ JsonNode root = mapper .readTree (getResponse .getBody ().asString ());
86+ String testDataName = root .path ("testDataName" ).asText ();
87+
88+ // ===== Columns: merge existing + any new from input =====
89+ ArrayNode existingColumns = (ArrayNode ) root .path ("columns" );
90+ List <String > columnsList = new ArrayList <>();
91+ for (JsonNode col : existingColumns ) {
92+ columnsList .add (col .asText ());
93+ }
94+ for (Iterator <String > it = inputData .fieldNames (); it .hasNext (); ) {
95+ String colName = it .next ();
96+ if (!columnsList .contains (colName )) {
97+ logger .info ("Adding new column: " + colName );
98+ columnsList .add (colName );
99+ }
100+ }
101+
102+ // ===== Rows =====
103+ ArrayNode existingData = (ArrayNode ) root .path ("data" );
104+ ArrayNode updatedData = mapper .createArrayNode ();
105+ boolean rowFound = false ;
106+
107+ for (JsonNode rowNode : existingData ) {
108+ ObjectNode updatedRow = mapper .createObjectNode ();
109+ if (rowNode .has ("id" )) {
110+ updatedRow .put ("id" , rowNode .get ("id" ).asLong ());
111+ }
112+
113+ String currentRowName = rowNode .get ("name" ).asText ();
114+ updatedRow .put ("name" , currentRowName );
115+
116+ ObjectNode rowData = mapper .createObjectNode ();
117+ rowNode .get ("data" ).fields ().forEachRemaining (e ->
118+ rowData .put (e .getKey (), e .getValue ().asText ())
119+ );
120+
121+ if (currentRowName .equals (row )) {
122+ logger .info ("Updating existing row: " + row );
123+ inputData .fields ().forEachRemaining (e -> rowData .put (e .getKey (), e .getValue ().asText ()));
124+ rowFound = true ;
125+ } else {
126+ // fill any newly added columns with empty string for other rows
127+ inputData .fieldNames ().forEachRemaining (colName -> {
128+ if (!rowData .has (colName )) rowData .put (colName , "" );
129+ });
130+ }
131+
132+ updatedRow .set ("data" , rowData );
133+ updatedData .add (updatedRow );
134+ }
135+
136+ if (!rowFound ) {
137+ logger .info ("Row not found, creating new row: " + row );
138+ ObjectNode newRow = mapper .createObjectNode ();
139+ newRow .put ("name" , row );
140+ ObjectNode newRowData = mapper .createObjectNode ();
141+ for (String col : columnsList ) {
142+ JsonNode inputVal = inputData .get (col );
143+ newRowData .put (col , inputVal != null ? inputVal .asText () : "" );
144+ }
145+ newRow .set ("data" , newRowData );
146+ updatedData .add (newRow );
147+ }
148+
149+ // ===== Payload =====
150+ ObjectNode payload = mapper .createObjectNode ();
151+ payload .put ("testDataName" , testDataName );
152+ payload .set ("data" , updatedData );
153+ ArrayNode columnsArray = mapper .createArrayNode ();
154+ columnsList .forEach (columnsArray ::add );
155+ payload .set ("columns" , columnsArray );
156+
157+ String payloadJson = mapper .writeValueAsString (payload );
158+ logger .info ("\n ===== PUT PAYLOAD =====" );
159+ logger .info (payloadJson );
160+
161+ // ===== PUT =====
162+ logger .info ("\n ===== PUT REQUEST =====" );
163+ Response putResponse = RestAssured .given ()
164+ .baseUri (apiUrl )
165+ .header ("Authorization" , "Bearer " + token )
166+ .header ("Accept" , "application/json" )
167+ .header ("Content-Type" , "application/json;charset=UTF-8" )
168+ .body (payloadJson )
169+ .put ();
170+
171+ logger .info ("PUT Status: " + putResponse .getStatusCode ());
172+ logger .info ("PUT Body: " + putResponse .getBody ().asString ());
173+
174+ if (putResponse .getStatusCode () != 200 ) {
175+ setErrorMessage ("PUT failed: " + putResponse .getBody ().asString ());
176+ return Result .FAILED ;
177+ }
178+
179+ String successMsg = "SUCCESS: Updated row '" + row + "' with " + rawJson ;
180+ logger .info (successMsg );
181+ setSuccessMessage (successMsg );
182+ return Result .SUCCESS ;
183+
184+ } catch (Exception e ) {
185+ logger .warn ("Exception Occurred: " + ExceptionUtils .getMessage (e ));
186+ setErrorMessage ("Exception: " + ExceptionUtils .getMessage (e ));
187+ return Result .FAILED ;
188+ }
189+ }
190+ }
0 commit comments