diff --git a/Documents/NMT/Live Doc Sample/Live_Docs_Overview.md b/Documents/NMT/Live Doc Sample/Live_Docs_Overview.md new file mode 100644 index 0000000..7e8b972 --- /dev/null +++ b/Documents/NMT/Live Doc Sample/Live_Docs_Overview.md @@ -0,0 +1,74 @@ +# Live Docs Sample - Overview + +## Table of Contents + +- [Live Docs Sample - Overview](#live-docs-sample---overview) + - [Table of Contents](#table-of-contents) + - [Tool Description](#tool-description) + - [How to use the tool](#how-to-use-the-tool) + +--- + +## Tool Description + +The **Live Docs Sample** is an interactive plugin that allows users to execute API functions with custom parameters from various IQGeo modules (Structure, Equipment, Conduit, Cable, Connection, and Circuit) directly from a modal interface in NMT. + +Key features: + +- **Function Selection:** Choose a class and a function from the available API menus. +- **Dynamic Parameter Inputs:** Input fields are automatically rendered based on the selected function’s parameter types (numbers, strings, booleans, arrays, features, transactions, or pin ranges). +- **Map Integration:** For feature-based parameters, users can select objects directly on the map. +- **Automatic Transaction Handling:** Transaction parameters are automatically created and committed after execution. +- **Real-Time Execution:** Functions are executed immediately with user-provided inputs, and results are logged in the console. +- **Support for Complex Types:** Handles nested arrays, objects, GeoJSON, and custom PinRange types. + +This tool is intended for those who want an **interactive way to use API functionality** without writing custom scripts. + +--- + +## How to use the tool + +1. **Open the Live Docs Modal** + + Open the `LiveDocsPlugin` in the application. The modal will show an introductory description of the plugin. Click **OK** to proceed. + +2. **Select a Class and Function** + + - Use the first dropdown to select a class (e.g., `Structure API`, `Equipment API`, etc.). + - After selecting a class, a second dropdown will display available functions for that class. + - Selecting a function dynamically generates input fields for its parameters. + + ![Class Selection](./Live_Docs_Overview_1.png) +

Fig. 2: Class dropdown menus + + ![Function Selection](./Live_Docs_Overview_2.png) +

Fig. 3: Function dropdown menus + +3. **Fill in Parameter Values** + + - Input fields adjust based on parameter types: + + - **Number / String / Boolean:** Standard input or dropdown. + - **Feature / Array of Features:** Click on map objects to populate. + - **Array of Numbers / Array of Arrays:** Click on features to input coordinates. + - **Objects / JSON / GeoJSON:** Enter JSON directly into a text area. + - **PinRange:** Select side and define low/high pin numbers. + - **Transaction:** Automatically created; no user input required. + + - Ensure all required parameters are filled to enable the **Execute** button. + - Description of the function, parameters and return value is provided. + + ![Parameter Input Fields](./Live_Docs_Overview_3.png) + +

Fig. 3: Example of parameter inputs for `validateRoutesForConduit` function. + +4. **Execute the Function** + + - Click the **Execute** button to run the selected function with the provided parameters. + - Function execution logs results in the console. + - Asynchronous functions will return results after their promise resolves. + +5. **Cancel or Exit** + + - Click the **Cancel** button to close the modal without executing any function. + - Switching between classes or functions resets parameter inputs automatically. diff --git a/Documents/NMT/Live Doc Sample/Live_Docs_Overview_1.png b/Documents/NMT/Live Doc Sample/Live_Docs_Overview_1.png new file mode 100644 index 0000000..8129c3f Binary files /dev/null and b/Documents/NMT/Live Doc Sample/Live_Docs_Overview_1.png differ diff --git a/Documents/NMT/Live Doc Sample/Live_Docs_Overview_2.png b/Documents/NMT/Live Doc Sample/Live_Docs_Overview_2.png new file mode 100644 index 0000000..719b32b Binary files /dev/null and b/Documents/NMT/Live Doc Sample/Live_Docs_Overview_2.png differ diff --git a/Documents/NMT/Live Doc Sample/Live_Docs_Overview_3.png b/Documents/NMT/Live Doc Sample/Live_Docs_Overview_3.png new file mode 100644 index 0000000..7aa06b7 Binary files /dev/null and b/Documents/NMT/Live Doc Sample/Live_Docs_Overview_3.png differ diff --git a/Documents/Reporting_Samples/Reporting_Samples_1.png b/Documents/Reporting_Samples/Reporting_Samples_1.png new file mode 100644 index 0000000..0dc3d91 Binary files /dev/null and b/Documents/Reporting_Samples/Reporting_Samples_1.png differ diff --git a/Documents/Reporting_Samples/Reporting_Samples_DeepDive.md b/Documents/Reporting_Samples/Reporting_Samples_DeepDive.md new file mode 100644 index 0000000..52fd000 --- /dev/null +++ b/Documents/Reporting_Samples/Reporting_Samples_DeepDive.md @@ -0,0 +1,576 @@ +# Reporting Samples - Code Deep Dive + +## Table of Contents + +## Table of Contents + +- [Reporting Samples - Code Deep Dive](#rest-apis---code-deep-dive) + - [Table of Contents](#table-of-contents) + - [Tool Description](#tool-description) + - [Tool files](#tool-files) + - [How the tool works](#how-the-tool-works) + - [utils\.py](#utilspy) + - [conduit\_capacity\.py](#conduit_capacitypy) + - [pole\_attachment\.py](#pole_attachmentpy) + - [spatial\_query\.py](#spatial_querypy) +--- + + +## Tool Description + +This sample provides examples for how to query the database and create reports similar to the Enterprise "Data Warehouse" reports using the available Platform REST APIs. These examples are designed for Professional customers to: + +- Authenticate via **RPOC auth** or **JWT auth** to get started +- Query feature data in various ways to mimic "Data Warehouse" reporting + + +The goal is to make REST API–based reporting as straightforward as possible, even without a replica database. All examples are written in **Python** with examples for conduit capacity, pole attachment, and spatial querying reports. + +As a stretch goal, we may also provide **code-free options** using tools like **FME** or **Postman**, to give Professional Edition customers an experience closer to Enterprise’s no-code “Data Warehouse” reporting workflows. + +Use this sample as a reference if you want to: + +- Understand spatial query workflows. +- Extend results handling into custom reporting or UI logic. +- Experiment quickly with geometry-based searches before building larger tools. + +## Tool Files + +- `utils.py` - Python lib helper file +- `conduit_capacity.py` - Reporting example for conduit capacity +- `pole_attachment.py` - Reporting example for pole attachment +- `spatial_query.py` - Reporting example for querying geometry with tolerance + +All files are located in the `modules/custom/public/js/Samples/reporting_samples` folder + +## How the tool works + +In this section we will go over the tool source code describing how it works. + +### utils.py + +The code starts with the relevant `import` statements. Make sure your Python environment has the libraries installed. + +``` +import requests +from pathlib import Path +``` + +- The `requests` library is used to send the HTTP requests to the server +- the `Path` liobrary is used to input the `token.txt` file for JWT authentication + +Next, some global variables are created + +```BASE_URL = "http://host.docker.internal" +# BASE_URL = "http://localhost" +LOGIN_URL = f"{BASE_URL}/auth" +SESSION = requests.Session() +HEADERS = {} +``` + +- `BASE_URL` is the URL for the requests. If you are running inside a docker container, the base URL will be `http://host.docker.internal`. Otherwise, the base URl is `localhost` +- `LOGIN_URL` is the URL for authentication +- `SESSION` is called to ensure cookie persistence. The authentication request will return a cookie that needs to be used in the following requests +- `HEADERS` will be passed through to the reporting samples as after a cookie is recieved, it must be added to all subsequent requests + + +First, we have the two authentication helper functions. Below is the JWT authentication helper function. + +``` +def iqgeo_jwt_auth(token_file: Path): + """ + Prompt user for JWT token and then set the auth header. + """ + if not token_file.exists(): + raise FileNotFoundError(f"Token file not found: {token_file}") + + token = token_file.read_text().strip() + response = SESSION.post(LOGIN_URL, data={"id_token": token}) + response.raise_for_status() + cookies = response.cookies.get_dict() + HEADERS[ + "cookie" + ] = f"myworldapp={cookies['myworldapp']}; csrf_token={cookies['csrf_token']}" +``` +- This function takes in the `token.txt` as a parameter to authenticate using the unique token. +- It then send a POST request using the data inside the token file and the `LOGIN_URL`, takes the response cookie, and sets the cookie in the `HEADER` + + +Next authentication helper function is the ROPC authentication function. + +``` +def iqgeo_interactive_ropc_auth(): + """ + Prompt user for credentials and then send AUTH request. + Raises exception if not authorized, returns the auth cookie on success. + """ + user = input("Enter username: ") + password = input("Enter password: ") + + uauth = {"user": user, "pass": password} + + response = SESSION.post(LOGIN_URL, data=uauth) + response.raise_for_status() + cookies = response.cookies.get_dict() + HEADERS[ + "cookie" + ] = f"myworldapp={cookies['myworldapp']}; csrf_token={cookies['csrf_token']}" +``` +- This function is similar to the JWT, however this time, we prompt the user for a username and password to authenticate. +- The inputted data, `LOGIN_URL` is sent with a POST request to recieve the cookie in response, which is then set in the `HEADER` + + +The next few functions are used by the individual examples to build off of. + +``` +def get_all_features(feature_type, design=None): + """Get all features of a specific type in the design""" + return iqgeo_get_request(f"{BASE_URL}/feature/{feature_type}", design).get( + "features", [] + ) +``` +- This function will take in `feature_type` and `design`, set to `None`, parameters +- It will return all the features using a get request given the `BASE_URL`, `feature_type`, and `design` specification + + +```def iqgeo_get_request(endpoint, params=None, design=None): + """ + Hit a GET endpoint using the auth cookie for this session. + + Raises HTTP errors, and returns the request body JSON. + """ + if design is not None: + params = params or {} + params["design"] = design + r = SESSION.get(endpoint, headers=HEADERS, params=params) + r.raise_for_status() + return r.json() +``` +- This function handles the GET requests made in the sample report files +- It takes in the `endpoint` URL to send request to, and the optional `params` and `design` to filter the query +- The return is the raw json data response from the GET request + +``` +def iqgeo_post_request(endpoint, params=None, design=None, data=None): + """ + Hit a POST endpoint using the auth cookie for this session. + + Raises HTTP errors, and returns the request body JSON. + """ + if design is not None: + params = params or {} + params["design"] = design + r = SESSION.post(endpoint, headers=HEADERS, params=params, json=data) + r.raise_for_status() + return r.json() +``` +- This function handles the POST request made in the sample report files +- It takes in the `endpoint` URL, and the optional `params`, `design`, and `data` to filter the query +- The return is the raw json data response from the + + +``` +def query_spatial(feature_type, geometry, tolerance=0): + """ + General spatial query for any feature type. + Supports Point, LineString, and Polygon geometries. + """ + geom_type = geometry.get("type") + + if geom_type == "Point": + lon, lat = geometry["coordinates"] + url = f"{BASE_URL}/feature/{feature_type}" + params = {"lat": lat, "lon": lon, "tolerance": tolerance} + return iqgeo_get_request(url, params=params) + + elif geom_type in ["LineString", "Polygon"]: + url = f"{BASE_URL}/feature/{feature_type}/get" + data = {"geometry": geometry, "tolerance": tolerance} + return iqgeo_post_request(url, data=data) + else: + raise ValueError(f"Unsupported geometry type: {geom_type}") +``` +- This function calls the spatial query requests using both the GET and POST helper functions +- It takes in the `feature_type` that the user wants to query, the `geometry` with its coordinates, and the `tolerance` to add to the coordinates +- First, it checks the geometry type from the `geometry` parameter to determine which type of geometry, `Point`, `LineString`, or `Polygon` +- If point, the `lat` and `lon` are used to pass into the parameters using a GET request +- If `LineString` or `Polygon`, the request is sent as a POST request to keep the endpoint URL clean + - The `data` is sent with the `geometry` and `tolerance` + + + +### conduit_capacity.py + +The code starts with the relevant `import` statements. + +``` +import argparse +import json +from pathlib import Path +from utils import ( + iqgeo_jwt_auth, + iqgeo_get_request, + get_all_features, + BASE_URL, +) +``` +- The `argparse` library is used to parse the arugments passed in to run the python file +- The `json` library is used to read the cookies dictionary and REST request response +- the `Path` liobrary is used to input the `token.txt` file for JWT authentication +- The imports from `utils` are the helper functions explained above to be used in the `conduit_capacity.py` + +The first function is the `get_cable_segments` which is used to get all the feature data of the fiber cable segments that are in relationship with a conduit given its ID. + +``` +def get_cable_segments(conduit_id, design): + """Get cable segments related to a specific conduit""" + return iqgeo_get_request( + f"{BASE_URL}/feature/conduit/{conduit_id}/relationship/cable_segments", design + ).get("features", []) +``` +- This function takes in the `conduit_id` and `design` as a parameter +- It will return the raw cable segment data using the `iqgeo_get_request` helper function + + +``` +def get_cable_diameter(cable_ref, design): + """Get cable diameter from cable properties + ref = e.g. fiber_cable/4 + """ + return ( + iqgeo_get_request(f"{BASE_URL}/feature/{cable_ref}", design) + .get("properties", {}) + .get("diameter") + ) +``` +- This function takes in the cable reference and design as parameters +- It returns the diameter data of the cable passed into the function + - This will be used to calculate the cable capacity percentage + + +``` +def calc_fill_ratio(conduit_diameter, cable_diameters): + """ + Calculate fill ratio and determine if within limits. + + Implementation of: + https://www.corning.com/optical-communications/worldwide/en/home/Resources/system-design-calculators/fill-ratio-calculator.html + """ + if not conduit_diameter or conduit_diameter == 0: + return None, None + ratio = sum(d**2 for d in cable_diameters) / (conduit_diameter**2) + + if len(cable_diameters) == 1: + limit = 0.65 + elif len(cable_diameters) == 2: + limit = 0.31 + elif len(cable_diameters) == 3: + limit = 0.40 + else: + limit = 1.0 + + return ratio, limit +``` +- This function uses the `conduit_diameter` and `cable_diameter` to calculate the fill ratio of a conduit +- First, it calculates the `ratio` using the sum of the `cable_diameter` squared over the `conduit_diameter` squared +- Then, using the amount of cables per conduit, calculates a `limit` +- The function then returns the `ratio` and `limit` + + +``` +def main(token_file, design): + """script entrypoint.""" + + iqgeo_jwt_auth(token_file) + + capacity_report = {} + conduits = get_all_features(feature_type="conduit", design=design) + + for conduit in conduits: + cid = conduit["properties"].get("id") + conduit_d = conduit["properties"].get("diameter") + + segments = get_cable_segments(cid, design) + cable_refs = { + seg["properties"].get("cable") + for seg in segments + if seg["properties"].get("cable") + } + + cable_diameters = [] + for cref in cable_refs: + d = get_cable_diameter(cref, design) + if d: + cable_diameters.append(d) + + # use the printed values to test + ratio, limit = calc_fill_ratio(conduit_d, cable_diameters) + if ratio is None: + status = "No diameter data" + else: + percent = f"{ratio*100:.1f}%" + if ratio <= limit and ratio > 0: + status = f"{percent} (OK), cable count: {len(cable_refs)}" + elif ratio == 0: + status = f"{percent} (EMPTY), cable count: {len(cable_refs)}" + else: + status = f"{percent} (OVERFILL), cable count: {len(cable_refs)}" + + capacity_report[f"conduit/{cid}"] = status + + print(json.dumps(capacity_report, indent=2)) +``` +- This is the main python funtion which takes in the `token_file` and `design` as parameters from the arguments +- First, it will call the `iqgeo_jwt_auth` function to authenticate the user +- Then, create some variables that will be used in the later helper function calls + - `capacity_report` will be returned at the end with all the conduit capacity information + - `conduits` is the list of conduits using the request in the `utils.py` file +- The function will then loop through each conduit in `conduits` + - Each conduit's ID is set to the `cid` variable + - Each conduit's diameter is set to the `conduit_d` variable + - Using the `cid` and `design`, the function then calls the `get_cable_segments` function to get a list of all the segments associated with the particular conduit + - Using the list of segments, it creates a list of `cable_refs`, this each reference to a fiber cable inside the properties of the cable segment + - Then, creating a list of `cable_diameters`, the function loops through all the cables in `cable_refs` to get the diameter of each cable and append to the list of `cable_diameters` + - Passing the `conduit_d` and `cable_diameters` into the `calc_fill_ratio` function, the `ratio` and `limit` is then received + - The is the `ratio` is used to calculate a `percent`, set a status for how full each conduit is, and add the information to the `capacity_report` to print out + +``` +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Conduit capacity report") + parser.add_argument( + "--token_file", + type=Path, + default="token.txt", + help="Path to the pre-generated JWT token", + ) + parser.add_argument( + "--design", + type=str, + default=None, + help="Design ID to use, e.g. design/2FMyDesign", + ) + args = parser.parse_args() + + main(token_file=args.token_file, design=args.design) +``` +This block makes the script runnable from the command line. It sets up two arguments: +- `--token_file` is the path to the JWT token file (default: `token.txt`) +- `--design` is the ID of the design to use (optional) + + +### pole_attachment.py + +The code starts with the relevent `impot` statements. + +``` +import argparse +import json +from pathlib import Path +from utils import ( + iqgeo_jwt_auth, + iqgeo_get_request, + get_all_features, + BASE_URL, +) +``` +- The `argparse` library is used to parse the arguments passed when running the Python file +- The `json` library is used to pretty-print the attachment report results +- The `Path` library is used to input the `token.txt` file for JWT authentication +- The imports from `utils` are the helper functions explained above, which handle authentication and REST API requests + +The first helper function gets equipment related to a specific pole. + +``` +def get_pole_equipment(pole_id, design): + """Get equipment attached to a specific pole""" + return iqgeo_get_request( + f"{BASE_URL}/feature/pole/{pole_id}/relationship/equipment", design + ).get("features", []) +``` +- This functions takes in the `pole_id` and `design` as parameters +- It uses the `iqgeo_get_request` helper to query all equipment features attached to that pole +- The return is a list of equipment in JSON format + +The next helper functions gets the routes associated with a pole. + +``` +def get_pole_routes(pole_id, design): + """Get routes associated with a specific pole""" + return iqgeo_get_request( + f"{BASE_URL}/feature/pole/{pole_id}/relationship/routes", design + ).get("features", []) +``` +- This function takes in the `pole_id` and `design` as parameters +- It uses the `iqgeo_get_request` helper to query all routes connected to that pole +- The return is a list of route features in JSON format + +```def main(token_file, design): + """script entrypoint.""" + + iqgeo_jwt_auth(token_file) + + # custom report section + attachment_report = {} + poles = get_all_features(feature_type="pole", design=design) + + for pole in poles: + pid = pole["properties"].get("id") + + equipment = get_pole_equipment(pid, design) + routes = get_pole_routes(pid, design) + + equip_list = [ + { + "id": e["properties"].get("name"), + "root_housing": e["properties"].get("root_housing"), + } + for e in equipment + if e.get("properties") + ] + + route_list = [ + { + "id": r["properties"].get("id"), + "in_structure": r["properties"].get("in_structure"), + "out_structure": r["properties"].get("out_structure"), + } + for r in routes + if r.get("properties") + ] + + attachment_report[f"pole/{pid}"] = { + "equipment_count": len(equip_list), + "equipment": equip_list, + "route_count": len(route_list), + "routes": route_list, + } + + print(json.dumps(attachment_report, indent=2)) +``` +- The function starts by authenticating with the `iqgeo_jwt_auth` helper using the provided token file +- It then retrieves all poles in the given design using the `get_all_features` function +- For each pole: + - It retrieves attached equipment (`get_pole_equipment`) and connected routes (`get_pole_routes`) + - It builds a list of equipment details (`equip_list`) including the equipment name and root housing + - It builds a list of route details (`route_list`) including route ID, in_structure, and out_structure + - It adds these results into the attachment_report dictionary under the pole's ID +- Finally, the report is printed in a JSON format output + +The script ends with a runnable block to parse arguments from the command line. + +``` +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Conduit capacity report") + parser.add_argument( + "--token_file", + type=Path, + default="token.txt", + help="Path to the pre-generated JWT token", + ) + parser.add_argument( + "--design", + type=str, + default=None, + help="Design ID to use, e.g. design/2FMyDesign", + ) + args = parser.parse_args() + + main(token_file=args.token_file, design=args.design) +``` +This block makes the script runnable from the command line. It sets up two arguments: +- `--token_file` is the path to the JWT token file (default: `token.txt`) +- `--design` is the ID of the design to use (optional) + +### spatial_query.py + +This example shows how to perform spatial queries against the Platform REST API using different geometry types: Point, LineString, and Polygon. It demonstrates how to use the `query_spatial` helper function (from `utils.py`) to search for features within a specified tolerance. + +The code starts with the relevant imports: + +``` +import argparse +from pathlib import Path +from utils import query_spatial, iqgeo_jwt_auth +``` +- `json` is used to format and print results +- `argparse` handles command line arguments +- `Path` manages the token.txt file for JWT authentication +- The imports from `utils` (`query_spatial`, `iqgeo_jwt_auth`) provide authentication and query helpers + +``` +def report_features(feature_type, geometry, tolerance=0): + """ + Generic report function: prints properties of features returned by query_spatial. + """ + results = query_spatial(feature_type, geometry, tolerance) + print(f"--- {feature_type.upper()} spatial query ---") + for f in results.get("features", []): + props = f.get("properties", {}) + print(props) +``` +- Calls the `query_spatial` helper to run a query with the given feature type, geometry, and tolerance +- Iterates through the returned `features` list and prints out the `properties` of each result +- This function is generic and can be reused for any feature type and geometry combination + + +``` +def main(token_file): + + iqgeo_jwt_auth(token_file) + + # Point + point = {"type": "Point", "coordinates": [0.14208, 52.23095]} + report_features("manhole", point, tolerance=60) + + # LineString + line = { + "type": "LineString", + "coordinates": [ + [0.13422048802249265, 52.220846611354546], + [0.135095125230265, 52.22157378945272], + [0.14540334946042321, 52.22735251836545], + ], + } + report_features("pole", line, tolerance=25) + + # Polygon + polygon = { + "type": "Polygon", + "coordinates": [ + [ + [0.1400, 52.2300], + [0.1450, 52.2300], + [0.1450, 52.2350], + [0.1400, 52.2350], + [0.1400, 52.2300], + ] + ], + } + report_features("pole", polygon, tolerance=10) +``` +- First authenticates using the provided token file +- Runs three example spatial queries: + - Point query: finds manholes near a given coordinate within 60m + - LineString query: finds poles intersecting a drawn line within 25m tolerance + - Polygon query: finds poles inside a bounding box polygon with 10m tolerance + +This shows how different geometries can be used to target specific areas or assets in the network. + + +Lastly, the command-line execution +``` +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Conduit capacity report") + parser.add_argument( + "--token_file", + type=Path, + default="token.txt", + help="Path to the pre-generated JWT token", + ) + args = parser.parse_args() + + main(token_file=args.token_file) +``` +- Accepts one argument: + - `--token_file`: path to the JWT token file (defasult: `token.txt`) + diff --git a/Documents/Reporting_Samples/Reporting_Samples_Overview.md b/Documents/Reporting_Samples/Reporting_Samples_Overview.md new file mode 100644 index 0000000..e3f5363 --- /dev/null +++ b/Documents/Reporting_Samples/Reporting_Samples_Overview.md @@ -0,0 +1,56 @@ +# Reporting Samples - Overview + +## Table of Contents + +- [Reporting Samples - Overview](#reporting-samples---overview) + - [Table of Contents](#table-of-contents) + - [Tool Description](#tool-description) + - [How to use the tool](#how-to-use-the-tool) + +--- + +## Tool Description + +This sample provides examples for how to query the database and create reports similar to the Enterprise "Data Warehouse" reports using the available REST APIs. These examples are designed for Professional customers to: + +- Authenticate via **RPOC auth** or **JWT auth** to get started +- Query feature data in various ways to mimic "Data Warehouse" reporting + + +The goal is to make REST API–based reporting as straightforward as possible, even without a replica database. All examples are written in **Python** with examples for conduit capacity, pole attachment, and spatial querying reports. + +As a stretch goal, we may also provide **code-free options** using tools like **FME** or **Postman**, to give Professional Edition customers an experience closer to Enterprise’s no-code “Data Warehouse” reporting workflows. + +Use this sample as a reference if you want to: + +- Understand spatial query workflows. +- Extend results handling into custom reporting or UI logic. +- Experiment quickly with geometry-based searches before building larger tools. + + +## How to use the tool + +The python scripts for reporting are located `modules/devrel-samples/public/Samples/reporting_samples`. The samples are three Python scripts that can be excuted within VS Code using the "Run Python File" command, or creating an executable for each script. Below are the instructions for creating the executable: + +1. Creating the executable + `chmod +x .py` + +2. Run as executable + `./.py` + +3. Using arguments + Each of the scripts take command line arguments for authentication and/or design specs. To use Python Argparse, run the Python files like so: + `./.py --token_file --design ` + + Note: `spatial_query.py` only takes in the token file path argument. + +4. OR use the `python` command to run + `python3 .py --token_file --design ` + + +The script will pause briefly while authenticating with the token. Once authenticated, the report is printed to the terminal in JSON format. + + +![Conduit Capacity REST API Output](./Reporting_Samples_1.png) + +

Fig. 1: Conduit Capacity report output example.

\ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a730e6b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "utils-devrel-samples", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "utils-devrel-samples", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "comment-parser": "^1.4.1" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..27c6582 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "utils-devrel-samples", + "version": "1.0.0", + "description": "This project provides several samples of functionalities for developers wanting to learn more about how to extend IQGeo applications.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "comment-parser": "^1.4.1" + } +} diff --git a/public/Samples/live_docs/classes_dictionary.js b/public/Samples/live_docs/classes_dictionary.js index 01a5e99..4b92540 100644 --- a/public/Samples/live_docs/classes_dictionary.js +++ b/public/Samples/live_docs/classes_dictionary.js @@ -3,6 +3,12 @@ import { useLocale } from 'myWorld-client/react'; import { param } from 'jquery'; import { MyWorldFeature } from 'myWorld-base'; import { useParams } from 'react-router-dom'; +import structureDescriptions from './function_descriptions/structureDescriptions.json'; +import equipmentDescriptions from './function_descriptions/equipmentDescriptions.json'; +import conduitDescriptions from './function_descriptions/conduitDescriptions.json'; +import cableDescriptions from './function_descriptions/cableDescriptions.json'; +import connectionDescriptions from './function_descriptions/connectionDescriptions.json'; +import circuitDescriptions from './function_descriptions/circuitDescriptions.json'; const { msg } = useLocale('LiveDocsPlugin'); const title = msg('classTitle'); @@ -72,7 +78,7 @@ export const StructureMenu = [ label: 'structContent', params: [ - { name: 'structure' , type: 'MyWorldFeature' }, + { name: 'structure' , type: 'MyWorldFeature'}, { name: 'includeProposed', type: 'Boolean' } ] @@ -81,24 +87,24 @@ export const StructureMenu = [ value: 'getStructuresAtCoords', label: 'getStructuresAtCoords', params: [ - { name: 'coords', type: 'Array' }, - { name: 'featureTypes', type: 'Array' } + { name: 'coords', type: 'Array>' }, + { name: 'featureTypes', type: 'Array', optional: true } ] }, { value: 'getStructureAt', label: 'getStructureAt', params: [ - { name: 'coord', type: 'Array' }, - { name: 'featureTypes', type: 'Array' } + { name: 'coord', type: 'Array' }, + { name: 'featureTypes', type: 'Array', optional: true } ] }, { value: 'getStructuresAt', label: 'getStructuresAt', params: [ - { name: 'coord', type: 'Array' }, - { name: 'featureTypes', type: 'Array' }, + { name: 'coord', type: 'Array' }, + { name: 'featureTypes', type: 'Array', optional: true }, { name: 'tolerance', type: 'Number' } ] }, @@ -114,7 +120,7 @@ export const StructureMenu = [ value: 'validateRoutesForConduit', label: 'validateRoutesForConduit', params: [ - { name: 'routes', type: 'Array' }, + { name: 'routes', type: 'Array' }, { name: 'conduit', type: 'MyWorldFeature' } ] }, @@ -167,6 +173,147 @@ export const StructureMenu = [ } ]; +export const StructureDescriptions = { + listStructures: { + body: ( +
+

+ Pressing the button will list all features that are configured as a building in + the myw.config['mywcom.structures'] array. +

+
+ ), + function: 'onListStructures' + }, + + structContent: { + body: ( +
+

+ {structureDescriptions.structContent} +

+
+ ), + function: 'onStructContent' + }, + + getStructuresAtCoords: { + body: ( +
+

+ {structureDescriptions.getStructuresAtCoords} +

+
+ ), + function: 'onGetStructuresAtCoords' + }, + + getStructureAt: { + body: ( +
+

+ {structureDescriptions.getStructureAt} +

+
+ ), + function: 'onGetStructureAt' + }, + + getStructuresAt: { + body: ( +
+

{structureDescriptions.getStructuresAt}

+
+ ), + function: 'onGetStructuresAt' + }, + + routeContent: { + body: ( +
+

+ {structureDescriptions.routeContent} +

+
+ ), + + function: 'onRouteContent' + }, + + validateRoutesForConduit: { + body: ( +
+

+ {structureDescriptions.validateRoutesForConduit} +

+
+ ), + function: 'onValidateRoutesForConduit' + }, + + isStructure: { + body: ( +
+

+ {structureDescriptions.isStructure} +

+
+ ), + function: 'onIsStructure' + }, + + isRoute: { + body: ( +
+

+ {structureDescriptions.isRoute} +

+
+ ), + function: 'onIsRoute' + }, + isConduit: { + body: ( +
+

+ {structureDescriptions.isConduit} +

+
+ ), + function: 'onIsConduit' + }, + fixRouteEnds: { + body: ( +
+

+ {structureDescriptions.fixRouteEnds} +

+
+ ), + function: 'onFixRouteEnds' + }, + houseInStructure: { + body: ( +
+

+ {structureDescriptions.houseInStructure} +

+
+ ), + function: 'onHouseInStructure' + }, + transferToStructure: { + body: ( +
+

+ {structureDescriptions.transferToStructure} +

+
+ ), + function: 'onTransferToStructure' + } +}; + export const EquipmentMenu = [ { label: List Equipment, @@ -230,14 +377,98 @@ export const EquipmentMenu = [ { name: 'feature', type: 'MyWorldFeature' }, { name: 'direction', type: 'String' }, { name: 'side', type: 'String' }, - { name: 'sourceRanges', type: 'Array' }, - { name: 'portInfoToRipple', type: 'Array' } + { name: 'sourceRanges', type: 'Array' }, + { name: 'portInfoToRipple', type: 'Array' } ] } ] } ]; +export const EquipmentDescriptions = { + listEquipment: { + body: ( +
+

+ Pressing the button will list all features that are configured as equipment in + the myw.config['mywcom.equipment'] array. +

+
+ ), + function: 'onListEquipment' + }, + isEquipment: { + body: ( +
+

+ {equipmentDescriptions.isEquipment} +

+
+ ), + function: 'onIsEquipment' + }, + moveAssembly: { + body: ( +
+

+ {equipmentDescriptions.moveAssembly} +

+
+ ), + function: 'onMoveAssembly' + }, + copyAssembly: { + body: ( +
+

+ {equipmentDescriptions.copyAssembly} +

+
+ ), + function: 'onCopyAssembly' + }, + connectionsIn: { + body: ( +
+

+ {equipmentDescriptions.connectionsIn} +

+
+ ), + function: 'onConnectionsIn' + }, + connectionsOf: { + body: ( +
+

+ {equipmentDescriptions.connectionsOf} +

+
+ ), + function: 'onConnectionsOf' + }, + equipmentWithPortInfo: { + body: ( +
+

+ {equipmentDescriptions.equipmentWithPortInfo} +

+
+ ), + function: 'onEquipmentWithPortInfo' + }, + ripplePortInfo: { + body: ( +
+

+ {equipmentDescriptions.ripplePortInfo} +

+
+ ), + function: 'onRipplePortInfo' + } +}; + export const ConduitMenu = [ { label: List Conduits, @@ -313,6 +544,89 @@ export const ConduitMenu = [ } ]; +export const ConduitDescriptions = { + listConduits: { + body: ( +
+

+ Pressing the button will list all features that are configured as equipment in + the myw.config['mywcom.equipment'] array. +

+
+ ), + function: 'onListConduits' + }, + disconnectConduit: { + body: ( +
+

+ {conduitDescriptions.disconnectConduit} +

+
+ ), + function: 'onDisconnectConduit' + }, + connectConduits: { + body: ( +
+

+ {conduitDescriptions.connectConduits} +

+
+ ), + function: 'onConnectConduits' + }, + routeNestedConduits: { + body: ( +
+

+ {conduitDescriptions.routeNestedConduits} +

+
+ ), + function: 'onRouteNestedConduits' + }, + moveInto: { + body: ( +
+

+ {conduitDescriptions.moveInto} +

+
+ ), + function: 'onMoveInto' + }, + isContinuousConduitType: { + body: ( +
+

+ {conduitDescriptions.isContinuousConduitType} +

+
+ ), + function: 'onIsContinuousConduitType' + }, + continuousPhysicalConduits: { + body: ( +
+

+ {conduitDescriptions.continuousPhysicalConduits} +

+
+ ), + function: 'onContinuousPhysicalConduits' + }, + deleteContinuousPhysicalConduits: { + body: ( +
+

+ {conduitDescriptions.deleteContinuousPhysicalConduits} +

+
+ ), + function: 'onDeleteContinuousPhysicalConduits' + } +}; export const CableMenu = [ { @@ -360,7 +674,7 @@ export const CableMenu = [ { name: 'struct', type: 'MyWorldFeature' }, { name: 'cable', type: 'MyWorldFeature' }, { name: 'housingUrn', type: 'String' }, - { name: 'length', type: 'Number' } + { name: 'length', type: 'Number', optional: true } ] }, { @@ -413,7 +727,7 @@ export const CableMenu = [ params: [ { name: 'featureUrn', type: 'MyWorldFeature' }, { name: 'housing_field', type: 'String' }, - { name: 'splices', type: 'Boolean' } + { name: 'splices', type: 'Boolean', optional: true} ] }, { @@ -496,7 +810,7 @@ export const CableMenu = [ label: 'adjustMeasuredLengths', params: [ { name: 'trans', type: 'Transaction' }, - { name: 'segs', type: 'Array' }, + { name: 'segs', type: 'Array' }, { name: 'tickDist', type: 'Number' }, ] }, @@ -504,9 +818,9 @@ export const CableMenu = [ value: 'routeCable', label: 'routeCable', params: [ - { name: 'cableJson', type: 'Array' }, - { name: 'structures', type: 'Array' }, - { name: 'parentFeatures', type: 'Array' } + { name: 'cableJson', type: 'Array' }, + { name: 'parentFeatures', type: 'Array' } ] }, { @@ -516,7 +830,7 @@ export const CableMenu = [ { name: 'struct', type: 'MyWorldFeature' }, { name: 'segment', type: 'MyWorldFeature' }, { name: 'forward', type: 'Boolean' }, - { name: 'spliceHousing', type: 'MyWorldFeature' } + { name: 'spliceHousing', type: 'MyWorldFeature', optional: true} ] }, { @@ -535,13 +849,17 @@ export const CableMenu = [ }, { value: 'rootHousingUrnOf', - label: 'rootHousingUrnOf' - // TODO: params: what is the method of input for housing parameter? + label: 'rootHousingUrnOf', + params: [ + { name: 'housing', type: 'MyWorldFeature'} + ] }, { value: 'getLength', - label: 'getLength' - // TODO: params: what is the method of input for housing parameter? + label: 'getLength', + params: [ + { name: 'feature', type: 'GeoJSON'} + ] }, { value: 'segmentTypeForCable', @@ -601,7 +919,7 @@ export const CableMenu = [ value: 'truncateLine', label: 'truncateLine', params: [ - { name: 'coords', type: 'Array' }, + { name: 'coords', type: 'Array>' }, { name: 'startTruncDist', type: 'Number' }, { name: 'endTruncDist', type: 'Number' } ] @@ -639,4 +957,834 @@ export const CableMenu = [ } ] } -]; \ No newline at end of file +]; + +export const CableDescriptions = { + listCables: { + body: ( +
+

+ Pressing the button will list all features that are configured as equipment in + the myw.config['mywcom.equipment'] array. +

+
+ ), + function: 'onListCables' + }, + highestUsedPinOn: { + body: ( +
+

+ {cableDescriptions.highestUsedPinOn} +

+
+ ), + function: 'onHighestUsedPinOn' + }, + connectionsFor: { + body: ( +
+

+ {cableDescriptions.connectionsFor} +

+
+ ), + function: 'onConnectionsFor' + }, + internalSegments: { + body: ( +
+

+ {cableDescriptions.internalSegments} +

+
+ ), + function: 'onInternalSegments' + }, + createDetachedInternalSeg: { + body: ( +
+

+ {cableDescriptions.createDetachedInternalSeg} +

+
+ ), + function: 'onCreateDetachedInternalSeg' + }, + createDetachedSlack: { + body: ( +
+

+ {cableDescriptions.createDetachedSlack} +

+
+ ), + function: 'onCreateDetachedSlack' + }, + splitSlack: { + body: ( +
+

+ {cableDescriptions.splitSlack} +

+
+ ), + function: 'onSplitSlack' + }, + createDetSlackAtSide: { + body: ( +
+

+ {cableDescriptions.createDetSlackAtSide} +

+
+ ), + function: 'onCreateDetSlackAtSide' + }, + addSlack: { + body: ( +
+

+ {cableDescriptions.addSlack} +

+
+ ), + function: 'onAddSlack' + }, + transferConnections: { + body: ( +
+

+ {cableDescriptions.transferConnections} +

+
+ ), + function: 'onTransferConnections' + }, + segmentContainment: { + body: ( +
+

+ {cableDescriptions.segmentContainment} +

+
+ ), + function: 'onSegmentContainment' + }, + setSegmentContainment: { + body: ( +
+

+ {cableDescriptions.setSegmentContainment} +

+
+ ), + function: 'onSetSegmentContainment' + }, + setTickMark: { + body: ( +
+

+ {cableDescriptions.setTickMark} +

+
+ ), + function: 'onSetTickMark' + }, + setInTickMark: { + body: ( +
+

+ {cableDescriptions.setInTickMark} +

+
+ ), + function: 'onSetInTickMark' + }, + findDownstreamSegsToTick: { + body: ( +
+

+ {cableDescriptions.findDownstreamSegsToTick} +

+
+ ), + function: 'onFindDownstreamSegsToTick' + }, + setOutTickMark: { + body: ( +
+

+ {cableDescriptions.setOutTickMark} +

+
+ ), + function: 'onSetOutTickMark' + }, + findUpstreamSegsToTick: { + body: ( +
+

+ {cableDescriptions.findUpstreamSegsToTick} +

+
+ ), + function: 'onFindUpstreamSegsToTick' + }, + computeTickDist: { + body: ( +
+

+ {cableDescriptions.computeTickDist} +

+
+ ), + function: 'onComputeTickDist' + }, + adjustMeasuredLengths: { + body: ( +
+

+ {cableDescriptions.adjustMeasuredLengths} +

+
+ ), + function: 'onAdjustMeasuredLengths' + }, + routeCable: { + body: ( +
+

+ {cableDescriptions.routeCable} +

+
+ ), + function: 'onRouteCable' + }, + cutCableAt: { + body: ( +
+

+ {cableDescriptions.cutCableAt} +

+
+ ), + function: 'onCutCableAt' + }, + isCable: { + body: ( +
+

+ {cableDescriptions.isCable} +

+
+ ), + function: 'onIsCable' + }, + isInternal: { + body: ( +
+

+ {cableDescriptions.isInternal} +

+
+ ), + function: 'onIsInternal' + }, + rootHousingUrnOf: { + body: ( +
+

+ {cableDescriptions.rootHousingUrnOf} +

+
+ ), + function: 'onRootHousingUrnOf' + }, + getLength: { + body: ( +
+

+ {cableDescriptions.getLength} +

+
+ ), + function: 'onGetLength' + }, + segmentTypeForCable: { + body: ( +
+

+ {cableDescriptions.segmentTypeForCable} +

+
+ ), + function: 'onSegmentTypeForCable' + }, + slackTypeForCable: { + body: ( +
+

+ {cableDescriptions.slackTypeForCable} +

+
+ ), + function: 'onSlackTypeForCable' + }, + slackTypeForSegment: { + body: ( +
+

+ {cableDescriptions.slackTypeForSegment} +

+
+ ), + function: 'onSlackTypeForSegment' + }, + isSegment: { + body: ( +
+

+ {cableDescriptions.isSegment} +

+
+ ), + function: 'onIsSegment' + }, + segmentTypes: { + body: ( +
+

+ {cableDescriptions.segmentTypes} +

+
+ ), + function: 'onSegmentTypes' + }, + connectionTypes: { + body: ( +
+

+ {cableDescriptions.connectionTypes} +

+
+ ), + function: 'onConnectionTypes' + }, + slackTypes: { + body: ( +
+

+ {cableDescriptions.slackTypes} +

+
+ ), + function: 'onSlackTypes' + }, + pinCountFor: { + body: ( +
+

+ {cableDescriptions.pinCountFor} +

+
+ ), + function: 'onPinCountFor' + }, + moveCableOnConnect: { + body: ( +
+

+ {cableDescriptions.moveCableOnConnect} +

+
+ ), + function: 'onMoveCableOnConnect' + }, + truncateLine: { + body: ( +
+

+ {cableDescriptions.truncateLine} +

+
+ ), + function: 'onTruncateLine' + }, + handleConnect: { + body: ( +
+

+ {cableDescriptions.handleConnect} +

+
+ ), + function: 'onHandleConnect' + }, + fixCableSegmentChain: { + body: ( +
+

+ {cableDescriptions.fixCableSegmentChain} +

+
+ ), + function: 'onFixCableSegmentChain' + }, + fixCable: { + body: ( +
+

+ {cableDescriptions.fixCable} +

+
+ ), + function: 'onFixCable' + }, + rippleStrandInfo: { + body: ( +
+

+ {cableDescriptions.rippleStrandInfo} +

+
+ ), + function: 'onRippleStrandInfo' + } +}; + +export const ConnectionMenu = [ + { + label: List Connections, + title: 'List Connections', + options: [ + { + value: 'listConnections', + label: 'List Connections' + } + ] + }, + { + label: Functions, + title: 'API Functions', + options: [ + { + value: 'freePinsOn', + label: 'freePinsOn', + params: [ + { name: 'feature', type: 'MyWorldFeature' }, + { name: 'tech', type: 'String' }, + { name: 'side', type: 'String' } + ] + }, + { + value: 'usedPinsOn', + label: 'usedPinsOn', + params: [ + { name: 'feature', type: 'MyWorldFeature' }, + { name: 'tech', type: 'String' }, + { name: 'side', type: 'String' } + ] + }, + { + value: 'highPinUsedOn', + label: 'highPinUsedOn', + params: [ + { name: 'feature', type: 'MyWorldFeature' }, + { name: 'tech', type: 'String' }, + { name: 'side', type: 'String' } + ] + }, + { + value: 'pinStateFor', + label: 'pinStateFor', + params: [ + { name: 'feature', type: 'MyWorldFeature' }, + { name: 'tech', type: 'String' }, + { name: 'side', type: 'String' } + ] + }, + { + value: 'pinCountFor', + label: 'pinCountFor', + params: [ + { name: 'feature', type: 'MyWorldFeature' }, + { name: 'tech', type: 'String' }, + { name: 'side', type: 'String' } + ] + }, + { + value: 'traceOut', + label: 'traceOut', + params: [ + { name: 'tech', type: 'String' }, + { name: 'feature', type: 'MyWorldFeature' }, + { name: 'pins', type: 'PinRange' }, + { name: 'direction', type: 'String' }, + { name: 'maxDist', type: 'Number' } + ] + }, + { + value: 'connect', + label: 'connect', + params: [ + { name: 'tech', type: 'String' }, + { name: 'fromFeature', type: 'MyWorldFeature' }, + { name: 'fromPins', type: 'PinRange' }, + { name: 'toFeature', type: 'MyWorldFeature' }, + { name: 'toPins', type: 'PinRange' }, + { name: 'housing', type: 'MyWorldFeature' }, + { name: 'ripple', type: 'Boolean', optional: true} + ] + }, + { + value: 'disconnect', + label: 'diconnect', + params: [ + { name: 'tech', type: 'String' }, + { name: 'feature', type: 'MyWorldFeature' }, + { name: 'pins', type: 'PinRange' }, + { name: 'ripple', type: 'Boolean', optional: true } + ] + }, + { + value: 'moveConns', + label: 'moveConns', + params: [ + { name: 'conns', type: 'Array' }, + { name: 'housingUrn', type: 'String' }, + { name: 'rootHousingUrn', type: 'String' } + ] + }, + { + value: 'switchConnSides', + label: 'switchConnSides', + params: [ + { name: 'feature', type: 'MyWorldFeature' } + ] + }, + { + value: 'techFor', + label: 'techFor', + params: [ + { name: 'feature', type: 'MyWorldFeature' }, + { name: 'side', type: 'String' } + ] + }, + { + value: 'fixConnectionSegments', + label: 'fixConnectionSegments', + params: [ + { name: 'conn', type: 'MyWorldFeature' } + ] + }, + { + value: 'isConnection', + label: 'isConnection', + params: [ + { name: 'feature', type: 'MyWorldFeature' } + ] + } + ] + } +]; + +export const ConnectionDescriptions = { + listConnections: { + body: ( +
+

+ Pressing the button will list all features that are configured as equipment in + the myw.config['mywcom.equipment'] array. +

+
+ ), + function: 'onListConnections' + }, + freePinsOn: { + body: ( +
+

+ {connectionDescriptions.freePinsOn} +

+
+ ), + function: 'onFreePinsOn' + }, + usedPinsOn: { + body: ( +
+

+ {connectionDescriptions.usedPinsOn} +

+
+ ), + function: 'onUsedPinsOn' + }, + highPinUsedOn: { + body: ( +
+

+ {connectionDescriptions.highPinUsedOn} +

+
+ ), + function: 'onHighPinUsedOn' + }, + pinStateFor: { + body: ( +
+

+ {connectionDescriptions.pinStateFor} +

+
+ ), + function: 'onPinStateFor' + }, + pinCountFor: { + body: ( +
+

+ {connectionDescriptions.pinCountFor} +

+
+ ), + function: 'onPinCountFor' + }, + traceOut: { + body: ( +
+

+ {connectionDescriptions.traceOut} +

+
+ ), + function: 'onTraceOut' + }, + connect: { + body: ( +
+

+ {connectionDescriptions.connect} +

+
+ ), + function: 'onConnect' + }, + disconnect: { + body: ( +
+

+ {connectionDescriptions.disconnect} +

+
+ ), + function: 'onDisconnect' + }, + moveConns: { + body: ( +
+

+ {connectionDescriptions.moveConns} +

+
+ ), + function: 'onMoveConns' + }, + switchConnSides: { + body: ( +
+

+ {connectionDescriptions.switchConnSides} +

+
+ ), + function: 'onSwitchConnSides' + }, + techFor: { + body: ( +
+

+ {connectionDescriptions.techFor} +

+
+ ), + function: 'onTechFor' + }, + fixConnectionSegments: { + body: ( +
+

+ {connectionDescriptions.fixConnectionSegments} +

+
+ ), + function: 'onFixConnectionSegments' + }, + isConnection: { + body: ( +
+

+ {connectionDescriptions.isConnection} +

+
+ ), + function: 'onIsConnection' + } +}; + +export const CircuitMenu = [ + { + label: List Circuits, + title: 'List Circuits', + options: [ + { + value: 'listCircuits', + label: 'List Circuits' + } + + ] + }, + { + label: Functions, + title: 'API Functions', + options: [ + { + value: 'traceLogicalCircuit', + label: 'traceLogicalCircuit', + params: [ + { name: 'circuit', type: 'MyWorldFeature' } + ] + }, + { + value: 'routeCircuit', + label: 'routeCircuit', + params: [ + { name: 'circuit', type: 'MyWorldFeature' } + ] + }, + { + value: 'unrouteCircuit', + label: 'unrouteCircuit', + params: [ + { name: 'circuit', type: 'MyWorldFeature' } + ] + }, + { + value: 'routeCircuits', + label: 'routeCircuits', + params: [ + { name: 'circuits', type: 'Array' } + ] + }, + { + value: 'isCircuitFeature', + lable: 'isCircuitFeature', + params:[ + { name: 'feature', type: 'MyWorldFeature' } + ] + + }, + { + value: 'getDetachedCircuitPath', + lable: 'getDetachedCircuitPath', + params: [ + { name: 'featureType', type: 'String' }, + { name: 'logicalCircuits', type: 'MyWorldFeature' } + ] + }, + { + value: 'updateCircuitStatus', + label: 'updateCircuitStatus', + params: [ + { name: 'circuit', type: 'MyWorldFeature' }, + { name: 'status', type: 'String' } + ] + } + ] + } +]; + +export const CircuitDescriptions = { + listCircuits: { + body: ( +
+

+ Pressing the button will list all features that are configured as equipment in + the myw.config['mywcom.equipment'] array. +

+
+ ), + function: 'onListCircuits' + }, + traceLogicalCircuit: { + body: ( +
+

+ {circuitDescriptions.traceLogicalCircuit} +

+
+ ), + function: 'onTraceLogicalCircuit' + }, + routeCircuit: { + body: ( +
+

+ {circuitDescriptions.routeCircuit} +

+
+ ), + function: 'onRouteCircuit' + }, + unrouteCircuit: { + body: ( +
+

+ {circuitDescriptions.unrouteCircuit} +

+
+ ), + function: 'onUnrouteCircuit' + }, + routeCircuits: { + body: ( +
+

+ {circuitDescriptions.routeCircuits} +

+
+ ), + function: 'onRouteCircuits' + }, + isCircuitFeature: { + body: ( +
+

+ {circuitDescriptions.isCircuitFeature} +

+
+ ), + function: 'onIsCircuitFeature' + }, + getDetachedCircuitPath: { + body: ( +
+

+ {circuitDescriptions.getDetachedCircuitPath} +

+
+ ), + function: 'onGetDetachedCircuitPath' + }, + updateCircuitStatus: { + body: ( +
+

+ {circuitDescriptions.updateCircuitStatus} +

+
+ ), + function: 'onUpdateCircuitStatus' + } +}; \ No newline at end of file diff --git a/public/Samples/live_docs/function_descriptions/cableDescriptions.json b/public/Samples/live_docs/function_descriptions/cableDescriptions.json new file mode 100644 index 0000000..8c167c9 --- /dev/null +++ b/public/Samples/live_docs/function_descriptions/cableDescriptions.json @@ -0,0 +1,45 @@ +{ + "highestUsedPinOn": "Async: true\n\nReturns the highest pin number currently used on the given cable. Useful for determining whether increasing the pin count would affect existing connections.\n\nParameters:\n- `cable` (MywFeature): The cable feature to evaluate\n\nReturns:\n- (Promise): highest pin number in use", + "connectionsFor": "Async: true\n\nGets all connection records for the given cable. Can optionally filter by splice type and return results sorted.\n\nParameters:\n- `cable` (MywFeature): The cable feature whose connections to retrieve\n- `splice` (boolean): If 'true', return only splice connections\n- `sorted` (boolean): If 'true', return the connections sorted\n\nReturns:\n- (Promise): of connection features", + "internalSegments": "Async: true\n\nRetrieves all cable segments housed within the specified feature.\n\nParameters:\n- `housingFeature` (MywFeature): The structure or housing feature\n- `root` (boolean): If 'true', fetches segments where this feature is the root housing\n\nReturns:\n- (Promise): newly created detached segment", + "createDetachedSlack": "Async: true\n\nCreates a detached slack feature for the given cable within a specified housing. This function initializes a new slack (e.g., fiber slack) associated with a cable and a housing structure. It is important to note that a detached segment, once created has NOT been inserted in the database yet.\n\nParameters:\n- `cableFeature` (MywFeature): The cable feature for which to create slack\n- `housingFeature` (MywFeature): The structure housing the slack\n\nReturns:\n- (Promise): detached slack feature", + "splitSlack": "Async: true\n\nSplits an existing slack feature into two parts by dividing its length. The function creates a new slack segment from the given slack feature by cutting it at the specified length. Returns both the updated original slack and the newly created one.\n\nParameters:\n- `slack` (MywFeature): The existing slack feature to split\n- `length` (number): The length to assign to the new slack segment\n\nReturns:\n- (Promise<[MywFeature, MywFeature]>): array containing: [0] The updated original slack [1] The newly created slack feature", + "createDetSlackAtSide": "Async: true\n\nCreates a detached slack feature associated with a cable segment. This function generates a new slack positioned relative to the given cable segment. It attaches metadata (`slackDetails`) indicating which segment and which side (before or after) the slack should appear. It is important to note that a detached segment, once create has NOT been inserted in the database yet.\n\nParameters:\n- `seg` (MywFeature): The existing cable segment to associate the slack with\n- `struct` (MywFeature): The structure that will house the slack\n- `side` (boolean): 'true' if the slack should be created before the segment; false' for after\n\nReturns:\n- (Promise): newly created detached slack feature", + "addSlack": "Async: true\n\nInserts a previously created detached slack into the database, attaching it to the specified cable segment at a specific side.\n\nParameters:\n- `featureType` (string): The type of the slack feature (e.g., 'mywcom_slack_fiber')\n- `detSlack` (MywFeature): The detached slack feature to insert\n- `segUrn` (string): The URN of the cable segment to attach the slack to\n- `side` (boolean): 'true' if the slack is inserted before the segment; false' for after\n\nReturns:\n- (Promise): ID of the inserted slack feature", + "transferConnections": "Async: true\n\nTransfers connections from one cable segment to another on the specified side. This function identifies all connections on `oldSeg` that match the specified `side` (\"in\" or \"out\") and updates them to reference `newSeg`. The returned array includes all updated connection features, which should be committed to a transaction. It's important to note that the changes are NOT pushed into the databse.\n\nParameters:\n- `oldSeg` (string): The URN of the original cable segment with existing connections\n- `newSeg` (string): The URN of the new cable segment to transfer connections to\n- `side` (string): The side of the segment to update (\"in\" or \"out\")\n\nReturns:\n- (Promise): array of updated connection features ENH: Move to connectionManagerPlugin", + "connectionsOf": "Async: true\n\nRetrieves all connection records associated with a given feature URN. Includes records where the feature is the `in_object`, `out_object`, or appears in the specified `housing_field`. Optionally filters by splice.\n\nParameters:\n- `featureURN` (MywFeature): URN of the feature to query connections for\n- `housing_field` (string): Field to use for housing match\n- `splices` (boolean): Optional splice value to limit results\n\nReturns:\n- (Promise): resolving to array of connection features ENH: move to ConnectionManagerPlugin", + "segmentContainment": "Async: true\n\nReturns the URN of the equipment in which the specified side of the cable segment is housed (if any). NOTE: Equipment connections must be manually set. Refer to the \"Setting an Enclosure on a cable\" section in the Developer Guide for more details.\n\nParameters:\n- `seg` (MywFeature): The cable segment to check\n- `side` (string): Which side of the segment to check ('in' or 'out')\n\nReturns:\n- (string|null): URN of the equipment, or null if not set", + "setSegmentContainment": "Async: true\n\nSets the equipment housing for the specified side of a cable segment. Also updates the adjacent segment on the opposite side to ensure consistent containment. If `equip` is `null`, the containment is removed.\n\nParameters:\n- `seg` (MywFeature): The cable segment whose containment is being updated\n- `side` (string): The side of the segment to update ('in' or 'out')\n- `equip` (MywFeature|null): The equipment feature that houses the segment, or `null` to unset\n\nReturns:\n- (Promise): ", + "_setSegmentContainment": "Async: true\n\nSets the equipment housing for the specified side of a cable segment. Also updates the adjacent segment on the opposite side to ensure consistent containment. If `equip` is `null`, the containment is removed.\n\nParameters:\n- `seg` (MywFeature): The cable segment whose containment is being updated\n- `side` (string): The side of the segment to update ('in' or 'out')\n- `equip` (MywFeature|null): The equipment feature that houses the segment, or `null` to unset\n\nReturns:\n- (Promise): updated segment feature", + "setTickMark": "Async: true\n\nSets a tick mark on the specified side of a cable segment and updates adjacent segments accordingly. The function updates the tick mark on the target segment, as well as on its immediate upstream or downstream neighbor, depending on whether the tick is applied to the 'in_tick' or 'out_tick' side. It also traces connected segments to recalculate measured lengths based on the provided spacing and unit.\n\nParameters:\n- `seg` (MywFeature): The cable segment to update\n- `tickMark` (number): The new tick mark value\n- `field` (string): Specifies whether to update the 'in_tick' or 'out_tick' side of the segment\n- `spacing` (number): The spacing between tick marks, in the specified unit\n- `unit` (String): The unit of measurement for the spacing (e.g., \"m\" or \"ft\")\n\nReturns:\n- (Promise): ", + "setInTickMark": "Async: true\n\nSets the `in_tick` value on the given segment and adjusts the measured lengths of downstream segments up to the next tick. Computes the total tick distance and adjusts measured lengths of all segments between the current one and the next ticked segment.\n\nParameters:\n- `trans` (Transaction): The active database transaction to include updates in\n- `seg` (MywFeature): The cable segment where the tick is being set\n- `tickMark` (number): The new `in_tick` value to assign\n- `spacing` (number): The distance between tick marks\n- `unit` (string): Unit of measurement for spacing (e.g., \"m\", \"ft\")\n\nReturns:\n- (number|undefined): tick value of the next downstream segment if found; otherwise 'undefined'", + "findDownstreamSegsToTick": "Async: true\n\nFinds all segments downstream from the given segment until a tick mark is encountered. Starts at the provided segment and follows the out_segment reference. If a tick mark is found on the out_tick of the current segment or the in_tick of the next segment, the traversal stops.\n\nParameters:\n- `seg` (MywFeature): The starting segment\n\nReturns:\n- ([Array, number|null]): array of segments and the first tick mark found", + "setOutTickMark": "Async: true\n\nSets the out tick mark of a segment and adjusts the measured lengths of all upstream segments until the next tick mark is encountered. This function starts from the provided segment and traverses upstream to find the next tick mark. It then updates the measured lengths of the segments between the two tick marks based on the given spacing and unit.\n\nParameters:\n- `trans` (Transaction): The transaction to batch updates into\n- `seg` (MywFeature): The cable segment to update\n- `tickMark` (number): The tick mark value to set\n- `spacing` (number): The distance between tick marks\n- `unit` (string): The unit of measurement (e.g. \"m\" or \"ft\")\n\nReturns:\n- (number|undefined): tick mark found upstream or undefined if none found", + "findUpstreamSegsToTick": "Async: true\n\nFinds all upstream segments from the given segment up to the next tick mark. Traverses the cable network upstream starting from the given segment until it finds a segment with an `in_tick` or `out_tick` set. Returns all visited segments and the tick mark value found.\n\nParameters:\n- `seg` (MywFeature): The segment to start from\n\nReturns:\n- ([Array, number|null]): array of upstream segments and the first tick mark found, or 'null' if none found", + "_setTickMarkNull": "Async: true\n\nClears the tick mark field on the given segment, and clears the corresponding adjacent field on the neighboring segment.\n\nParameters:\n- `trans` (Transaction): The transaction to batch updates\n- `seg` (MywFeature): The segment to clear the tick mark from\n- `field` (string): Either `in_tick` or `out_tick`", + "computeTickDist": "Async: false\n\nCalculates the physical distance in meters between two tick marks on a cable segment. Converts tick spacing from meters to the given unit, then computes the scaled distance between the two tick marks. Returns the result in meters.\n\nParameters:\n- `segTick` (number): The tick mark on the segment\n- `tick` (number): The target tick mark\n- `spacing` (number): The distance between each tick mark, in meters\n- `unit` (string): The unit in which tick spacing is expressed (e.g., \"m\", \"ft\")\n\nReturns:\n- (number): in meters between the two tick marks", + "adjustMeasuredLengths": "Async: true\n\nAdjusts measured lengths of cable segments based on the given tick distance.\n\nParameters:\n- `trans` (Transaction): The transaction object used to batch updates\n- `segs` (Array): An array of cable segment features to adjust\n- `tickDist` (number): The target distance (in meters) between tick marks\n\nReturns:\n- (Array): updated cable segments", + "_assertEndSegValid": "Async: true\n\nValidates the end segment tick marks. If either inTick or outTick is missing, it finds the next tick mark upstream or downstream and verifies that the current tickMark does not overlap with existing ticks.\n\nParameters:\n- `seg` (MywFeature): The cable segment to validate\n- `tickMark` (number): The tick mark being set on this segment\n- `inTick` (number): The tick mark at the 'in' side of the segment\n- `outTick` (number): The tick mark at the 'out' side of the segment", + "_assertTickMarkValid": "Async: false\n\nThrow error if `tickMark` overlaps next tick mark\n\nParameters:\n- `tickMark` (number): The tick mark value to validate\n- `inTick` (number): The tick mark at the 'in' side of the segment\n- `outTick` (number): The tick mark at the 'out' side of the segment", + "routeCables": "Async: true\n\nIterates over an array of cables and routes the cables using the given structures. Once the cables are routed, they are matched with the correct housing and updated.\n\nParameters:\n- `cablesJson` (Array): Array of cable data\n- `structures` (Array): Array of structure features\n- `parentFeatures` (Array): Features that the created cable segments will be housed in\n\nReturns:\n- (Array): cable features", + "_throwError": "Async: false\n\nThrow 'overlapping_tick' error", + "_segHasTick": "Async: false\n\nReturns true if seg has a tick value set on the specified field\n\nParameters:\n- `seg` (MywFeature): Cable segment\n- `field` (string): `in_tick` or `out_tick`\n\nReturns:\n- (boolean): if tick exists, otherwise 'false'", + "cutCableAt": "Async: true\n\nCuts a cable at a given segment within a structure. Creates new separate cables if needed.\n\nParameters:\n- `struct` (MywFeature): Structure where the cut occurs\n- `segment` (MywFeature): Segment to cut\n- `forward` (boolean): 'true' if cut forward, else 'false'\n- `spliceHousing` (MywFeature): Optional housing for splice\n\nReturns:\n- (Object): of the cutCable operation", + "isCable": "Async: false\n\nReturns 'true' if a feature is a cable\n\nParameters:\n- `feature` (MywFeature): Feature to check\n\nReturns:\n- (boolean): if feature is a cable", + "isInternal": "Async: true\n\nReturns 'true' if all segments of a cable are internal. Detached cables or cables crossing structures are considered not internal.\n\nParameters:\n- `cable` (MywFeature): Cable feature to check\n\nReturns:\n- (boolean): if all segments are internal", + "fireFeatureEvents": "Async: false\n\nFires events to update the display and other parts of the application when features have changed. This triggers 'featureCollection-modified' events for relevant feature types: routes, equipment, conduits, cables, circuits, and a specific structure type.\n\nParameters:\n- `aspect` (string): An aspect describing the context of the update\n- `cableFeatureType` (string): Optional. A specific structure feature type to fire an event for if structures are in separate layers", + "rootHousingUrnOf": "Async: false\n\nReturns the URN of the root housing of the given housing feature.\n\nParameters:\n- `housing` (RouteMixin|Conduit): The housing feature\n\nReturns:\n- (string): URN of the root housing, or the housing URN if no root housing is set", + "getLength": "Async: false\n\nReturns the geographic length of a LineString feature.\n\nParameters:\n- `feature` (GeoJSON.Feature): The LineString feature\n\nReturns:\n- (number): length of the feature", + "segmentTypeForCable": "Async: false\n\nReturns the segment feature type for the given cable.\n\nParameters:\n- `cable` (MywFeature): The cable feature\n\nReturns:\n- (string): segment feature type associated with the cable", + "slackTypeForCable": "Async: false\n\nReturns the slack feature type for the given cable.\n\nParameters:\n- `cable` (MywFeature): The cable feature\n\nReturns:\n- (string): slack feature type associated with the cable", + "slackTypeForSegment": "Async: false\n\nReturns the slack feature type for the given segment.\n\nParameters:\n- `segment` (MywFeature): The segment feature\n\nReturns:\n- (string|undefined): slack feature type associated with the segment, or undefined if not found", + "isSegment": "Async: false\n\nDetermines if the given URN corresponds to a segment feature.\n\nParameters:\n- `urn` (string): The URN string\n\nReturns:\n- (boolean): if the URN corresponds to a segment feature, 'false' otherwise", + "pinCountFor": "Async: true\n\nReturns pin count for a feature and on side if specified and appropriate\n\nParameters:\n- `feature` (MywFeature): The feature to get the pin count for\n- `side` (string): Optional. The side ('in' or 'out') to get pins for\n\nReturns:\n- (number|undefined): pin count or undefined if not found", + "moveCableOnConnect": "Async: true\n\nShortens a cable segment and moves its endpoint to the equipment's offset geometry.\n\nParameters:\n- `connRec` (MywFeature): Connection record containing cable and equipment info\n\nReturns:\n- (MywFeature|undefined): updated cable feature or undefined if no update", + "truncateLine": "Async: false\n\nShortens a line by trimming coordinates from the start and/or end.\n\nParameters:\n- `coords` (Array>): Coordinates of the line\n- `startTruncDist` (number|null): Distance to trim from start (or null)\n- `endTruncDist` (number|null): Distance to trim from end (or null)\n\nReturns:\n- (Array>): truncated line coordinates", + "handleConnect": "Async: true\n\nHandles a connection event by moving the cable endpoint and firing update events.\n\nParameters:\n- `event` (Object): The connection event object", + "fixCableSegmentChain": "Async: true\n\nFixes the segment chain of a cable using its placement geometry.\n\nParameters:\n- `cable` (MywFeature): The cable feature to fix", + "fixCable": "Async: true\n\nCreates new cable segments based on route and copies segment data; deletes old segments.\n\nParameters:\n- `cable` (MywFeature): The cable feature to fix\n\nReturns:\n- (Promise): resolving after fix completes", + "rippleStrandInfo": "Async: true\n\nWrapper for ds.comms.rippleStrandInfo used by the strandInfo dialog.\n\nParameters:\n- `feature` (MywFeature): Feature to ripple strand info on\n- `direction` (string): Direction of ripple\n- `sourceRanges` (Array): Source ranges for ripple\n- `strandInfoToRipple` (Object): Strand info to apply" +} \ No newline at end of file diff --git a/public/Samples/live_docs/function_descriptions/circuitDescriptions.json b/public/Samples/live_docs/function_descriptions/circuitDescriptions.json new file mode 100644 index 0000000..e47b335 --- /dev/null +++ b/public/Samples/live_docs/function_descriptions/circuitDescriptions.json @@ -0,0 +1,8 @@ +{ + "traceLogicalCircuit": "Async: true\n\nRuns a trace on a logical circuit returning a trace result that references the features the circuit passes through.\n\nParameters:\n- `circuit` (MywFeature): The circuit to be traced\n\nReturns:\n- (Promise): containing the trace result", + "routeCircuit": "Async: true\n\nSet route for the provided circuit. Updates the features the circuit passes through to reference the circuit, and sets the circuit's geometry\n\nParameters:\n- `circuit` (MywFeature): The circuit to be routed\n\nReturns:\n- (Promise): result", + "unrouteCircuit": "Async: true\n\nUnset the route for the provided circuit\n\nParameters:\n- `circuit` (MywFeature): The circuit to be unrouted\n\nReturns:\n- (Promise): result", + "routeCircuits": "Async: true\n\nRoute multiple circuits\n\nParameters:\n- `circuits` (Array): The circuits to be routed\n\nReturns:\n- (Promise>): with individual results for each circuit", + "isCircuitFeature": "Async: false\n\nDetermines if feature is a circuit feature\n\nParameters:\n- `feature` (MywFeature): Feature to be checked\n\nReturns:\n- (boolean): if the feature is a circuit", + "getDetachedCircuitPath": "Async: true\n\nReturns a detached feature with reference to logical circuit\n\nParameters:\n- `featureType` (string): Type of the feature to be created\n- `logicalCircuit` (MywFeature): Logical circuit to which the detached feature will be related\n\nReturns:\n- (Promise): for a newly created detached feature" +} \ No newline at end of file diff --git a/public/Samples/live_docs/function_descriptions/conduitDescriptions.json b/public/Samples/live_docs/function_descriptions/conduitDescriptions.json new file mode 100644 index 0000000..a340023 --- /dev/null +++ b/public/Samples/live_docs/function_descriptions/conduitDescriptions.json @@ -0,0 +1,9 @@ +{ + "disconnectConduit": "Async: true\n\nDisconnects the conduit at structure.\n\nParameters:\n- `conduit` (MywFeature): Conduit to disconnect\n- `housing` (MywFeature): Structure to disconnect conduit at", + "connectConduits": "Async: true\n\nConnects two conduits at a structure.\n\nParameters:\n- `housing` (MywFeature): Structure to connect the conduit to\n- `first_conduit` (MywFeature): First conduit to connect\n- `second_conduit` (Feature): Second conduit to connect", + "routeNestedConduits": "Async: true\n\nRoute new conduits for all conduits that are part of the conduit hierarchy.\n\nParameters:\n- `conduitsJson` (Object): Serialized conduit hierarchy\n- `structures` (Object): Serialized structures where conduits are being routed\n- `parentConduits` (Object): Serialzied parent conduits in the hierarchy, used to set housing\n- `transaction` (Transaction): Current update transaction, if not provided a new one will be created\n\nReturns:\n- (): conduits created in a single array", + "moveInto": "Async: true\n\nMove conduit to a new housing in the same route\n\nParameters:\n- `housing` (MywFeature): New conduit housing\n- `feature` (MywFeature): Conduit to be moved", + "isContinuousConduitType": "Async: false\n\nCheck if the conduit is continuous\n\nParameters:\n- `feature` (MywFeature): Conduit to be checked\n\nReturns:\n- (boolean): if the conduit is continuous, false otherwise", + "continuousPhysicalConduits": "Async: true\n\nReturns conduits connected to FEATURE that are in the same run and considered to be the same physical conduit. We do this based on name.\n\nParameters:\n- `feature` (MywFeature): \n\nReturns:\n- (): conduits in the same run AND parent bundle (run)", + "deleteContinuousPhysicalConduits": "Async: true\n\n\n\nParameters:\n- `conduit` (MywFeature): Conduit to be deleted\n\nReturns:\n- (): " +} \ No newline at end of file diff --git a/public/Samples/live_docs/function_descriptions/connectionDescriptions.json b/public/Samples/live_docs/function_descriptions/connectionDescriptions.json new file mode 100644 index 0000000..aef622c --- /dev/null +++ b/public/Samples/live_docs/function_descriptions/connectionDescriptions.json @@ -0,0 +1,16 @@ +{ + "freePinsOn": "Async: true\n\nReturns list of pins on side of feature that are not connected\n\nParameters:\n- `feature` (MywFeature): Feature to check\n- `tech` (string): Type (e.g.: 'fiber' or 'copper')\n- `side` (string): Side to check: 'in' or 'out'\n\nReturns:\n- (Promise>): of free pins", + "usedPinsOn": "Async: true\n\nReturns list of pins on side of feature that are connected\n\nParameters:\n- `feature` (MywFeature): Feature to check\n- `tech` (string): Type (e.g.: 'fiber' or 'copper')\n- `side` (string): Side to check: 'in' or 'out'\n\nReturns:\n- (Promise>): of used pins", + "highPinUsedOn": "Async: true\n\nReturns the highest pin in use on the side of the feature\n\nParameters:\n- `feature` (MywFeature): Feature to check\n- `tech` (string): Type (e.g.: 'fiber' or 'copper')\n- `side` (string): Side to check: 'in' or 'out'\n\nReturns:\n- (Promise): pin number in use", + "pinStateFor": "Async: true\n\nreturns list of state of pins on the side of the feature\n\nParameters:\n- `feature` (MywFeature): Feature to check\n- `tech` (string): Type (e.g.: 'fiber' or 'copper')\n- `side` (string): Side to check: 'in' or 'out'\n\nReturns:\n- (Promise>): of pins states, keyed by pin number (true means free, false means connected)", + "pinCountFor": "Async: true\n\nReturns number of pins (if any) on the side of the feature\n\nParameters:\n- `feature` (MywFeature): Feature to check\n- `tech` (string): Type (e.g.: 'fiber' or 'copper')\n- `side` (string): Side to check: 'in' or 'out'\n\nReturns:\n- (Promise): of pins on feature", + "traceOut": "Async: true\n\nPerform network trace starting on the pin range of the feature\n\nParameters:\n- `tech` (string): Type (e.g.: 'fiber' or 'copper')\n- `feature` (MywFeature): Feature to start trace from\n- `pins` (PinRange): Range of pins on feature to start from\n- `direction` (string): Direction to perform the trace: 'upstream', 'downstream' or 'both'\n- `maxDist` (number): Maximum distance to trace\n\nReturns:\n- (Promise): object containing the trace results", + "connect": "Async: true\n\nConnect two sets of pins. Raises the event 'connected'\n\nParameters:\n- `tech` (string): Type (e.g.: 'fiber' or 'copper')\n- `fromFeature` (MywFeature): Feature where the connection starts\n- `fromPins` (PinRange|Array): Pins on the fromFeature to connect. It can be a Vector of the form [side,low,high] or a PinRange object\n- `toFeature` (MywFeature): Feature where the connection ends\n- `toPins` (PinRange|Array): Pins on the toFeature to connect. It can be a Vector of the form [side,low,high] or a PinRange object\n- `toFeature` (MywFeature): Feature where the connection ends\n- `housing` (MywFeature): Feature where the connection is housed\n- `ripple` (boolean): If true, ripples line of count information in both directions\n\nReturns:\n- (Promise): record created", + "disconnect": "Async: true\n\nDisconnect a set pins of a feature. Raises the event 'disconnected'\n\nParameters:\n- `tech` (string): Type (e.g.: 'fiber' or 'copper')\n- `feature` (MywFeature): Feature where the connection starts\n- `pins` (PinRange|Array): Pins on the feature to disconnect. It can be a Vector of the form [side,low,high] or a PinRange object\n- `ripple` (boolean): If true, ripples line of count information in both directions\n\nReturns:\n- (Promise): request result", + "moveConns": "Async: true\n\nMove fiber connection records to new housing and root housing\n\nParameters:\n- `conns` (Array): Array of fiber connection urns\n- `housingUrn` (string): URN of the conections new housing\n- `rootHousingUrn` (string): URN of the conections new root housing\n\nReturns:\n- (Promise): result", + "switchConnSides": "Async: true\n\nSwitch the sides of connections of a feature\n\nParameters:\n- `feature` (MywFeature): Feature to check\n\nReturns:\n- (Promise): result", + "techFor": "Async: false\n\nDetermines the tech for a feature on a side\n\nParameters:\n- `feature` (MywFeature): Feature to check\n- `side` (string): Side to check: 'in' or 'out'\n\nReturns:\n- (string): tech type of the side", + "fixConnectionSegments": "Async: true\n\nFix a connection which has become invalid after a split due to in_object or out_object segments being adjusted.\n\nParameters:\n- `conn` (MywFeature): Connection to fix", + "_fixConnectionSegmentsSide": "Async: true\n\nFix the in or out side of a connection which has become invalid after a split of a cable\n\nParameters:\n- `conn` (MywFeature): Connection to fix\n- `side` (string): Side to fix: 'in' or 'out'\n\nReturns:\n- (Promise): result", + "isConnection": "Async: false\n\nCheck if a feature is a connection\n\nParameters:\n- `feature` (MywFeature): Feature to check\n\nReturns:\n- (boolean): if the feature is a connection" +} \ No newline at end of file diff --git a/public/Samples/live_docs/function_descriptions/equipmentDescriptions.json b/public/Samples/live_docs/function_descriptions/equipmentDescriptions.json new file mode 100644 index 0000000..f690dcd --- /dev/null +++ b/public/Samples/live_docs/function_descriptions/equipmentDescriptions.json @@ -0,0 +1,10 @@ +{ + "isEquipment": "Async: false\n\nDetermines whether the specified feature is an equipment type.\n\nParameters:\n- `feature` (MywFeature): - The feature to check.\n\nReturns:\n- (Boolean): True if the feature is an equipment type, false otherwise.", + "fireFeatureEvents": "Async: false\n\nFires events to update the display and other parts of the application when features have changed.
This triggers 'featureCollection-modified' events for relevant feature types.\n\nParameters:\n- `aspect` (String): - An aspect describing the context of the update.\n- `type` (String): - Optional. A specific equipment feature type to fire an event for if equipment features are in separate layers.", + "moveAssembly": "Async: true\n\nMoves an existing equipment feature and all of its children, to a different already-existing housing feature.\n\nParameters:\n- `equip` (MywFeature): - The equipment feature to be moved along with its children.\n- `housing` (MywFeature): - The existing housing feature to which the equipment will be moved.", + "copyAssembly": "Async: true\n\nCopy an equipment feature, and all of its children, to an already-existing housing feature.\n\nParameters:\n- `equip` (MywFeature): - The equipment feature to be copied along with its children.\n- `housing` (MywFeature): - The existing housing feature to which the equipment will be copied.", + "connectionsIn": "Async: true\n\nTakes a housing feature and returns an array of all connections of the given housing feature as well as the connections of any contained equipment.\n\nParameters:\n- `housing` (MywFeature): - The housing feature.", + "connectionsOf": "Async: true\n\nTakes a housing feature and returns an array of its connections.\n\nParameters:\n- `housing` (MywFeature): - The housing feature.", + "equipmentWithPortInfo": "Async: true\n\nReturns an array of equipment types with port information.\n\nReturns:\n- (Array): array of equipment types.", + "ripplePortInfo": "Async: true\n\nTakes attribute values from ranges of ports and copies (\"ripples\") those values down or up the circuit.\n\nParameters:\n- `feature` (MywFeature): \n- `direction` (String): - Possible values: 'upstream', 'downstream', or 'both'.\n- `side` (String): - Possible values: 'in' or 'out'.\n- `sourceRanges` (Array): - An array of pin range objects.\n- `portInfoToRipple` (Array): - An array of port info attribute names with values to be copied." +} \ No newline at end of file diff --git a/public/Samples/live_docs/function_descriptions/structureDescriptions.json b/public/Samples/live_docs/function_descriptions/structureDescriptions.json new file mode 100644 index 0000000..857a605 --- /dev/null +++ b/public/Samples/live_docs/function_descriptions/structureDescriptions.json @@ -0,0 +1,15 @@ +{ + "fireFeatureEvents": "Async: false\n\nFires events to update the display and other parts of the application when features have changed. This triggers 'featureCollection-modified' events for relevant feature types: routes, equipment, conduits, cables, circuits, and a specific structure type.\n\nParameters:\n- `aspect` (String): - An aspect describing the context of the update.\n- `structFeatureType` (String): - Optional. A specific structure feature type to fire an event for if structures are in separate layers.", + "structContent": "Async: true\n\nThe equipment, cables etc housed within (or connected to) 'struct' Returns a StructContent", + "getStructuresAtCoords": "Async: true\n\nGets the structure at each given a coordinate array. For each coordinate, finds a structure at that location and returns an array containing the structure (or returns 0 if none found) for each coordinate.\n\nParameters:\n- `coords` (Array>): - An array of coordinates [lon, lat].\n- `featureTypes` (Array): - Optional list of feature types to limit the search.\n\nReturns:\n- (Promise>): promise that resolves to an array of structures.", + "getStructureAt": "Async: true\n\nGets a single structure at the given coordinate (or empty array if none found). If multiple structures are found, one is returned at random.\n\nParameters:\n- `coord` (Array): - The coordinate [lon, lat].\n- `featureTypes` (Array): - Optional list of feature types to limit the search.\n\nReturns:\n- (Promise): promise that resolves to the found structure or 0.", + "getStructuresAt": "Async: true\n\nGets all structures at the given coordinate (or empty array if none found). Searches within a specified tolerance radius (in meters) around the coordinate and returns all matching structures.\n\nParameters:\n- `coord` (Array): - The coordinate [lon, lat].\n- `featureTypes` (Array): - Optional list of feature types to limit the search.\n- `tolerance` (Number): - The search radius in meters.\n\nReturns:\n- (Promise>): promise that resolves to an array of found structures.", + "routeContent": "Async: true\n\nGets the cables and conduits housed within a route. Calls the comms API to retrieve the route content, then returns an instance of `RouteContents`.\n\nParameters:\n- `route` (MywFeature): - The route feature.\n- `includeProposed` (Boolean): - Whether to include proposed items.\n\nReturns:\n- (Promise): promise that resolves to the route content.", + "validateRoutesForConduit": "Async: false\n\nValidates whether the given routes can contain the specified conduit. Checks each route type against the housings configured for the conduit.\n\nParameters:\n- `routes` (Array): - The routes to validate.\n- `conduit` (MywFeature): - The conduit feature to check compatibility with.\n\nReturns:\n- (Boolean): if invalid routes exist; `false` if all are valid.", + "isStructure": "Async: false\n\nChecks if the specified feature is a structure. A feature is considered a structure if its type is configured in `myw.config['mywcom.structures']`.\n\nParameters:\n- `feature` (MywFeature): - The feature to check.\n\nReturns:\n- (Boolean): if the feature is a structure; otherwise, `false`.", + "isRoute": "Async: false\n\nChecks if the specified feature is a route. A feature is considered a route if its type is configured in `myw.config['mywcom.routes']`.\n\nParameters:\n- `feature` (MywFeature): - The feature to check.\n\nReturns:\n- (Boolean): if the feature is a route; otherwise, `false`.", + "isConduit": "Async: false\n\nChecks if the specified feature is a conduit type. A feature is considered a conduit if its type is configured in `myw.config['mywcom.conduit’]`.\n\nParameters:\n- `feature` (MywFeature): - The feature to check.\n\nReturns:\n- (Boolean): if the feature is a conduit; otherwise, `false`.", + "fixRouteEnds": "Async: true\n\nUpdates the start and end points of the given route so that it begins at struct1 and ends at struct2. This method recalculates the route geometry to connect to the new structures, splits the existing geometry at the nearest points, and adjusts the coordinates accordingly. It then updates the route and corresponding conduits to reflect the new geometry. **Note:** This is an advanced operation that may produce invalid data if used incorrectly. Use only for specialized data editing tasks.\n\nParameters:\n- `route` (MywFeature): - The route feature to update.\n- `struct1` (MywFeature): - The structure feature to set as the new starting point.\n- `struct2` (MywFeature): - The structure feature to set as the new ending point.\n\nReturns:\n- (Promise): promise resolving when the route and related features are updated.", + "houseInStructure": "Async: true\n\nTransfers equipment, routes, segments, and other contained items that overlap with the specified structure so that they are housed within it.\n\nParameters:\n- `toStructure` (MywFeature): - The structure that will receive the features.\n\nReturns:\n- (Promise): promise resolving when the operation is complete.", + "transferToStructure": "Async: true\n\nTransfers equipment, routes, segments, and other contained items from one structure to another. Moves all assets housed in one structure into another structure.\n\nParameters:\n- `fromStructure` (MywFeature): - The structure from which contents will be moved.\n- `toStructure` (MywFeature): - The structure that will receive the contents.\n\nReturns:\n- (Promise): promise resolving when the transfer is complete." +} \ No newline at end of file diff --git a/public/Samples/live_docs/liveDocs_modal.js b/public/Samples/live_docs/liveDocs_modal.js index 70c9a73..d410d14 100644 --- a/public/Samples/live_docs/liveDocs_modal.js +++ b/public/Samples/live_docs/liveDocs_modal.js @@ -3,39 +3,62 @@ import React, { useState, useEffect, useRef } from 'react'; import { DraggableModal, Button, Input } from 'myWorld-client/react'; import { Alert, Space, Select } from 'antd'; import { useLocale } from 'myWorld-client/react'; -import { Classes, ConduitMenu, EquipmentMenu, StructureMenu, CableMenu } from './classes_dictionary'; - -export const LiveDocsModal = ({ open, plugin}) => { +import { + Classes, + ConduitMenu, + EquipmentMenu, + StructureMenu, + CableMenu, + ConnectionMenu, + CircuitMenu, + StructureDescriptions, + EquipmentDescriptions, + ConduitDescriptions, + CableDescriptions, + ConnectionDescriptions, + CircuitDescriptions +} from './classes_dictionary'; +import PinRange from 'modules/comms/js/api/pinRange'; + + +export const LiveDocsModal = ({ open, plugin }) => { const { msg } = useLocale('LiveDocsPlugin'); const [showIntro, setShowIntro] = useState(true); const [appRef] = useState(myw.app); + const [db] = useState(appRef.database); const [isOpen, setIsOpen] = useState(open); const [pickedClass, setPickedClass] = useState(''); const [pickedFunction, setPickedFunction] = useState(''); const [paramValues, setParamValues] = useState({}); - // const [selectedFeature, setSelectedFeature] = useState(null); const [activeParam, setActiveParam] = useState(null); - - // const [selectedFeatureId, setSelectedFeatureId] = useState(null); - // const [disabled, setDisabled] = useState(true); - - + const [rawInput, setRawInput] = useState({}); const ApiFunctionMenus = { structureApi: StructureMenu, equipmentApi: EquipmentMenu, conduitApi: ConduitMenu, - cableApi: CableMenu - // TODO: Add others + cableApi: CableMenu, + connectionApi: ConnectionMenu, + circuitApi: CircuitMenu }; const apiInstances = { structureApi: plugin.structureApi, equipmentApi: plugin.equipmentApi, conduitApi: plugin.conduitApi, cableApi: plugin.cableApi, - // TODO: Add others + connectionApi: plugin.connectionApi, + circuitApi: plugin.circuitApi + }; + const ApiFunctionDictionaries = { + structureApi: StructureDescriptions, + equipmentApi: EquipmentDescriptions, + conduitApi: ConduitDescriptions, + cableApi: CableDescriptions, + connectionApi: ConnectionDescriptions, + circuitApi: CircuitDescriptions }; + const transactionMessage = "Transaction parameter is automatically created for you. It will be committed after the function execution."; const getSelectedFunctionParams = () => { if (!pickedClass || !pickedFunction) return []; @@ -46,33 +69,53 @@ export const LiveDocsModal = ({ open, plugin}) => { for (const group of menu) { const found = group.options.find(opt => opt.value === pickedFunction); if (found && found.params) { - // return Object.keys(found.params); - // return Object.entries(found.params).map(([type, name]) => ({ - // name, - // type - // })); return found.params; } - } return []; - }; - // useEffect(() => { - // setOnFunctions(); - // updateFeatures(); - // }, []); + const allParamsFilled = React.useMemo(() => { + if (!pickedFunction || !pickedClass) return false; + const paramMeta = getSelectedFunctionParams(); + return paramMeta.every( + ({ name, optional }) => + optional || (paramValues[name] !== undefined && paramValues[name] !== '') + ); + }, [pickedFunction, pickedClass, paramValues]); - useEffect(() => { + useEffect(() => { function listener() { const feature = appRef.currentFeature; - console.log('Listener triggered, feature:', feature); if (feature && activeParam) { - console.log('Updating paramValues for activeParam:', activeParam, 'with feature:', feature); - setParamValues(prev => ({ ...prev, [activeParam]: feature })); + const paramMeta = getSelectedFunctionParams().find(p => p.name === activeParam); + const type = paramMeta.type.toLowerCase(); + + if (type === 'myworldfeature') { + setParamValues(prev => ({ ...prev, [activeParam]: feature })); + } + else if (type === 'array') { + setParamValues(prev => { + const current = Array.isArray(prev[activeParam]) ? prev[activeParam] : []; + const alreadyExists = current.some(f => f.id === feature.id); + return alreadyExists + ? prev + : { ...prev, [activeParam]: [...current, feature] }; + }); + } + else if (type === 'array') { + const coords = feature.getGeometry().coordinates; + setParamValues(prev => ({ ...prev, [activeParam]: [coords[0], coords[1]] })); + } + else if (type === 'array>') { + const coords = feature.getGeometry().coordinates; + setParamValues(prev => { + const current = Array.isArray(prev[activeParam]) ? prev[activeParam] : []; + return { ...prev, [activeParam]: [...current, coords] }; + }); + } } } @@ -83,9 +126,23 @@ export const LiveDocsModal = ({ open, plugin}) => { appRef.off('currentFeature-changed', listener); appRef.off('currentFeatureSet-changed', listener); }; - }, [activeParam]); + useEffect(() => { + if (pickedFunction) { + const paramMeta = getSelectedFunctionParams(); + paramMeta.forEach(({ name, type }) => { + if (type.toLowerCase() === 'transaction' && !paramValues[name]) { + try { + const trans = new myw.Transaction(db); + setParamValues(prev => ({ ...prev, [name]: trans })); + } catch (err) { + console.error('Failed to create transaction:', err); + } + } + }); + } + }, [pickedFunction, paramValues, db]); const handleParamChange = (paramName, value) => { setParamValues(prev => ({ ...prev, [paramName]: value })); @@ -99,14 +156,17 @@ export const LiveDocsModal = ({ open, plugin}) => { setIsOpen(false); }; - // function setOnFunctions() { - // appRef.on('currentFeature-changed currentFeatureSet-changed', updateFeatures); - // } - const executeFunction = () => { console.log('Executing function:', pickedFunction, 'from class:', pickedClass); - if (!pickedClass || !pickedFunction) return; + if (pickedFunction.startsWith('list')){ + const feature = pickedFunction.slice(4); + console.log(myw.config[`mywcom.${feature.toLowerCase()}`]); + return; + } + const paramMeta = getSelectedFunctionParams(); + + if (!pickedClass || !pickedFunction) return; const apiInstance = apiInstances[pickedClass]; if (!apiInstance) { @@ -114,12 +174,11 @@ export const LiveDocsModal = ({ open, plugin}) => { return; } - const paramMeta = getSelectedFunctionParams(); const params = paramMeta.map(({ name }) => paramValues[name]); console.log('Executing function:', pickedFunction, 'with params:', params); - const fn = apiInstance[pickedFunction]; + const fn = apiInstance[pickedFunction]; if (typeof fn !== 'function') { console.warn(`${pickedFunction} is not a function on ${pickedClass}`); return; @@ -131,11 +190,16 @@ export const LiveDocsModal = ({ open, plugin}) => { result.then(res => { console.log('Function result:', res); }); - } else { - console.log('Function result:', result); + } else { + console.log('Function result:', result); } }; - + + const currentDictionary = pickedClass ? ApiFunctionDictionaries[pickedClass] : null; + const currentDescription = + pickedFunction && currentDictionary && currentDictionary[pickedFunction] + ? currentDictionary[pickedFunction].body + : null; return ( { key="execute" onClick={executeFunction} type="primary" - disabled={!pickedFunction} + disabled={!allParamsFilled} > Execute @@ -184,75 +248,237 @@ export const LiveDocsModal = ({ open, plugin}) => { /> {pickedClass && ApiFunctionMenus[pickedClass] && ( setActiveParam(name)} - /> - ); - } - if (type.toLowerCase() === 'boolean') { - return ( - handleParamChange(name, e.target.value)} - /> - ); - } - if (type.toLowerCase() === 'string') { - return ( - handleParamChange(name, e.target.value)} - /> - ); - } - // TODO - add array types - // TODO - add object type - return ( - handleParamChange(name, e.target.value)} - /> - ); - })} + {currentDescription && ( +

+ {currentDescription} +

+ )} + {pickedFunction && + getSelectedFunctionParams().map(({ name, type }) => { + console.log('paramValues', paramValues); + if (type.toLowerCase() === 'myworldfeature') { + return ( + setActiveParam(name)} + /> + ); + } + if (type.toLowerCase() === 'boolean') { + return ( + handleParamChange(name, e.target.value)} + /> + ); + } + if (type.toLowerCase() === 'string') { + return ( + handleParamChange(name, e.target.value)} + /> + ); + } + if (type.toLowerCase() === 'array' ) { + return ( + { + const value = e.target.value; + setRawInput(prev => ({ ...prev, [name]: value })); + const arrayValue = value + .split(',') + .map(item => item.trim()) + .filter(Boolean); + handleParamChange(name, arrayValue); + }} + /> + ); + } + if (type.toLowerCase() === 'array') + { + const coords = paramValues[name] || []; + return ( + setActiveParam(name)} + /> + ); + } + if (type.toLowerCase() === 'array>'){ + const coordsArray = Array.isArray(paramValues[name]) ? paramValues[name] : []; + return ( + `[${c[0]}, ${c[1]}]`).join('; ')} + readOnly + onFocus={() => setActiveParam(name)} + /> + ); + } + if (type.toLowerCase() === 'array') { + const features = Array.isArray(paramValues[name]) + ? paramValues[name] + : []; + return ( + f.id).join(', ')} + readOnly + onFocus={() => setActiveParam(name)} + /> + ); + } + if ( + type.toLowerCase() === 'object' || + type.toLowerCase() === 'array' || + type.toLowerCase() === 'array' + ) { + const raw = + rawInput[name] ?? + JSON.stringify(paramValues[name] || {}, null, 2); + + return ( +
+ + { + const value = e.target.value; + setRawInput(prev => ({ + ...prev, + [name]: value + })); + + try { + const parsed = JSON.parse(value); + handleParamChange(name, parsed); + } catch (err) { + console.error( + 'Failed to read object:', + err + ); + } + }} + autoSize={{ minRows: 6, maxRows: 12 }} + /> +
+ ); + } + if (type.toLowerCase() === 'transaction') { + return ( +
+

{transactionMessage}

+ +
+ ); + } + if (type.toLowerCase() === 'pinrange') { + const pinRange = paramValues[name] || new PinRange('in', 1, 1); + + const update = (field, newValue) => { + const updated = { + side: pinRange.side, + low: pinRange.low, + high: pinRange.high, + [field]: newValue + }; + handleParamChange( + name, + new PinRange(updated.side, updated.low, updated.high) + ); + }; + + return ( +
+ + + + update('low', Number(e.target.value)) + } + /> + + + update('high', Number(e.target.value)) + } + /> +
+ ); + } + + return ( + handleParamChange(name, e.target.value)} + /> + ); + })} )} diff --git a/public/Samples/live_docs/liveDocs_modal_tsx.tsx b/public/Samples/live_docs/liveDocs_modal_tsx.tsx new file mode 100644 index 0000000..a3f28df --- /dev/null +++ b/public/Samples/live_docs/liveDocs_modal_tsx.tsx @@ -0,0 +1,540 @@ +import myw from 'myWorld-client'; +import React, { useState, useEffect, useRef } from 'react'; +import { DraggableModal, Button, Input } from 'myWorld-client/react'; +import { Space, Select, InputRef } from 'antd'; +import { useLocale } from 'myWorld-client/react'; +import { + Classes, + ConduitMenu, + EquipmentMenu, + StructureMenu, + CableMenu, + ConnectionMenu, + CircuitMenu, + StructureDescriptions, + EquipmentDescriptions, + ConduitDescriptions, + CableDescriptions, + ConnectionDescriptions, + CircuitDescriptions +} from './classes_dictionary'; +import PinRange from 'modules/comms/js/api/pinRange'; + + + + +interface LiveDocsModalProps { + open: boolean; + plugin: { + structureApi?: any; + equipmentApi?: any; + conduitApi?: any; + cableApi?: any; + connectionApi?: any; + circuitApi?: any; + [key: string]: any; // fallback + }; +} + +export const LiveDocsModal: React.FC = ({ open, plugin }) => { + const { msg } = useLocale('LiveDocsPlugin'); + const [showIntro, setShowIntro] = useState(true); + const [appRef] = useState(myw.app); + const [db] = useState(appRef.database); + const [isOpen, setIsOpen] = useState(open); + const [pickedClass, setPickedClass] = useState(''); + const [pickedFunction, setPickedFunction] = useState(''); + const [paramValues, setParamValues] = useState>({}); + const [activeParam, setActiveParam] = useState(null); + const [rawInput, setRawInput] = useState>({}); + const inputRef = useRef(null); + + interface ApiParam { + name: string; + type: string; + } + + interface ApiMenuOption { + value: string; + label: string | React.ReactNode; + params?: ApiParam[]; + } + + interface ApiMenuGroup { + label: string | React.ReactNode; + options: ApiMenuOption[]; + } + + type ApiFunctionMenu = ApiMenuGroup[]; + + type ApiKey = + | "structureApi" + | "equipmentApi" + | "conduitApi" + | "cableApi" + | "connectionApi" + | "circuitApi"; + + // Menus + const ApiFunctionMenus: Record = { + structureApi: StructureMenu, + equipmentApi: EquipmentMenu, + conduitApi: ConduitMenu, + cableApi: CableMenu, + connectionApi: ConnectionMenu, + circuitApi: CircuitMenu, + }; + + // API instances + const apiInstances: Record = { + structureApi: plugin.structureApi, + equipmentApi: plugin.equipmentApi, + conduitApi: plugin.conduitApi, + cableApi: plugin.cableApi, + connectionApi: plugin.connectionApi, + circuitApi: plugin.circuitApi, + }; + + // Dictionaries + type ApiFunctionDictionary = Record; + + const ApiFunctionDictionaries: Partial> = { + structureApi: StructureDescriptions, + equipmentApi: EquipmentDescriptions, + conduitApi: ConduitDescriptions, + cableApi: CableDescriptions, + connectionApi: ConnectionDescriptions, + circuitApi: CircuitDescriptions + }; + + + const transactionMessage = "Transaction parameter is automatically created for you. It will be committed after the function execution."; + + const getSelectedFunctionParams = () => { + if (!pickedClass || !pickedFunction) return []; + + const menu = ApiFunctionMenus[pickedClass]; + if (!menu) return []; + + for (const group of menu) { + const found = group.options.find(opt => opt.value === pickedFunction); + if (found && found.params) { + return found.params; + } + } + return []; + }; + + const allParamsFilled = React.useMemo(() => { + if (!pickedFunction || !pickedClass) return false; + const paramMeta = getSelectedFunctionParams(); + return paramMeta.every( + ({ name }) => paramValues[name] !== undefined && paramValues[name] !== '' + ); + }, [pickedFunction, pickedClass, paramValues]); + + useEffect(() => { + function listener() { + const feature = appRef.currentFeature; + if (feature && activeParam) { + const paramMeta = getSelectedFunctionParams().find(p => p.name === activeParam); + const isArrayMywFeature = + paramMeta && paramMeta.type.toLowerCase() === 'array'; + + setParamValues(prev => { + if (isArrayMywFeature) { + const current = Array.isArray(prev[activeParam]) ? prev[activeParam] : []; + const alreadyExists = current.some(f => f.id === feature.id); + return alreadyExists + ? prev + : { ...prev, [activeParam]: [...current, feature] }; + } else { + return { ...prev, [activeParam]: feature }; + } + }); + } + } + + appRef.on('currentFeature-changed', listener); + appRef.on('currentFeatureSet-changed', listener); + + return () => { + appRef.off('currentFeature-changed', listener); + appRef.off('currentFeatureSet-changed', listener); + }; + }, [activeParam]); + + useEffect(() => { + if (pickedFunction) { + const paramMeta = getSelectedFunctionParams(); + paramMeta.forEach(({ name, type }) => { + if (type.toLowerCase() === 'transaction' && !paramValues[name]) { + try { + const trans = new myw.Transaction(db); + setParamValues(prev => ({ ...prev, [name]: trans })); + } catch (err) { + console.error('Failed to create transaction:', err); + } + } + }); + } + }, [pickedFunction, paramValues, db]); + + const handleParamChange = (paramName: string, value: any) => { + setParamValues(prev => ({ ...prev, [paramName]: value })); + }; + + const hideIntro = () => { + setShowIntro(false); + }; + + const handleCancel = () => { + setIsOpen(false); + }; + + const executeFunction = () => { + console.log('Executing function:', pickedFunction, 'from class:', pickedClass); + + if (pickedFunction.startsWith('list')){ + const feature = pickedFunction.slice(4); + console.log(myw.config[`mywcom.${feature.toLowerCase()}`]); + return; + } + + if (!pickedClass || !pickedFunction) return; + + const apiInstance = apiInstances[pickedClass]; + if (!apiInstance) { + console.warn(`No API instance found for ${pickedClass}`); + return; + } + + const paramMeta = getSelectedFunctionParams(); + + const params = paramMeta.map(({ name }) => paramValues[name]); + console.log('Executing function:', pickedFunction, 'with params:', params); + + const fn = apiInstance[pickedFunction]; + + if (typeof fn !== 'function') { + console.warn(`${pickedFunction} is not a function on ${pickedClass}`); + return; + } + + const result = fn.apply(apiInstance, params); + + if (result && typeof result.then === 'function') { + result.then(res => { + console.log('Function result:', res); + }); + } else { + console.log('Function result:', result); + } + }; + + const parseNestedArray = (input: string): any[][] => { + if (!input || typeof input !== 'string') return []; + + const safeInput = `[${input}]`; + try { + const parsed = JSON.parse(safeInput); + if (Array.isArray(parsed)) { + return parsed; + } + } catch (err) { + console.error('Invalid nested array format:', err); + } + return []; + }; + + const currentDictionary = pickedClass ? ApiFunctionDictionaries[pickedClass] : null; + const currentDescription = + pickedFunction && currentDictionary && currentDictionary[pickedFunction] + ? currentDictionary[pickedFunction].body + : null; + + return ( + + OK + + ] + : [ + , + + ] + } + > + {showIntro ? ( +
{msg('description')}
+ ) : ( +
+ {' '} + +

{msg('classSelection')}

+ { + setPickedFunction(value); + setParamValues({}); + }} + options={ApiFunctionMenus[pickedClass].flatMap( + group => group.options + )} + /> + )} + {currentDescription && ( +

+ {currentDescription} +

+ )} + {pickedFunction && + getSelectedFunctionParams().map(({ name, type }) => { + console.log('paramValues', paramValues); + if (type.toLowerCase() === 'myworldfeature') { + return ( + setActiveParam(name)} + /> + ); + } + if (type.toLowerCase() === 'boolean') { + return ( + ) => + handleParamChange(name, e.target.value)} + /> + ); + } + if (type.toLowerCase() === 'string') { + return ( + ) => + handleParamChange(name, e.target.value)} + /> + ); + } + if ( + type.toLowerCase() === 'array' || + type.toLowerCase() === 'array' + ) { + return ( + ) => { + const value = e.target.value; + setRawInput(prev => ({ ...prev, [name]: value })); + const arrayValue = value + .split(',') + .map(item => item.trim()) + .filter(Boolean); + handleParamChange(name, arrayValue); + }} + /> + ); + } + if (type.toLowerCase() === 'array') { + const features = Array.isArray(paramValues[name]) + ? paramValues[name] + : []; + return ( + f.id).join(', ')} + readOnly + onFocus={() => setActiveParam(name)} + /> + ); + } + if (type.toLowerCase().includes('array JSON.stringify(a)) + .join(', ') + : '' + } + onChange={(e: React.ChangeEvent) => { + const raw = e.target.value; + const nestedArray = parseNestedArray(raw); + handleParamChange(name, nestedArray); + }} + /> + ); + } + if ( + type.toLowerCase() === 'object' || + type.toLowerCase() === 'array' || + type.toLowerCase() === 'array' + ) { + const raw = + rawInput[name] ?? + JSON.stringify(paramValues[name] || {}, null, 2); + + return ( +
+ + ) => { + const value = e.target.value; + setRawInput(prev => ({ + ...prev, + [name]: value + })); + + try { + const parsed = JSON.parse(value); + handleParamChange(name, parsed); + } catch (err) { + console.error( + 'Failed to read object:', + err + ); + } + }} + autoSize={{ minRows: 6, maxRows: 12 }} + /> +
+ ); + } + if (type.toLowerCase() === 'transaction') { + return ( +
+

{transactionMessage}

+ +
+ ); + } + if (type.toLowerCase() === 'pinrange') { + const pinRange = paramValues[name] || new PinRange('in', 1, 1); + + const update = (field, newValue) => { + const updated = { + side: pinRange.side, + low: pinRange.low, + high: pinRange.high, + [field]: newValue + }; + handleParamChange( + name, + new PinRange(updated.side, updated.low, updated.high) + ); + }; + + return ( +
+ + + ) => + update('low', Number(e.target.value)) + } + /> + + ) => + update('high', Number(e.target.value)) + } + /> +
+ ); + } + + return ( + ) => + handleParamChange(name, e.target.value)} + /> + ); + })} + + + )} + + ); +}; \ No newline at end of file diff --git a/public/Samples/live_docs/liveDocs_plugin.js b/public/Samples/live_docs/liveDocs_plugin.js index 452cc89..2a4f69b 100644 --- a/public/Samples/live_docs/liveDocs_plugin.js +++ b/public/Samples/live_docs/liveDocs_plugin.js @@ -7,6 +7,8 @@ import StructureManagerPlugin from '../../../../comms/public/js/api/structureMan import EquipmentManagerPlugin from '../../../../comms/public/js/api/equipmentManagerPlugin'; import CableManagerPlugin from '../../../../comms/public/js/api/cableManagerPlugin'; import ConduitManagerPlugin from '../../../../comms/public/js/api/conduitManagerPlugin'; +import ConnectionManagerPlugin from '../../../../comms/public/js/api/connectionManagerPlugin'; +import CircuitManagerPlugin from '../../../../comms/public/js/api/circuitManagerPlugin'; export class LiveDocsPlugin extends Plugin { static { @@ -33,8 +35,11 @@ export class LiveDocsPlugin extends Plugin { this.equipmentApi = new EquipmentManagerPlugin(owner, options); this.cableApi = new CableManagerPlugin(owner, options); this.conduitApi = new ConduitManagerPlugin(owner, options); + this.connectionApi = new ConnectionManagerPlugin(owner, options); + this.circuitApi = new CircuitManagerPlugin(owner, options); } + showModal() { this.renderRoot = renderReactNode( null, diff --git a/public/Samples/reporting_samples/conduit_capacity.py b/public/Samples/reporting_samples/conduit_capacity.py new file mode 100644 index 0000000..b338f20 --- /dev/null +++ b/public/Samples/reporting_samples/conduit_capacity.py @@ -0,0 +1,114 @@ +#! /bin/env python3 + +import argparse +import json +from pathlib import Path +from utils import ( + iqgeo_jwt_auth, + iqgeo_get_request, + get_all_features, + BASE_URL, +) + + +def get_cable_segments(conduit_id, design): + """Get cable segments related to a specific conduit""" + return iqgeo_get_request( + f"{BASE_URL}/feature/conduit/{conduit_id}/relationship/cable_segments", design + ).get("features", []) + + +def get_cable_diameter(cable_ref, design): + """Get cable diameter from cable properties + ref = e.g. fiber_cable/4 + """ + return ( + iqgeo_get_request(f"{BASE_URL}/feature/{cable_ref}", design) + .get("properties", {}) + .get("diameter") + ) + + +def calc_fill_ratio(conduit_diameter, cable_diameters): + """ + Calculate fill ratio and determine if within limits. + + Implementation of: + https://www.corning.com/optical-communications/worldwide/en/home/Resources/system-design-calculators/fill-ratio-calculator.html + """ + if not conduit_diameter or conduit_diameter == 0: + return None, None + ratio = sum(d**2 for d in cable_diameters) / (conduit_diameter**2) + + if len(cable_diameters) == 1: + limit = 0.65 + elif len(cable_diameters) == 2: + limit = 0.31 + elif len(cable_diameters) == 3: + limit = 0.40 + else: + limit = 1.0 + + return ratio, limit + + +def main(token_file, design): + """script entrypoint.""" + + iqgeo_jwt_auth(token_file) + + capacity_report = {} + conduits = get_all_features(feature_type="conduit", design=design) + + for conduit in conduits: + cid = conduit["properties"].get("id") + conduit_d = conduit["properties"].get("diameter") + + segments = get_cable_segments(cid, design) + cable_refs = { + seg["properties"].get("cable") + for seg in segments + if seg["properties"].get("cable") + } + + cable_diameters = [] + for cref in cable_refs: + d = get_cable_diameter(cref, design) + if d: + cable_diameters.append(d) + + # use the printed values to test + ratio, limit = calc_fill_ratio(conduit_d, cable_diameters) + if ratio is None: + status = "No diameter data" + else: + percent = f"{ratio*100:.1f}%" + if ratio <= limit and ratio > 0: + status = f"{percent} (OK), cable count: {len(cable_refs)}" + elif ratio == 0: + status = f"{percent} (EMPTY), cable count: {len(cable_refs)}" + else: + status = f"{percent} (OVERFILL), cable count: {len(cable_refs)}" + + capacity_report[f"conduit/{cid}"] = status + + print(json.dumps(capacity_report, indent=2)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Conduit capacity report") + parser.add_argument( + "--token_file", + type=Path, + default="token.txt", + help="Path to the pre-generated JWT token", + ) + parser.add_argument( + "--design", + type=str, + default=None, + help="Design ID to use, e.g. design/2FMyDesign", + ) + args = parser.parse_args() + + main(token_file=args.token_file, design=args.design) diff --git a/public/Samples/reporting_samples/pole_attachment.py b/public/Samples/reporting_samples/pole_attachment.py new file mode 100644 index 0000000..5dd5d48 --- /dev/null +++ b/public/Samples/reporting_samples/pole_attachment.py @@ -0,0 +1,88 @@ +#! /bin/env python3 + +import argparse +import json +from pathlib import Path +from utils import ( + iqgeo_jwt_auth, + iqgeo_get_request, + get_all_features, + BASE_URL, +) + + +def get_pole_equipment(pole_id, design): + """Get equipment attached to a specific pole""" + return iqgeo_get_request( + f"{BASE_URL}/feature/pole/{pole_id}/relationship/equipment", design + ).get("features", []) + + +def get_pole_routes(pole_id, design): + """Get routes associated with a specific pole""" + return iqgeo_get_request( + f"{BASE_URL}/feature/pole/{pole_id}/relationship/routes", design + ).get("features", []) + + +def main(token_file, design): + """script entrypoint.""" + + iqgeo_jwt_auth(token_file) + + # custom report section + attachment_report = {} + poles = get_all_features(feature_type="pole", design=design) + + for pole in poles: + pid = pole["properties"].get("id") + + equipment = get_pole_equipment(pid, design) + routes = get_pole_routes(pid, design) + + equip_list = [ + { + "id": e["properties"].get("name"), + "root_housing": e["properties"].get("root_housing"), + } + for e in equipment + if e.get("properties") + ] + + route_list = [ + { + "id": r["properties"].get("id"), + "in_structure": r["properties"].get("in_structure"), + "out_structure": r["properties"].get("out_structure"), + } + for r in routes + if r.get("properties") + ] + + attachment_report[f"pole/{pid}"] = { + "equipment_count": len(equip_list), + "equipment": equip_list, + "route_count": len(route_list), + "routes": route_list, + } + + print(json.dumps(attachment_report, indent=2)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Conduit capacity report") + parser.add_argument( + "--token_file", + type=Path, + default="token.txt", + help="Path to the pre-generated JWT token", + ) + parser.add_argument( + "--design", + type=str, + default=None, + help="Design ID to use, e.g. design/2FMyDesign", + ) + args = parser.parse_args() + + main(token_file=args.token_file, design=args.design) diff --git a/public/Samples/reporting_samples/spatial_query.py b/public/Samples/reporting_samples/spatial_query.py new file mode 100755 index 0000000..79b02c5 --- /dev/null +++ b/public/Samples/reporting_samples/spatial_query.py @@ -0,0 +1,64 @@ +#! /bin/env python3 + +import argparse +from pathlib import Path +from utils import query_spatial, iqgeo_jwt_auth + + +def report_features(feature_type, geometry, tolerance=0): + """ + Generic report function: prints properties of features returned by query_spatial. + """ + results = query_spatial(feature_type, geometry, tolerance) + print(f"--- {feature_type.upper()} spatial query ---") + for f in results.get("features", []): + props = f.get("properties", {}) + print(props) + + +def main(token_file): + + iqgeo_jwt_auth(token_file) + + # Point + point = {"type": "Point", "coordinates": [0.14208, 52.23095]} + report_features("manhole", point, tolerance=60) + + # LineString + line = { + "type": "LineString", + "coordinates": [ + [0.13422048802249265, 52.220846611354546], + [0.135095125230265, 52.22157378945272], + [0.14540334946042321, 52.22735251836545], + ], + } + report_features("pole", line, tolerance=25) + + # Polygon + polygon = { + "type": "Polygon", + "coordinates": [ + [ + [0.1400, 52.2300], + [0.1450, 52.2300], + [0.1450, 52.2350], + [0.1400, 52.2350], + [0.1400, 52.2300], + ] + ], + } + report_features("pole", polygon, tolerance=10) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Conduit capacity report") + parser.add_argument( + "--token_file", + type=Path, + default="token.txt", + help="Path to the pre-generated JWT token", + ) + args = parser.parse_args() + + main(token_file=args.token_file) diff --git a/public/Samples/reporting_samples/utils.py b/public/Samples/reporting_samples/utils.py new file mode 100644 index 0000000..dc5646d --- /dev/null +++ b/public/Samples/reporting_samples/utils.py @@ -0,0 +1,102 @@ +import requests +from pathlib import Path + + +BASE_URL = "http://host.docker.internal" +# BASE_URL = "http://localhost" +LOGIN_URL = f"{BASE_URL}/auth" +SESSION = requests.Session() +HEADERS = {} + + +# Authentication helpers +def iqgeo_jwt_auth(token_file: Path): + """ + Prompt user for JWT token and then set the auth header. + """ + if not token_file.exists(): + raise FileNotFoundError(f"Token file not found: {token_file}") + + token = token_file.read_text().strip() + response = SESSION.post(LOGIN_URL, data={"id_token": token}) + response.raise_for_status() + cookies = response.cookies.get_dict() + HEADERS[ + "cookie" + ] = f"myworldapp={cookies['myworldapp']}; csrf_token={cookies['csrf_token']}" + + +def iqgeo_interactive_ropc_auth(): + """ + Prompt user for credentials and then send AUTH request. + Raises exception if not authorized, returns the auth cookie on success. + """ + user = input("Enter username: ") + password = input("Enter password: ") + + uauth = {"user": user, "pass": password} + + response = SESSION.post(LOGIN_URL, data=uauth) + response.raise_for_status() + cookies = response.cookies.get_dict() + HEADERS[ + "cookie" + ] = f"myworldapp={cookies['myworldapp']}; csrf_token={cookies['csrf_token']}" + + +def get_all_features(feature_type, design=None): + """Get all features of a specific type in the design""" + return iqgeo_get_request(f"{BASE_URL}/feature/{feature_type}", design).get( + "features", [] + ) + + +def iqgeo_get_request(endpoint, params=None, design=None): + """ + Hit a GET endpoint using the auth cookie for this session. + + Raises HTTP errors, and returns the request body JSON. + """ + if design is not None: + params = params or {} + params["design"] = design + r = SESSION.get(endpoint, headers=HEADERS, params=params) + r.raise_for_status() + return r.json() + + +def iqgeo_post_request(endpoint, params=None, design=None, data=None): + """ + Hit a POST endpoint using the auth cookie for this session. + + Raises HTTP errors, and returns the request body JSON. + """ + if design is not None: + params = params or {} + params["design"] = design + r = SESSION.post(endpoint, headers=HEADERS, params=params, json=data) + r.raise_for_status() + return r.json() + + +def query_spatial(feature_type, geometry, tolerance=0): + """ + General spatial query for any feature type. + Supports Point, LineString, and Polygon geometries. + """ + geom_type = geometry.get("type") + + if geom_type == "Point": + lon, lat = geometry["coordinates"] + url = f"{BASE_URL}/feature/{feature_type}" + params = {"lat": lat, "lon": lon, "tolerance": tolerance} + return iqgeo_get_request(url, params=params) + + elif geom_type in ["LineString", "Polygon"]: + url = f"{BASE_URL}/feature/{feature_type}/get" + data = {"geometry": geometry, "tolerance": tolerance} + # print(geometry, tolerance) + return iqgeo_post_request(url, data=data) + + else: + raise ValueError(f"Unsupported geometry type: {geom_type}") diff --git a/tools/extractDescriptions.js b/tools/extractDescriptions.js new file mode 100644 index 0000000..0374e96 --- /dev/null +++ b/tools/extractDescriptions.js @@ -0,0 +1,58 @@ +const fs = require('fs'); +const { parse } = require('comment-parser'); + +const args = process.argv.slice(2); +const filePath = args[0]; // argument for the input file path +const outputPath = args[1] || './functionDescriptions.json'; //argument for the output file path, defaults to functionDescriptions.json + +if (!fs.existsSync(filePath)) { + console.error(`Input file not found: ${filePath}`); + process.exit(1); +} + +const code = fs.readFileSync(filePath, 'utf8'); +const lines = code.split('\n'); +const comments = parse(code); + +const descriptions = {}; + +for (const block of comments) { + const commentLine = block.source[block.source.length - 1]?.number; + + // search next 5 lines for function definition + for (let offset = 1; offset <= 5; offset++) { + const checkLine = lines[commentLine + offset]?.trim(); + if (!checkLine) continue; + + // match function definition + const match = checkLine.match(/^(async\s+)?(\w+)\s*\(/); + + if (match) { + // extract async status and function name + const isAsync = !!match[1]; + const funcName = match[2]; + if (!descriptions[funcName]) { + let fullDesc = `Async: ${isAsync}\n\n${block.description.trim()}`; + + // add parameters if available + const params = block.tags.filter(tag => tag.tag === 'param'); + if (params.length > 0) { + fullDesc += '\n\nParameters:'; + for (const param of params) { + fullDesc += `\n- \`${param.name}\` (${param.type}): ${param.description}`; + } + } + + // add returns if available + const returns = block.tags.find(tag => tag.tag === 'returns'); + if (returns) { + fullDesc += `\n\nReturns:\n- (${returns.type}): ${returns.description}`; + } + descriptions[funcName] = fullDesc; + } + break; + } + } +} + +fs.writeFileSync(outputPath, JSON.stringify(descriptions, null, 2));