Skip to content

Commit

Permalink
GITBOOK-410: added docs for Improvements Performance/Cloud infrastruc…
Browse files Browse the repository at this point in the history
…ture/updated user journey/demo experience
  • Loading branch information
vaness453 authored and gitbook-bot committed Aug 23, 2023
1 parent 0253843 commit b567f3d
Show file tree
Hide file tree
Showing 126 changed files with 700 additions and 93 deletions.
Binary file modified docs/.gitbook/assets/0 (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/0 (4) (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/0 (4).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/0 (6).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/1 (1) (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/1 (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/1 (3).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/11 (5) (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/11 (5).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/11 (7).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/12 (3) (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/12 (3).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/13 (2) (2).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/13 (2).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/14 (4) (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/14 (4).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/15 (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/15 (4).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/2 (1) (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/2 (1) (3).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/2 (3) (2).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/2 (3).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/3 (1) (1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/3 (1) (2).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/.gitbook/assets/3 (2).png
Binary file modified docs/.gitbook/assets/3 (6).png
Binary file modified docs/.gitbook/assets/4 (1).png
Binary file modified docs/.gitbook/assets/6 (1) (2).png
Binary file modified docs/.gitbook/assets/6 (1).png
Binary file modified docs/.gitbook/assets/6 (2).png
Binary file modified docs/.gitbook/assets/7 (1).png
Binary file modified docs/.gitbook/assets/7 (5) (1).png
Binary file modified docs/.gitbook/assets/7 (5).png
Binary file modified docs/.gitbook/assets/7 (6).png
Binary file modified docs/.gitbook/assets/8 (1) (3).png
Binary file modified docs/.gitbook/assets/8 (1).png
Binary file modified docs/.gitbook/assets/8 (4).png
Binary file modified docs/.gitbook/assets/image (1) (1).png
Binary file modified docs/.gitbook/assets/image (1) (9).png
Binary file modified docs/.gitbook/assets/image (113) (1).png
Binary file modified docs/.gitbook/assets/image (113).png
Binary file modified docs/.gitbook/assets/image (12) (4).png
Binary file modified docs/.gitbook/assets/image (12).png
Binary file modified docs/.gitbook/assets/image (13) (6).png
Binary file modified docs/.gitbook/assets/image (13).png
Binary file modified docs/.gitbook/assets/image (14) (6).png
Binary file modified docs/.gitbook/assets/image (14).png
Binary file modified docs/.gitbook/assets/image (145) (1).png
Binary file modified docs/.gitbook/assets/image (145).png
Binary file modified docs/.gitbook/assets/image (154) (1).png
Binary file modified docs/.gitbook/assets/image (154).png
Binary file modified docs/.gitbook/assets/image (161) (1).png
Binary file modified docs/.gitbook/assets/image (161).png
Binary file modified docs/.gitbook/assets/image (167) (1).png
Binary file modified docs/.gitbook/assets/image (167).png
Binary file modified docs/.gitbook/assets/image (17) (1).png
Binary file modified docs/.gitbook/assets/image (17).png
Binary file modified docs/.gitbook/assets/image (179) (1).png
Binary file modified docs/.gitbook/assets/image (179).png
Binary file modified docs/.gitbook/assets/image (189) (2).png
Binary file modified docs/.gitbook/assets/image (189).png
Binary file modified docs/.gitbook/assets/image (20) (4).png
Binary file modified docs/.gitbook/assets/image (20).png
Binary file modified docs/.gitbook/assets/image (201) (1).png
Binary file modified docs/.gitbook/assets/image (201).png
Binary file modified docs/.gitbook/assets/image (203) (1).png
Binary file modified docs/.gitbook/assets/image (203).png
Binary file modified docs/.gitbook/assets/image (220) (1).png
Binary file modified docs/.gitbook/assets/image (220).png
Binary file added docs/.gitbook/assets/image (223) (1).png
Binary file modified docs/.gitbook/assets/image (223).png
Binary file modified docs/.gitbook/assets/image (25) (4).png
Binary file modified docs/.gitbook/assets/image (25).png
Binary file modified docs/.gitbook/assets/image (27) (3).png
Binary file modified docs/.gitbook/assets/image (27).png
Binary file modified docs/.gitbook/assets/image (47) (2).png
Binary file modified docs/.gitbook/assets/image (47).png
Binary file modified docs/.gitbook/assets/image (5) (3).png
Binary file modified docs/.gitbook/assets/image (5).png
Binary file modified docs/.gitbook/assets/image (51) (2).png
Binary file modified docs/.gitbook/assets/image (51).png
Binary file modified docs/.gitbook/assets/image (6) (3).png
Binary file modified docs/.gitbook/assets/image (6).png
Binary file modified docs/.gitbook/assets/image (66) (1).png
Binary file modified docs/.gitbook/assets/image (66).png
Binary file modified docs/.gitbook/assets/image (7) (4).png
Binary file modified docs/.gitbook/assets/image (7) (6).png
Binary file modified docs/.gitbook/assets/image (7).png
Binary file modified docs/.gitbook/assets/image (96) (1).png
Binary file modified docs/.gitbook/assets/image (96).png
4 changes: 4 additions & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@
* [📱 Mobile operation for the Standard Registry](guardian/standard-registry/mobile-support-for-data-interface/mobile-operation-for-the-standard-registry.md)
* [Fixing UX issues For Standard Registries](guardian/standard-registry/fixing-ux-issues-for-standard-registries/README.md)
* [Improvements on the UX](guardian/standard-registry/fixing-ux-issues-for-standard-registries/improvements-on-the-ux.md)
* [Demo Experience](guardian/standard-registry/demo-experience.md)
* [🛠 Standard Registry Operations](guardian/standard-registry/standard-registry-operations/README.md)
* [⚙ Settings APIs](guardian/standard-registry/standard-registry-operations/settings-apis/README.md)
* [Displaying Current Settings](settings-apis/displaying-current-settings.md)
Expand Down Expand Up @@ -443,3 +444,6 @@
* [📈 Guardian in Production](guardian-in-production/README.md)
* [📄 API Architecture Customization](guardian-in-production/api-architecture-customization.md)
* [📉 Monitoring tools](guardian-in-production/monitoring-tools.md)
* [Performance Improvement](guardian-in-production/performance-improvement.md)
* [Cloud Infrastructure](guardian-in-production/cloud-infrastructure.md)
* [Independent Packaged Deployment](guardian-in-production/independent-packaged-deployment.md)
265 changes: 265 additions & 0 deletions docs/guardian-in-production/cloud-infrastructure.md

Large diffs are not rendered by default.

194 changes: 194 additions & 0 deletions docs/guardian-in-production/independent-packaged-deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Independent Packaged Deployment

## Introduction

Providing customers with a deployable solution that is easy to use and customizable. Customers expect the solution to work seamlessly right out of the box without requiring any additional configuration or setup. However, at the same time, they also want the flexibility to choose which components of the solution they want to deploy.

For example, in the case of a service standalone, customers may not want to deploy the front-end code, as it may not be relevant to their specific needs. Therefore, the solution must be designed in such a way that customers can easily select which components they want to deploy and which ones they want to exclude.

This level of extensibility is critical in ensuring that customers can tailor the solution to their unique requirements, without having to deal with unnecessary complexity or bloat. By providing customers with the ability to customize the solution to their needs, businesses can increase customer satisfaction and improve the overall user experience.

## Dependency tree

An important step for Guardian project to work as a deployable modular solution out of the box is to catalog its services and determine: 

* which ones are more likely to be replaced by others with exactly the same functionality but deployed using different infrastructure. An example of this could be an external mongodb DBaaS.
* which ones could be replaced with others with some compatibility (these are the ones that require more effort from a development perspective). An example of this could be to replace Vault with AWS secrets manager.
* which ones are optional, and aren’t required to provide the core functionality of Guardian. An example of this could be the frontend or api-docs.

For this purpose, a services dependency tree can be found below. In that tree, services are ordered based on the number of dependents, from the ones required by the highest number of other services, to the ones with less dependent services. A service with no dependents is more likely to be optional.

Furthermore, the services are grouped into external and internal ones. External services are more likely to be replaced by client managed ones or SaaS (we can also refer to them as third-party services).

### External (third-party services)

#### with dependants

*   mongo:6.0.3
*   mongo-express:1.0.0-alpha.4
*   mongo
*   ipfs/kubo:v0.18.1
*   message-broker:2.9.8
*   vault:1.12.2

### Internal ( Guardian services ) 

#### without dependencies but with dependants

*   api-docs
*   mrv-sender
*   topic-viewer

#### with dependencies and dependants

*   logger-service
*   message-broker
*   auth-service
*   mongo
*   vault
*   logger-service
* policy-service
*   auth-service
* worker-service-1
*   ipfs-node
*   auth-service
*   worker-service-2
*   ipfs-node
*   auth-service
* guardian-service
*   worker-service-1
*   worker-service-2
*   policy-service
*   api-gateway
*   guardian-service

#### with dependencies but without dependants 

*   application-events
*   guardian-service
*   web-proxy
*   mongo-express (optional)
*   api-docs (optional)
*   mrv-sender (optional)
*   api-gateway (optional)

## Roadmap Proposal

### Initial phase

Dependency on the first phase of Cloud Infrastructure topic.

#### Make internal services optional

Using configuration flags when installing or upgrading a Guardian instance, the user will be able to select which services to install. Take this yaml snippet as example:

```
internal_services:
logger_service: true
auth_service: true
proxy_service: true
guardian_service: true
worker_service: true
api_gateway: true
application_events: false
web_proxy: false
```

#### Direct replacement of external services 

Using configuration items, the user will be able to replace external services.

The replacement services must guarantee a secure way for Guardian to reach them.

It’s out of the scope of this document the setup and maintenance of those services.

For a production workload it’s recommended to replace the external servers using this approach.

```
mongodb:
use_internal: true|false
host: <needed when use_internal==false>
username: <needed when use_internal==false>
password: <needed when use_internal==false>
```

### Second phase

Dependency on 1 click deployment on different cloud providers, Cloud Infrastructure topic.

#### External services replacement with cloud provider SaaS

Using the IaC codebase settings, the user will be able to replace some external services with the managed equivalents provided by the cloud provider.

Those replacements will be deployed and configured as part of the Guardian 1 click deployment.

Find below a couple examples of the use case for this milestone, for better understanding of it.

#### **Use case 1: a client wants to use AWS DocumentDB as a replacement for MongoDB.**

DocumentDB is a fully compatible direct replacement for MongoDB, so no backend development is needed. With the previous milestone the clients are capable of configuring the DocumentDB on their own, and then add the connection settings to the values.yml used by Helm to set up the Guardian instance. After finishing this milestone, which would add an additional module to the AWS terraform project, the DocumentDB itself will be created by terraform at the same time of spawning the Kubernetes cluster, by modifying the terraform variables; and the connection settings passed automatically to Helm to tight everything together. Additionally, the internal MongoDB instance won’t be installed.

#### **Use case 2: a client wants to use GCP Secret Manager as a replacement for Vault.**

GCP Secret Manager is a “same features replacement” for Vault, so the backend has to have logic to use the specific driver depending on the configuration provided. An additional module will be added to the GCP terraform project, which will register a vault for Guardian at the same time the Kubernetes cluster is created, and the configuration will be passed to the backend services to use the Secret Manager driver to fetch secrets instead of the default one (Hashicorp Vault). Additionally, the internal Vault instance won’t be installed.

**NOTE**: For now, the only service that supports this “equal features replacement” is Vault, which has an ongoing development on backend side to allow using different keyrings for the secrets storage purpose.

### Third phase with specific tools added

#### Security analysis tool - SonarQube Open source integration

Integrating a Node.js application with SonarQube is important for analyzing code security as it provides a powerful static analysis tool for identifying potential security vulnerabilities in the code. SonarQube is an open-source platform that supports various programming languages and provides a range of security-focused features, such as code scanning, vulnerability detection, and code quality analysis.

By integrating a Node.js application with SonarQube, developers can gain insights into the security and quality of their codebase, enabling them to identify and fix potential issues early on in the development process. This can help reduce the risk of security breaches and ensure that the codebase is reliable and maintainable.

SonarQube provides a range of security-focused plugins and rulesets for Node.js applications, including those for detecting common vulnerabilities such as SQL injection, cross-site scripting (XSS), and command injection. It also provides detailed reports and visualizations that help developers understand the severity and impact of any security issues that are identified.

In addition to security analysis, SonarQube can also help developers identify areas of the codebase that require refactoring or optimization, improving the overall quality and performance of the application.

Overall, integrating a Node.js application with SonarQube is an important step in ensuring the security and quality of the codebase. By using SonarQube's powerful static analysis tools, developers can identify and address potential security vulnerabilities and code quality issues, helping to ensure the reliability and maintainability of the application.

To add SonarQube to a Node.js project pipeline with GitHub, you can follow these general steps:

* Install and configure SonarQube:
* Install SonarQube server locally or use a cloud-based SonarQube service
* Install and configure SonarScanner for your Node.js project
* Create a SonarQube project:
* Create a new SonarQube project and generate a project key
* Assign permissions to the project to allow access from your pipeline
* Add SonarQube to your pipeline:
* Define the SonarScanner command in your pipeline script
* Add a SonarQube step to your pipeline configuration to run the scanner
* Pass the SonarQube project key and access token as environment variables
* Configure your GitHub repository:
* Add a webhook to trigger the pipeline when code is pushed to the repository
* Create a personal access token for the pipeline to access the repository
* Store the access token securely as a GitHub secret

Here's a sample pipeline configuration using GitHub Actions and SonarCloud:

```
name: Node.js CI with SonarCloud
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_PROJECT_KEY: your_project_key
```

Note: You'll need to replace your\_project\_key with the actual project key for your SonarQube project, and configure the SONAR\_TOKEN secret with an access token generated in your SonarCloud account.
55 changes: 55 additions & 0 deletions docs/guardian-in-production/performance-improvement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Performance Improvement

This report aims to analyze and address the current inefficiencies in the token minting process. Specifically, the focus is on reducing the processing time required to mint a given quantity of tokens. The current scenario reveals that minting 1,000 tokens takes approximately 7 minutes, while processing 10,000 tokens consumes approximately 1 hour and 15 minutes. Additionally, the batch quantity processed is set to 10 out of 10. This report will explore potential solutions to optimize the minting process and enhance its efficiency.

Current Scenario

* **Minting 1,000 Tokens:**
* **Processing time: Approximately 7 minutes**
* **Minting 10,000 Tokens:**
* **Processing time: Approximately 1 hour and 15 minutes**
* **Batch quantity processed: 10 out of 10**

## Issues and Challenges

The current minting process suffers from the following issues and challenges:

1. Lengthy Processing Time: The time taken to mint both 1,000 and 10,000 tokens is significantly high, affecting the overall efficiency of the system.
2. Inconsistent Batch Processing: The current batch quantity processed is set to 10 out of 10, which suggests that the system might be configured to process a fixed batch size. This approach may result in suboptimal performance and limited scalability.

## Optimization Strategies

To address the aforementioned issues and enhance the efficiency of the minting process in a Node.js environment, the following strategies can be considered, including the utilization of Promise.all:

**Performance Evaluation:** Conduct a thorough evaluation of the existing Node.js infrastructure, including hardware and software components involved in the minting process. Identify potential bottlenecks and areas for improvement specific to Node.js.

**System Scaling:** Explore options for scaling up the Node.js infrastructure to handle larger minting volumes. This may involve upgrading hardware components, optimizing network configurations, and leveraging cloud-based services to distribute the processing load.&#x20;

**Batch Size Optimization**: Reconsider the batch quantity processed during each minting operation, taking advantage of Promise.all. By using Promise.all in Node.js, it can concurrently process multiple tokens within a batch, significantly reducing overall processing time. Dynamically adjust the batch size based on system capabilities and workload, considering factors such as available memory and CPU resources.

**Parallel Processing:** Utilize Promise.all in Node.js to implement parallel processing techniques. With Promise.all, we can execute multiple asynchronous minting tasks concurrently, taking advantage of Node.js's event-driven, non-blocking architecture. This approach allows for efficient resource utilization and can significantly reduce processing time. Carefully design the codebase to leverage Promise.all effectively, considering error handling, load balancing, and concurrency limits.

**Asynchronous Operations:** Maximize the use of asynchronous operations in the Node.js codebase. By utilizing asynchronous APIs and non-blocking I/O operations, it can ensure that the minting process remains responsive and efficient. Avoid blocking or synchronous operations that may introduce unnecessary delays.

The topics above are indicated regardless of the kind of application which handles asynchronous tasks, process and so on.&#x20;

## Code Analysis

At the time when this issue was addressed the current functionalities were not developed or at least improved. Nowadays, the mint process contains a lot of these best practices, many which were pointed out above. 1000 tokens are minted in 4 minutes because we can define an environment variable `BATCH_NFT_MINT_SIZE` that allows us to increase the number of tokens of each interaction.

According to the history, one month before this issue was created on github ([https://github.com/hashgraph/guardian/issues/1756](https://github.com/hashgraph/guardian/issues/1756)) a developer, Simonov Valery was working on this topic, available at that time on the release v2.9.0, 3e6b8f89e32bb1ab8b9d4a7a099dc7adb6c620cc.

Considering all strategies above, this particular operation **already covers the best practices to mint the tokens.**&#x20;

However, some changes were applied in the main function which handles directly the minting actions, but no difference was found in terms of performance, according to [this draft PR](https://github.com/hashgraph/guardian/pull/2401/files) that uses Promise.allSettled vs Promise.all.&#x20;

V2:\


<figure><img src="https://lh4.googleusercontent.com/Jtf1mhdOt51sngHt0cYjCkx4v00ZqeN2LRu4A6x0bJNO9mq12moZwVG2S2kRyj23TbieMDpoQYDKgdcBVXGb8BOZMJWw4ju7l8tp9M9Ax91FdfawL7XbMxKI0i0V2bYszp_JqYHWm3AV3oF3NFlxDw8" alt=""><figcaption></figcaption></figure>

Current version:

<figure><img src="https://lh3.googleusercontent.com/sj0gOqV872K1mmoKltwSC8EPMfiA20ji6vUQ9tZBz-0Fi-aV749p1dqdFeZG0tskaytxl1cxIcifGOJxp3o1L_HPTVFiblVTB3rbyVutgL3iEun7eExQtYibcNApbUFs-sHbCKO00VukhqSlNRuYluM" alt=""><figcaption></figcaption></figure>

In both scenarios where BATCH\_NFT\_MINT\_SIZE=50, 1000 tokens were processed in 4 minutes.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Next, fill out the Organization data form.

Once completed, add a new entity by clicking on the "New Entity" button and filling out the New Entity form.

<figure><img src="../../../.gitbook/assets/image (20).png" alt=""><figcaption></figcaption></figure>
<figure><img src="../../../.gitbook/assets/image (20) (4).png" alt=""><figcaption></figcaption></figure>

<figure><img src="../../../.gitbook/assets/image (16) (1) (2).png" alt=""><figcaption></figcaption></figure>

Expand All @@ -51,7 +51,7 @@ Once the form has been submitted, you'll need to define a product. Click on the

The atma GHG policy issues an NFT token (AtmaProductToken) for every product that has been defined. Once the product has been defined, you can go out of the Policy screen and click over to the Profile screen. There you can click on the "Tokens" tab and notice that the token balance has been increased.

<figure><img src="../../../.gitbook/assets/image (27).png" alt=""><figcaption></figcaption></figure>
<figure><img src="../../../.gitbook/assets/image (27) (3).png" alt=""><figcaption></figcaption></figure>

Once the product has been defined, you'll need to define the life cycle stage. Click on the "life cycle stage" button and fill out the life cycle stage form.

Expand Down
Loading

0 comments on commit b567f3d

Please sign in to comment.