From da7d576326f6c49d884582e1639604d3e946b555 Mon Sep 17 00:00:00 2001 From: Ian Roy Sacbibit Date: Mon, 2 Mar 2026 16:26:32 -0500 Subject: [PATCH 1/4] deduplicate properties by name --- .../convert_tags_to_custom_properties.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py b/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py index db49eb4..6678a40 100644 --- a/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py +++ b/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py @@ -100,8 +100,23 @@ def fetch_custom_properties(): return properties +def deduplicate_properties_by_name(properties): + """ + Return properties with duplicate names removed, keeping the first occurrence. + """ + seen_names = set() + deduplicated = [] + for prop in properties: + name = prop["name"] + if name not in seen_names: + seen_names.add(name) + deduplicated.append(prop) + return deduplicated + + def main(): properties = fetch_custom_properties() + properties = deduplicate_properties_by_name(properties) for index, property_info in enumerate(properties, 1): property_name = property_info["name"] From c77324592b2890f0056d2af11a589255393e1f8d Mon Sep 17 00:00:00 2001 From: Ian Roy Sacbibit Date: Thu, 5 Mar 2026 16:28:55 -0500 Subject: [PATCH 2/4] deduplicate by alias instead of name --- .../convert_tags_to_custom_properties.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py b/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py index 6678a40..597b78b 100644 --- a/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py +++ b/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py @@ -100,23 +100,26 @@ def fetch_custom_properties(): return properties -def deduplicate_properties_by_name(properties): +def deduplicate_properties_by_alias(properties): """ - Return properties with duplicate names removed, keeping the first occurrence. + Return properties with duplicate aliases removed, keeping the first occurrence. + Uses the first alias from each property's aliases list. """ - seen_names = set() + seen_aliases = set() deduplicated = [] for prop in properties: - name = prop["name"] - if name not in seen_names: - seen_names.add(name) + alias = prop["aliases"][0] if prop.get("aliases") else None + if alias is not None and alias not in seen_aliases: + seen_aliases.add(alias) deduplicated.append(prop) + elif alias is None: + deduplicated.append(prop) # Keep properties with no aliases return deduplicated def main(): properties = fetch_custom_properties() - properties = deduplicate_properties_by_name(properties) + properties = deduplicate_properties_by_alias(properties) for index, property_info in enumerate(properties, 1): property_name = property_info["name"] From 8fb036fc11dd6a3ca3c217e598adb72921225bd7 Mon Sep 17 00:00:00 2001 From: Ian Roy Sacbibit Date: Mon, 23 Mar 2026 13:38:18 -0400 Subject: [PATCH 3/4] use alias instead of aliases --- .../convert_tags_to_custom_properties.py | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py b/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py index 597b78b..8e518a3 100644 --- a/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py +++ b/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py @@ -15,7 +15,7 @@ } nodes { name - aliases + alias schema id } @@ -102,18 +102,16 @@ def fetch_custom_properties(): def deduplicate_properties_by_alias(properties): """ - Return properties with duplicate aliases removed, keeping the first occurrence. - Uses the first alias from each property's aliases list. + Return properties with duplicate alias values removed, keeping the first occurrence. + Uses the singular `alias` field from each property definition. """ seen_aliases = set() deduplicated = [] for prop in properties: - alias = prop["aliases"][0] if prop.get("aliases") else None - if alias is not None and alias not in seen_aliases: + alias = prop["alias"] + if alias not in seen_aliases: seen_aliases.add(alias) deduplicated.append(prop) - elif alias is None: - deduplicated.append(prop) # Keep properties with no aliases return deduplicated @@ -123,14 +121,14 @@ def main(): for index, property_info in enumerate(properties, 1): property_name = property_info["name"] - property_alias = property_info["aliases"][0] # Only the first alias + property_alias = property_info["alias"] property_schema_type = property_info["schema"]["type"] print(f"{index}. Property Name: {property_name}, Alias: {property_alias}, Schema Type: {property_schema_type}") selected_index = int(input("Select a property by entering its index (only booleans and arrays are supported at this time): ")) - 1 if 0 <= selected_index < len(properties): selected_property = properties[selected_index] - print(f"You selected: {selected_property['name']} with alias: {selected_property['aliases'][0]} and schema type: {selected_property['schema']['type']}") + print(f"You selected: {selected_property['name']} with alias: {selected_property['alias']} and schema type: {selected_property['schema']['type']}") #TODO: Add support for other schema types if needed, such as: # - text @@ -156,7 +154,7 @@ def execute_boolean_mutation(property_info): while has_next_page: # Execute the GraphQL query using the selected alias as the tag key response_services_by_tag = opslevel_graphql_query( - SERVICES_BY_TAG_QUERY, variables={"endCursor": cursor, "tag_key": property_info["aliases"][0]} + SERVICES_BY_TAG_QUERY, variables={"endCursor": cursor, "tag_key": property_info["alias"]} ) # Loop through the service nodes to confirm the key in tags nodes matches the selected alias @@ -164,12 +162,12 @@ def execute_boolean_mutation(property_info): for service in services: tags = service["tags"]["nodes"] for tag in tags: - if tag["key"] == property_info["aliases"][0]: + if tag["key"] == property_info["alias"]: print(f"Service ID: {service['id']} has the selected alias as a tag.") # Execute the mutation query response = opslevel_graphql_query( UPDATE_PROPERTY_MUTATION, - variables={"service_id": service["id"], "definition_alias": property_info["aliases"][0], "value": tag["value"]} + variables={"service_id": service["id"], "definition_alias": property_info["alias"], "value": tag["value"]} ) # # Process the mutation response if needed print("Bool mutation executed.") @@ -186,20 +184,20 @@ def execute_boolean_mutation(property_info): def execute_array_mutation(property_info): # Execute the GraphQL query using the selected alias as the tag key response_services_by_tag = opslevel_graphql_query( - SERVICES_BY_TAG_QUERY, variables={"endCursor": None, "tag_key": property_info["aliases"][0]} + SERVICES_BY_TAG_QUERY, variables={"endCursor": None, "tag_key": property_info["alias"]} ) # Loop through the service nodes to confirm the key in tags nodes matches the selected alias services = response_services_by_tag["data"]["account"]["services"]["nodes"] for service in services: tags = service["tags"]["nodes"] - array_values = [tag_node["value"] for tag_node in tags if tag_node["key"] == property_info["aliases"][0]] + array_values = [tag_node["value"] for tag_node in tags if tag_node["key"] == property_info["alias"]] # Convert array values to JSON string array_values_json = json.dumps(array_values) # Execute the mutation query response = opslevel_graphql_query( UPDATE_PROPERTY_MUTATION, - variables={"service_id": service["id"], "definition_alias": property_info["aliases"][0], "value": array_values_json} + variables={"service_id": service["id"], "definition_alias": property_info["alias"], "value": array_values_json} ) # Process the mutation response if needed print("Array mutation executed.") From 2f40be3912dbd3ac6993353c9e9056800c68f63c Mon Sep 17 00:00:00 2001 From: Ian Roy Sacbibit Date: Mon, 23 Mar 2026 13:43:03 -0400 Subject: [PATCH 4/4] implement review suggestions --- .../convert_tags_to_custom_properties.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py b/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py index 8e518a3..6a13758 100644 --- a/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py +++ b/scripts/convert_tags_to_custom_properties/convert_tags_to_custom_properties.py @@ -108,7 +108,9 @@ def deduplicate_properties_by_alias(properties): seen_aliases = set() deduplicated = [] for prop in properties: - alias = prop["alias"] + alias = prop.get("alias") + if alias is None: + continue if alias not in seen_aliases: seen_aliases.add(alias) deduplicated.append(prop) @@ -172,14 +174,14 @@ def execute_boolean_mutation(property_info): # # Process the mutation response if needed print("Bool mutation executed.") print(response) - else: - print("No other services found with tags matching the boolean key. Completed!") # Check if there are more pages services_page_info = response_services_by_tag["data"]["account"]["services"]["pageInfo"] has_next_page = services_page_info["hasNextPage"] cursor = services_page_info["endCursor"] + print("No other services found with tags matching the boolean key. Completed!") + def execute_array_mutation(property_info): # Execute the GraphQL query using the selected alias as the tag key