Skip to content

Commit

Permalink
Release 0.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sudoplatform-engineering committed Nov 22, 2021
1 parent cc09170 commit 37cac7a
Show file tree
Hide file tree
Showing 62 changed files with 3,869 additions and 2,816 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Sudo Decentralized Identity Cloud Agent Admin Console

The Sudo Decentralized Cloud Agent Admin Console implements a Sample Web UI to control the process of
creating, issuing and holding Verifiable Credentials. This Web UI utilises the
creating, issuing, holding, verifying and revoking Verifiable Credentials. This Web UI utilises the
[Sudo Decentralized Identity Cloud Agent SDK](https://sudoplatform-labs.github.io/sudo-di-cloud-agent-js/) to create a local, standalone,
Decentralized Identity development environment.

Expand All @@ -11,7 +11,7 @@ _NOTE:_ Currently only MacOS is supported as a development environment and Chrom

| Technology | Supported version |
| -------------- | ----------------- |
| docker desktop | 3.1.0 |
| docker desktop | 4.1.1 |
| yarn | 1.22.10 |
| node | 12.6.2 |

Expand All @@ -25,25 +25,25 @@ Before begining these instructions you must have [yarn](https://yarnpkg.com) and
This will initialise the `node_modules` directory with project dependencies, including the [Sudo Decentralized Identity Cloud Agent SDK](https://sudoplatform-labs.github.io/sudo-di-cloud-agent-js/).
4. Edit `public/acapy.json` and add element `"endorserSeed": “<your unique/secret 32 byte seed>”,`
5. Start the local development environment using the installed
[Sudo Decentralized Identity Cloud Agent SDK](https://sudoplatform-labs.github.io/sudo-di-cloud-agent-js/), command `yarn di-env start -c $PWD/public/acapy.json`.
[Sudo Decentralized Identity Cloud Agent SDK](https://sudoplatform-labs.github.io/sudo-di-cloud-agent-js/), command `yarn di-env up -c $PWD/public/acapy.json`.
This will download and start local docker instances of both a [VON Network](https://github.com/bcgov/von-network) Indy Ledger and the
Sudo Cloud Agency Service, which is derived from the [hyperledger Aries Cloud Agent Python](https://github.com/hyperledger/aries-cloudagent-python). See `yarn di-env -h` for other command options.
6. Run `yarn start` to begin serving the Web UI.
7. Browse to the web page url at `localhost:3000`
8. Stop the local development environment when finished using `yarn di-env down`.
**NOTE:** This will destroy all credentials, schemas and DIDs created.
**NOTE:** This will destroy all credentials, schemas and DIDs created. Use `yarn di-env stop` if you want to pause the containers whilst maintaining state and `yarn di-env start` to resume.

**_Troubleshooting_**

It is possible that the `yarn di-env down/start` commands could fail. Instances of this occuring usually relate to a change of the `node_modules/@sudoplatform-labs/sudo-di-cloud-agent`
It is possible that the `yarn di-env up/start` commands could fail. Instances of this occuring usually relate to a change of the `node_modules/@sudoplatform-labs/sudo-di-cloud-agent`
package whilst the environment is running (e.g. performing a `yarn upgrade`). The simplest way to recover from such errors is to manualy terminate the docker containers related to the VON-network and sudo-di-cloud-agent (i.e. via the docker desktop user interface).

## Using a Public Ledger and Public Cloud Agent Endpoint

**IMPORTANT** : When using a public ledger, information written is persistent and immutable. Personally Identifiable Information (PII) **MUST NOT** be written and is a major reason why the Transaction Authorisation Agreement must be signed in the acceptance/setup process.
It is recommended that as much development activity as possible is performed with the local VON Ledger before using a Public ledger.

The Web UI can support using a public ledger such as [Sovrin BuilderNet or StagingNet](https://sovrin.org/overview).
The Web UI can support using a public ledger such as [Sovrin BuilderNet/StagingNet](https://sovrin.org/overview) or [Indicio TestNet/DemoNet](https://indicio.tech/indicio-testnet/).
When using these ledgers the UI will automatically display the Transaction Authorisation Agreement and require acceptance before any ledger write operation (e.g Schema Creation, Credential Definition, DID Writes).

For details on starting the Development Environment with public ledgers and/or creating a public Cloud Agent endpoint, refer to the [Sudo Decentralized Identity Cloud Agent SDK](https://www.npmjs.com/package/@sudoplatform-labs/sudo-di-cloud-agent) documentation.
Expand Down
115 changes: 32 additions & 83 deletions e2e/browser-server.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash
#
# This script starts/stops a test bowser server in a docker container
# so that e2e tests can be isolated from browser versions
# This script brings up/shutsdown a test bowser server and webserver in
# docker containers so that e2e tests can be isolated from browser versions
#
# Usage:
function usage() {
Expand All @@ -10,13 +10,14 @@ function usage() {
Commands:
start - Create a local browser server docker container
up - Create local browser server and webserver docker containers
start options :
-i : docker image to use
-p : port to map to the browser control port inside the container
-p : port to map to the browser server control port inside the container
stop - Stop running instance of browser server docker container
down - Bring down running instance of the browser server an webserver
docker environment.
EOF
exit 1
Expand All @@ -28,8 +29,8 @@ exit 1
# Check Programs needed are installed
##########################################################################################

type docker >/dev/null 2>&1 || {
echo >&2 "docker is required but is not installed. Aborting."
type docker-compose >/dev/null 2>&1 || {
echo >&2 "docker-compose is required but is not installed. Aborting."
exit 1
}

Expand All @@ -43,9 +44,6 @@ fi
# Make sure everything is done starting in our commands home directory
cd ${REAL_PWD}
ROOT_DIR="${REAL_PWD}/.."
DEVEL_DIR="${ROOT_DIR}/devel"

BROWSER_DOCKER_NAME_FILE="${DEVEL_DIR}/browser_runner.json"

# Print an indication of script reaching a processing
# milestone in a noticable way
Expand All @@ -70,86 +68,49 @@ function runEval() {
return $returnValue
}

# Pull the value for a specific field out of a simple JSON
# format object and echo it.
# $1 : The field name
# $2 : The JSON file name
function getJSONFieldValue() {
returnValue=`awk -F'"' '/'${1}'/ { print $4 }' ${2}`
echo $returnValue
}

# $1: The name of a variable to return the IP address to
function getHostIP() {
local hostOS="$(uname -s)"
local hostIP
# Start docker browser server and webserver containers
# $1: The browser server docker image to use
# $2: The host port number to map the browser server command port onto
function upBrowserEnv() {
export BROWSER_SERVER_DOCKER_IMAGE=$(cut -d':' -f1 <<<${1})
export BROWSER_SERVER_DOCKER_TAG=$(cut -d':' -f2 <<<${1})
export BROWSER_SERVER_CONTROL_EXTERNAL_PORT=${2}
export FRONTEND_NETWORK="von_von";
export FRONTEND_EXTERNAL="true"

if [ ${hostOS} = "Darwin" ]; then
# Look at wired interface first
hostIP=$(ipconfig getifaddr en1)
if [[ $? != 0 ]]; then
hostIP=$(ipconfig getifaddr en0)
fi
else
hostIP=$(hostname -i | awk '{print $1}')
fi

local result=${1}
if [[ "${result}" ]]; then
eval ${result}="'${hostIP}'"
fi
}


# Start a docker standalone browser container which has
# the host machines address injected into its DNS knowledge
# $1: The browser docker image to use
# $2: The host port number to map the browser command port onto
# $3: The name of a variable to return the container ID to
function startBrowserServer() {
acapyContainer=$(docker ps | grep sudo-di-cloud-agent | awk '{print $1}')
getHostIP DOCKER_HOST_IP
randName=$(cat /dev/urandom | env LC_CTYPE=ALL tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)
browserCmd="docker run -d -e SCREEN_WIDTH=1600 -e SCREEN_HEIGHT=1200 --name browser-server_${randName} --rm --link=${acapyContainer} --add-host react.webserver:${DOCKER_HOST_IP} \
-p ${2}:4444 -p 5900:5900 -v /dev/shm:/dev/shm ${1}"
upCmd="docker-compose -f ${ROOT_DIR}/e2e/docker/docker-compose-test.yml -p browser-environment up -d"

printMilestone "Starting browser server docker image with command: \n \
\t ${browserCmd}"
printMilestone "Starting browser server and webserver docker images with command: \n \
\t ${upCmd}"

containerId=$(${browserCmd})
${upCmd}
local returnStatus=$?
if [[ ${returnStatus} != 0 ]]; then
echo "**** FAIL - Browser Server failed to start, exiting. ****"
echo "**** FAIL - Browser Server and Webserver failed to start, exiting. ****"
exit 1
fi
local result=${3}
if [[ "${result}" ]]; then
eval ${result}="'${containerId}'"
fi
}

# Stop a running browser server docker container
# $1: The docker container identifier
function stopBrowserServer() {
local containerId="${1}"
stopCmd="docker stop ${containerId}"
# Stop a running browser server and webserver docker containers
function downBrowserEnv() {
downCmd="docker-compose -f ${ROOT_DIR}/e2e/docker/docker-compose-test.yml -p browser-environment down"

printMilestone "Stopping Browser Server docker container id : \n \
\t ${containerId}"
${stopCmd}
printMilestone "Stopping Browser Server and Webserver docker containers"
${downCmd}
}


##########################################################################################
# MAIN LINE
##########################################################################################

# Support start, and stop commands
# Support up, and down commands
subCommand=$1
shift || usage;

case "${subCommand}" in
start)
up)
# start comes with several options on how to construct the environment
while getopts ':i:p:' option; do
case ${option} in
Expand All @@ -161,23 +122,11 @@ case "${subCommand}" in
# Remove processed options
shift $((OPTIND -1))

if [ ! -d "${DEVEL_DIR}" ]; then
runEval "mkdir -p ${DEVEL_DIR}"
fi

startBrowserServer ${imageOption} ${hostPortOption} containerId
# Save the browser server docker container name so that it can be killed on stop
# commands and not potentially destroy another instance we didn't
# start.
cat <<EOF > ${BROWSER_DOCKER_NAME_FILE}
{
"browserDockerContainer": "${containerId}"
}
EOF
upBrowserEnv ${imageOption} ${hostPortOption}
;;
stop)
containerId=$(getJSONFieldValue "browserDockerContainer" ${BROWSER_DOCKER_NAME_FILE})
stopBrowserServer ${containerId}
down)
downBrowserEnv
;;
*) usage;
esac
Expand Down
143 changes: 142 additions & 1 deletion e2e/commonHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const commonWaitDefault = 20000;

/**
* Utility function to wait for a element to appear and be
* visible. Returns to element if found.
* visible. Returns the element if found.
*
* @param {?number} wait an optional amount of time to use when waiting for
* elements to resolve when locating them.
Expand All @@ -28,6 +28,26 @@ export async function e2eWaitElementVisible(
}
}

export async function e2eScrollToElement(
locator: Locator,
wait: number = commonWaitDefault,
): Promise<WebElement> {
try {
const element = await driver.wait(until.elementLocated(locator), wait);

// Move will scroll down and bring the element into view
await driver
.actions({ bridge: true })
.pause(200)
.move({ origin: element })
.pause(200)
.perform();
return element;
} catch (e) {
throw `Failed to scroll to element ${locator}: ${e}`;
}
}

/**
* Utility function to test navigation to a specified card
* and verify presence.
Expand Down Expand Up @@ -324,6 +344,7 @@ export async function e2eExecuteTableRowDropdownAction(
.move({ origin: element })
.pause(200)
.click()
.pause(200)
.perform();
}

Expand Down Expand Up @@ -483,3 +504,123 @@ export async function e2eAcceptTAAForm(
)
).click();
}

/**
* Utility function to input a time into an antd DatePicker
* Expects the DatePicker to be currently displaying on entry
*
* @param {!string} locatorPrefix a string that can be used at the front of an XPATH
* to locate the specific DatePicker element of interest
* @param {!Date} date a Date object representing the entryDate Date/Time to
* select in the RangePicker
* @param {?number} wait an optional amount of time to use when waiting for
* elements to resolve when locating them.
*/
export async function e2eEnterDatePickerDetails(
locatorPrefix: string,
entryDate: Date,
wait: number = commonWaitDefault,
): Promise<void> {
const monthNumberFromString = (month: string): number => {
return new Date(Date.parse(month + ' 1, 2000')).getMonth() + 1;
};

// Split the start date up into YY,MM,DD, hh, mm, ss components
// to use in navigating the picker components
const YY = String(entryDate.getUTCFullYear()).padStart(4, '0');
const MM = String(entryDate.getUTCMonth() + 1).padStart(2, '0');
const DD = String(entryDate.getUTCDate()).padStart(2, '0');
const hh = String(entryDate.getUTCHours()).padStart(2, '0');
const mm = String(entryDate.getUTCMinutes()).padStart(2, '0');
const ss = String(entryDate.getUTCSeconds()).padStart(2, '0');

// Calculate the number of moves to get to the requested
// year and month
const yearMoves =
parseInt(YY) -
parseInt(
await (
await driver.wait(
until.elementLocated(
By.xpath(
`${locatorPrefix}//div[@class='ant-picker-date-panel']/div/div/button[@class='ant-picker-year-btn']`,
),
),
wait,
)
).getText(),
);

const monthMoves =
parseInt(MM) -
monthNumberFromString(
await (
await driver.wait(
until.elementLocated(
By.xpath(
`${locatorPrefix}//div[@class='ant-picker-date-panel']/div/div/button[@class='ant-picker-month-btn']`,
),
),
wait,
)
).getText(),
);

// Action the moves to correct year then correct month
const yearMoveButton =
yearMoves > 0
? `${locatorPrefix}//div[@class='ant-picker-date-panel']/div/button[@class='ant-picker-header-super-next-btn']`
: `${locatorPrefix}//div[@class='ant-picker-date-panel']/div/button[@class='ant-picker-header-super-prev-btn']`;

for (let i = Math.abs(yearMoves); i > 0; i--) {
await (
await driver.wait(until.elementLocated(By.xpath(yearMoveButton)), wait)
).click();
}

const monthMoveButton =
monthMoves > 0
? `${locatorPrefix}//div[@class='ant-picker-date-panel']/div/button[@class='ant-picker-header-next-btn']`
: `${locatorPrefix}//div[@class='ant-picker-date-panel']/div/button[@class='ant-picker-header-prev-btn']`;

for (let i = Math.abs(monthMoves); i > 0; i--) {
await (
await driver.wait(until.elementLocated(By.xpath(monthMoveButton)), wait)
).click();
}

// Select Day, Hour, Minute and Seconds from selection Arrays
const dayDate = `${YY}-${MM}-${DD}`;
await (
await e2eWaitElementVisible(
By.xpath(
`${locatorPrefix}//div[@class='ant-picker-date-panel']/div[@class='ant-picker-body']//td[@title='${dayDate}']`,
),
wait,
)
).click();

await (
await e2eScrollToElement(
By.xpath(
`${locatorPrefix}//div[@class='ant-picker-time-panel']/div[@class='ant-picker-content']/ul[1]/li/div[contains(.,'${hh}')]`,
),
)
).click();

await (
await e2eScrollToElement(
By.xpath(
`${locatorPrefix}//div[@class='ant-picker-time-panel']/div[@class='ant-picker-content']/ul[2]/li/div[contains(.,'${mm}')]`,
),
)
).click();

await (
await e2eScrollToElement(
By.xpath(
`${locatorPrefix}//div[@class='ant-picker-time-panel']/div[@class='ant-picker-content']/ul[3]/li/div[contains(.,'${ss}')]`,
),
)
).click();
}
Loading

0 comments on commit 37cac7a

Please sign in to comment.