Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bots for demo users+projects [MARXAN-674] #519

Merged
merged 38 commits into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6ea726f
wip/incomplete bot lib
hotzevzl Aug 13, 2021
d9a9acb
(discard) test different settings for scenario creation
hotzevzl Aug 23, 2021
622bea9
(wip) split library into components
hotzevzl Aug 26, 2021
6c9f730
prepare for option to provide auth info
hotzevzl Aug 27, 2021
9ea8e16
swap arguments
hotzevzl Aug 27, 2021
755f899
add WIP scenario status lib module
hotzevzl Aug 27, 2021
610d7b0
WIP update Brazil bot to use libbot
hotzevzl Aug 27, 2021
76f2512
do not ignore lib within bots
hotzevzl Aug 30, 2021
24397e0
add update functions for projects and scenarios
hotzevzl Aug 30, 2021
6675e07
fix retry delay/interval
hotzevzl Aug 30, 2021
18a8fcb
add planning area uploader
hotzevzl Aug 30, 2021
bca8733
make props optional
hotzevzl Aug 30, 2021
a1da24b
add WIP libbot modules
hotzevzl Aug 31, 2021
5ea47a9
unwrap JSON:API responses
hotzevzl Aug 31, 2021
cd1f16f
fix missing function invocation
hotzevzl Aug 31, 2021
ba8e746
update data types
hotzevzl Aug 31, 2021
2ba5b5d
expose new modules through bot
hotzevzl Aug 31, 2021
ae2ffe3
add scenario editing metadata module
hotzevzl Aug 31, 2021
8d453ec
add marxan runner module
hotzevzl Aug 31, 2021
3f583b9
add utility functions for scenario status checks
hotzevzl Aug 31, 2021
1593988
port Brazil bot to libbot
hotzevzl Aug 31, 2021
73bfdd3
add instrumentation
hotzevzl Sep 1, 2021
b73c134
add marxan run status monitor
hotzevzl Sep 1, 2021
fa1636a
clean up Brazil demo bot
hotzevzl Sep 1, 2021
df0c3b7
port Australia bot to libbot
hotzevzl Sep 1, 2021
89fec87
adjust labels, etc.
hotzevzl Sep 1, 2021
13be94f
add missing per-project libs
hotzevzl Sep 1, 2021
b0e4093
apply deno fmt
hotzevzl Sep 1, 2021
72fb280
add user signup module
hotzevzl Sep 1, 2021
58e788f
split bot core from execution
hotzevzl Sep 1, 2021
d5ec834
move Okavango demo bot to core-demos
hotzevzl Sep 1, 2021
be5783b
add über-bot for demo projects
hotzevzl Sep 1, 2021
3b77578
apply deno fmt
hotzevzl Sep 1, 2021
82184c9
update README
hotzevzl Sep 2, 2021
fb7abea
document DTO prop
hotzevzl Sep 2, 2021
5aac503
use seconds consistently for long intervals
hotzevzl Sep 2, 2021
6c25517
handle failure states
hotzevzl Sep 2, 2021
cc98932
add lookup of protected areas by admin area id
hotzevzl Sep 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/apps/api/src/modules/authentication/dto/sign-up.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsDefined,
IsEmail,
Expand All @@ -16,6 +16,7 @@ import {
export class SignUpDto {
@IsOptional()
@IsString()
@ApiPropertyOptional()
displayName?: string;

@IsEmail()
Expand Down
1 change: 1 addition & 0 deletions data/bots/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!lib
55 changes: 44 additions & 11 deletions data/bots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,52 @@ This folder contains friendly bots for the Marxan API.

They take care of automating things.

How to run them:
1.- Install deno if not already installed:
## Running

1. Install deno if not already installed

`curl -fsSL https://deno.land/x/install/install.sh | sh`
create a .env file with the next info:

2. Configure the bot(s) you wish to run: this is done via an `.env` file in the
bot's directory

```
API_URL=http://localhost:3030
USERNAME=
PASSWORD=
POSTGRES_URL=
API_URL=<base URL of the API instance>
USERNAME=<username to authenticate as>
PASSWORD=<password>
POSTGRES_URL=<only needed if the bot inserts data via PostgreSQL>
```

2.- Run demo cases:
`deno run --allow-all bot.ts`
Individual demo bots (`demo-brazil` and `demo-australia`) are currently
expecting the user whose credentials are configured in `.env` to already exist
in the API instance configured, when run individually.

The `core-demos` bot needs the same `.env` file, but it will first create a new
user with the credentials configured, then run the Brazil and Australia demo
bots using a JWT for the newly created user.

3. Run bot

`OPTIC_MIN_LEVEL=Info deno run --allow-read --allow-net --allow-env ./bot.ts`

For a cleaner output (just errors) set `OPTIC_MIN_LEVEL=Error`; to see debug
output (mostly `inspect` of data from API responses), set
`OPTIC_MIN_LEVEL=Debug`.

## Developing on bots

Things are quite in flux right now, but in general

* `libbot` is where new bot functionality should be added, via classes that take
care of a single domain of the platform.
* `libbot/scenario-status` currently handles waiting for scenario status changes
for all kinds of async operations; it may be advisable to split the
operation-specific waiting code to individual modules if the current module
grows further
* do not forget to run `deno fmt` before committing new code

## Compiling bots

3.- Compile demo cases:
`deno compile `
If wanting to run bots somewhere without having to copy over the source tree,
or if it is not possible or practical to install the Deno runtime, bots can
be compiled to a single binary via `deno compile`.
28 changes: 28 additions & 0 deletions data/bots/core-demos/bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
dirname,
fromFileUrl,
relative,
} from "https://deno.land/[email protected]/path/mod.ts";
import { config } from "https://deno.land/x/[email protected]/mod.ts";
import { runBot as runBrazilBot } from "./demo-brazil/core.ts";
import { runBot as runAustraliaBot } from "./demo-australia/core.ts";
import { Users } from "../lib/libbot/users.ts";

const scriptPath = dirname(relative(Deno.cwd(), fromFileUrl(import.meta.url)));

const { API_URL, USERNAME, PASSWORD, POSTGRES_URL } = config({
path: scriptPath + "/.env",
});

const settings = {
apiUrl: API_URL,
credentials: {
username: USERNAME,
password: PASSWORD,
},
};

await new Users(settings).signUp();

await runBrazilBot(settings);
await runAustraliaBot(settings);
23 changes: 23 additions & 0 deletions data/bots/core-demos/demo-australia/bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
dirname,
fromFileUrl,
relative,
} from "https://deno.land/[email protected]/path/mod.ts";
import { config } from "https://deno.land/x/[email protected]/mod.ts";
import { runBot } from "./core.ts";

const scriptPath = dirname(relative(Deno.cwd(), fromFileUrl(import.meta.url)));

const { API_URL, USERNAME, PASSWORD, POSTGRES_URL } = config({
path: scriptPath + "/.env",
});

const settings = {
apiUrl: API_URL,
credentials: {
username: USERNAME,
password: PASSWORD,
},
};

await runBot(settings);
109 changes: 109 additions & 0 deletions data/bots/core-demos/demo-australia/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
dirname,
fromFileUrl,
relative,
} from "https://deno.land/[email protected]/path/mod.ts";
import { sleep } from "https://deno.land/x/[email protected]/mod.ts";
import { createBot } from "../../lib/libbot/init.ts";
import { MarxanBotConfig } from "../../lib/libbot/marxan-bot.ts";
import { SpecificationStatus } from "../../lib/libbot/geo-feature-specifications.ts";
import { getDemoFeatureSpecificationFromFeatureNamesForProject } from "./lib.ts";

const scriptPath = dirname(relative(Deno.cwd(), fromFileUrl(import.meta.url)));

export const runBot = async (settings: MarxanBotConfig) => {
const bot = await createBot(settings);

const organization = await bot.organizations.create({
name: "[demo] Australia - Kimberley " + crypto.randomUUID(),
description: "",
});

const planningUnitAreakm2 = 2000;

const planningAreaId = await bot.planningAreaUploader.uploadFromFile(
`${scriptPath}/kimberley.zip`,
);
const project = await bot.projects.createInOrganization(organization.id, {
name: "Australia - Kimberley " + crypto.randomUUID(),
description: "",
planningAreaId: planningAreaId,
planningUnitGridShape: "hexagon",
planningUnitAreakm2,
});

// We don't expose status of planning unit grid calculations yet so we cannot
// poll for completion of this task, but a reasonable, project-specific wait
// here will do when there is nothing else keeping the API and PostgreSQL busy.
await sleep(30);

// Scenario creation with the bare minimum; From there we need to be setting
// other traits via patch.
const scenario = await bot.scenarios.createInProject(project.id, {
name: `Kimberley - scenario 01`,
type: "marxan",
description: "Demo scenario",
metadata: bot.metadata.analysisPreview(),
});

// get the list of protected areas in the region and use all of them
const paCategories = await bot.protectedAreas
.getIucnCategoriesForPlanningAreaWithId(planningAreaId);

await bot.scenarios.update(scenario.id, {
wdpaIucnCategories: paCategories,
});

await bot.scenarios.update(scenario.id, {
wdpaThreshold: 50,
metadata: bot.metadata.analysisPreview(),
});

await bot.scenarioStatus.waitForPlanningAreaProtectedCalculationFor(
project.id,
scenario.id,
"short",
);

//Setup features in the project
const wantedFeatures = [
// "demo_ecoregions_new_class_split",
// "calidris_(erolia)_ferruginea",
// "chlamydosaurus_kingii",
// "erythrura_(chloebia)_gouldiae",
// "haliaeetus_(pontoaetus)_leucogaster",
// "malurus_(malurus)_coronatus",
// "mesembriomys_macrurus",
// "onychogalea_unguifera",
// "pseudechis_australis",
// "wyulda_squamicaudata",
"zyzomys_woodwardi",
];

const featuresForSpecification =
await getDemoFeatureSpecificationFromFeatureNamesForProject(
project.id,
bot,
wantedFeatures,
);

await bot.geoFeatureSpecifications.submitForScenario(
scenario.id,
featuresForSpecification,
SpecificationStatus.created,
);

await bot.scenarioStatus.waitForFeatureSpecificationCalculationFor(
project.id,
scenario.id,
"some",
);

await bot.marxanExecutor.runForScenario(scenario.id);

await bot.scenarioStatus.waitForMarxanCalculationsFor(
project.id,
scenario.id,
"some",
);
};
38 changes: 38 additions & 0 deletions data/bots/core-demos/demo-australia/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { logDebug } from "../../lib/libbot/logger.ts";
import _ from "https://deno.land/x/[email protected]/lodash.js";
import { Bot } from "../../lib/libbot/init.ts";

export const getDemoFeatureSpecificationFromFeatureNamesForProject = async (
projectId: string,
bot: Bot,
wantedFeatures: string[],
) => {
const geoFeatureIds = await Promise.all(
wantedFeatures.map(async (f) =>
(await bot.geoFeatures.getIdFromQueryStringInProject(projectId, f))[0]
),
);

logDebug(
`geoFeatureIds for inclusion in specification:\n${
Deno.inspect(geoFeatureIds)
}`,
);

const featuresForSpecification = geoFeatureIds.map((i) => ({
kind: "plain",
featureId: i.id,
marxanSettings: {
prop: 0.3,
fpf: 1,
},
}));

logDebug(
`Features for specification:\n${
Deno.inspect(featuresForSpecification, { depth: 6 })
}`,
);

return featuresForSpecification;
};
23 changes: 23 additions & 0 deletions data/bots/core-demos/demo-brazil/bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
dirname,
fromFileUrl,
relative,
} from "https://deno.land/[email protected]/path/mod.ts";
import { config } from "https://deno.land/x/[email protected]/mod.ts";
import { runBot } from "./core.ts";

const scriptPath = dirname(relative(Deno.cwd(), fromFileUrl(import.meta.url)));

const { API_URL, USERNAME, PASSWORD, POSTGRES_URL } = config({
path: scriptPath + "/.env",
});

const settings = {
apiUrl: API_URL,
credentials: {
username: USERNAME,
password: PASSWORD,
},
};

await runBot(settings);
Loading