Skip to content

Instantly share code, notes, and snippets.

@JogoShugh
Last active April 10, 2019 22:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JogoShugh/dff1c17a1b45133dcd2727cd5860d78e to your computer and use it in GitHub Desktop.
Save JogoShugh/dff1c17a1b45133dcd2727cd5860d78e to your computer and use it in GitHub Desktop.
VersionOne Bulk API

Bulking up!

image

Summary

These are evolving notes demonstrating a new Bulk API for VersionOne Lifecycle to support Create, Update, and Execute Operation commands built upon the powerful and intuitive query.v1 syntax.

Where can I use this?

This functionality exists in 18.0 releases at http://host/instance/api/asset. It's in production and in usage by customers today. See the Customer requests section about tickets and requests customers have been making for years for features like this.

How do I give feedback?

Please post feedback in #lifecycle-bulk-api in Slack.

Quickstart

Below are a couple cURL examples for Create scenarios with either JSON or YAML.

Sample programs which build the same requests with code:

More features and automated tests

See the Features and End to End Functional Tests section for more Create scenarios plus Update and Execute Operation scenario tests.

Create a single Story (JSON)

curl -i -X POST \
   -H "Content-Type:application/json" \
   -H "Authorization:Bearer 1.BO9wxGpDbI2Oh2OPbhb7wW4ghj0=" \
   -d \
'{
  "Scope": "System (All Projects)",
  "AssetType": "Story",
  "Name": "Story"
}' \
 'https://www4.v1host.com/VersionOneJG/api/asset'

Create multiple Stories with child Task and Tests (JSON)

When using JSON, you just group your multiple Assets into an array:

curl -i -X POST \
   -H "Content-Type:application/json" \
   -H "Authorization:Bearer 1.BO9wxGpDbI2Oh2OPbhb7wW4ghj0=" \
   -d \
'[
  {
    "Scope": "System (All Projects)",
    "AssetType": "Story",
    "Name": "Story-1",
    "Children": [
      {
        "AssetType": "Test",
        "Name": "Test-1 in Story-1"
      },
      {
        "AssetType": "Task",
        "Name": "Task-1 in Story-1"
      },
      {
        "AssetType": "Test",
        "Name": "Test-2 in Story-1"
      },
      {
        "AssetType": "Task",
        "Name": "Task-2 in Story-1"
      }
    ]
  },
  {
    "Scope": "System (All Projects)",
    "AssetType": "Story",
    "Name": "Story-2",
    "Children": [
      {
        "AssetType": "Test",
        "Name": "Test-1 in Story-2"
      },
      {
        "AssetType": "Task",
        "Name": "Task-1 in Story-2"
      },
      {
        "AssetType": "Test",
        "Name": "Test-2 in Story-2"
      },
      {
        "AssetType": "Task",
        "Name": "Task-2 in Story-2"
      }
    ]
  },
  {
    "Scope": "System (All Projects)",
    "AssetType": "Story",
    "Name": "Story-3",
    "Children": [
      {
        "AssetType": "Test",
        "Name": "Test-1 in Story-3"
      },
      {
        "AssetType": "Task",
        "Name": "Task-1 in Story-3"
      },
      {
        "AssetType": "Test",
        "Name": "Test-2 in Story-3"
      },
      {
        "AssetType": "Task",
        "Name": "Task-2 in Story-3"
      }
    ]
  }
]' \
 'https://www4.v1host.com/VersionOneJG/api/asset'

Create a single Story (YAML)

curl -i -X POST \
   -H "Content-Type:text/yaml" \
   -H "Authorization:Bearer 1.BO9wxGpDbI2Oh2OPbhb7wW4ghj0=" \
   -d \
'Scope: System (All Projects)
AssetType: Story
Name: Story' \
 'https://www4.v1host.com/VersionOneJG/api/asset'

Create multiple Stories with child Task and Tests (YAML)

When using YAML, you use the --- document separator character to specify multiple Assets.

curl -i -X POST \
   -H "Content-Type:text/yaml" \
   -H "Authorization:Bearer 1.BO9wxGpDbI2Oh2OPbhb7wW4ghj0=" \
   -d \
'Scope: System (All Projects)
AssetType: Story
Name: Story-1
Children:
- AssetType: Test
  Name: Test-1 in Story-1
- AssetType: Task
  Name: Task-1 in Story-1
- AssetType: Test
  Name: Test-2 in Story-1
- AssetType: Task
  Name: Task-2 in Story-1
---
Scope: System (All Projects)
AssetType: Story
Name: Story-2
Children:
- AssetType: Test
  Name: Test-1 in Story-2
- AssetType: Task
  Name: Task-1 in Story-2
- AssetType: Test
  Name: Test-2 in Story-2
- AssetType: Task
  Name: Task-2 in Story-2
---
Scope: System (All Projects)
AssetType: Story
Name: Story-3
Children:
- AssetType: Test
  Name: Test-1 in Story-3
- AssetType: Task
  Name: Task-1 in Story-3
- AssetType: Test
  Name: Test-2 in Story-3
- AssetType: Task
  Name: Task-2 in Story-3' \
 'https://www4.v1host.com/VersionOneJG/api/asset'

Sample output

Whether sending YAML or JSON, the result will always be in JSON, and includes a variety of information about the processing and includes a list and count of how many Assets were created, modified, or operated on. Note that if you send a payload that is nothing but a standard query.v1 payload it will just return a query result for you.

{
  "requestId": "9c5ac358-da24-4cf5-b2b8-c88ee902a293",
  "createdDate": "2018-02-02T20:49:54.5776014Z",
  "completedDate": "2018-02-02T20:49:54.7336065Z",
  "duration": "00:00:00.1560051",
  "durationSeconds": 0.15600509999999998,
  "complete": true,
  "processing": false,
  "assetsCreated": {
    "oidTokens": [
      "Story:251186",
      "Test:251187",
      "Task:251188",
      "Test:251189",
      "Task:251190",
      "Story:251191",
      "Test:251192",
      "Task:251193",
      "Test:251194",
      "Task:251195",
      "Story:251196",
      "Test:251197",
      "Task:251198",
      "Test:251199",
      "Task:251200"
    ],
    "count": 15
  },
  "assetsModified": {
    "oidTokens": [],
    "count": 0
  },
  "assetsOperatedOn": {
    "oidTokens": [],
    "count": 0
  },
  "queryResult": {
    "results": [],
    "count": -1
  }
}

Fetures and End to End Functional Tests

The basic Create, Update, and Execute features have a number of tests that demonstrate and verify their functionality. The tests are located here.

Create features

  • Create single Story with Scope referenced by OID Token
  • Create single Story with Scope referenced by Name
  • Create two Stories with Scope referenced by Name and OID Token
  • Create single Story that has two Children, of type Task and Test
  • Create Scope, Epic, and Story via Subs relation

Update features

  • Update Owners relational Attribute on two Stories, adding two new owners by OID Token
  • Update Owners relational Attribute on single Story, removing one of two owners by OID Token
  • Update Owners relational Attribute on single Story, adding two new owners by OID Token
  • Update Owners relational Attribute on single Story, adding and removing owners with object context pointing to scalars by OID Token
  • Update Owners relational Attribute on single Story, adding and removing owners with object context pointing to arrays by OID Token
  • Update Owners relational Attribute on single Story, adding a new owner by OID Token
  • Update Name Text scalar Attribute on two Stories, replacing one exact match string with another by OID Token
  • Update Name LongText scalar Attribute on two Stories, replacing one exact match string with another by OID Token
  • Update Epic, changing its Subs relation by removing Story references
  • Update Epic, changing its Subs relation by adding Story references via subquery
  • Update Epic, changing its Subs relation by adding Story references via subquery
  • Update Epic, changing its Name and Subs relation, referencing an existing Story and adding a new one
  • Update Description LongText scalar Attribute on two Defects, replacing one exact match string with another by OID Token
  • Update Description scalar Attribute on two Stories matching a where clause by Scope OID Token

Execute Operation features

  • Execute Delete Operation upon two Stories matching a where clause by Scope OID Token

How do I run the functional feature tests?

You can run the functional tests by cloning the [versionone/automation] repo and then doing the following:

cd automation
npm install
LIFECYCLE_URL=http://yourhost/instance npm test -- lifecycle/feature_tests/asset_api

More information

Deeper technical explanation

For an even deeper technical explanation, see this gist: https://gist.github.com/JogoShugh/92243c8f5759c5de19859c4ed1c1f92e. Long term, the goal is to support long-running "Jobs" that can recover from downtime and pick up where they left off.

Example integration

As an example of an integration that utilizes this endpoint support, see this code:

https://github.com/JogoShugh/SpaceMiner/blob/Hackweek_V1/client/lib/integrations/V1Integration.next.js#L1-L29

The following lines (from the link above) of ECMAScript produce the large payload in the 02 Create Deep Graph of Assets.yaml or 02 Create Deep Graph of Assets.json files of this gist (these enhancements will, like query.v1, support input as either YAML or JSON). You can see how this eases sending data into VersionOne from objects in memory of a script or program. Most importantly, this builds up a nested set of Assets so that you don't have to fire off dozens of HTTP requests. You just send ONE REQUEST!

const lessonToAssetApiPayload = (lesson, scopeName) => {
  const epic = {
    AssetType: 'Epic',
    Scope: scopeName,
    Description: lesson.description,
    Name: lesson.title,
    Subs: []
  };

  for(const section of lesson.sections) {
    const story = {
      AssetType: 'Story',
      Name: section.title,
      Description: section.description,
      Children: []
    };

    for(const part of section.parts) {
      const task = {
        AssetType: 'Task',
        Name: part.title
      };
      story.Children.push(task);
    }
    epic.Subs.push(story);
  }

  return epic;
};

Customer requests

Each of these tickets express a desire for these kinds of features:

How can I close multiple Defects

https://versiononesupport.zendesk.com/agent/tickets/20919

API Script to delete projects

https://versiononesupport.zendesk.com/agent/tickets/4559

Script/function to close all completed tasks

https://versiononesupport.zendesk.com/agent/tickets/24156

API to deactivate members

https://versiononesupport.zendesk.com/agent/tickets/11352

Move Test Suites

https://versiononesupport.zendesk.com/agent/tickets/2502

Bulk field update

https://versiononesupport.zendesk.com/agent/tickets/26001

Deleting over 4500 BZ entries in V1

https://versiononesupport.zendesk.com/agent/tickets/782

Question Regarding Deactivation of users in VersionOne

https://versiononesupport.zendesk.com/agent/tickets/7574

Deleting Inbox Notifications

https://versiononesupport.zendesk.com/agent/tickets/19010

Deleting stuff in my V1 Inbox

https://versiononesupport.zendesk.com/agent/tickets/3566

Best way to copy/move all project assets to new project

https://versiononesupport.zendesk.com/agent/tickets/1570

Question on bulk upload

https://versiononesupport.zendesk.com/agent/tickets/3589

Customer looks forward to updating multiple assets with a single command

https://versiononesupport.zendesk.com/agent/tickets/26754

// Runs in node 8.5 with axios NPM package installed
const Axios = require('axios');
const uri = 'https://www4.v1host.com/VersionOneJG/api';
const resource = 'Asset';
const accessToken = '1.BO9wxGpDbI2Oh2OPbhb7wW4ghj0=';
const parentScope = 'System (All Projects)';
const axios = Axios.create({
baseURL: uri,
timeout: 30000,
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
async function create_single_Story() {
const story = {
Scope: parentScope,
AssetType: 'Story',
Name: 'Story'
};
console.log(JSON.stringify(story));
const result = await axios.post('/asset', story);
console.log(result.data.assetsCreated);
};
async function create_multiple_Stories_with_child_Task_and_Tests() {
const storyCount = 3;
const taskAndTestCount = 2;
const stories = [];
for (let storyNum = 1; storyNum <= storyCount; storyNum++)
{
const story =
{
Scope: parentScope,
AssetType: 'Story',
Name: `Story-${storyNum}`,
Children: []
};
for (let childNum = 1; childNum <= taskAndTestCount; childNum++)
{
story.Children.push(
{
AssetType: 'Test',
Name: `Test-${childNum} in Story-${storyNum}`
});
story.Children.push(
{
AssetType: 'Task',
Name: `Task-${childNum} in Story-${storyNum}`
});
}
stories.push(story);
}
console.log(JSON.stringify(stories));
const result = await axios.post('/asset', stories);
console.log(result.data.assetsCreated);
};
create_single_Story();
create_multiple_Stories_with_child_Task_and_Tests();
```$ node simple-bulk.js
{"Scope":"System (All Projects)","AssetType":"Story","Name":"Story"}
[{"Scope":"System (All Projects)","AssetType":"Story","Name":"Story-1","Children":[{"AssetType":"Test","Name":"Test-1 in Story-1"},{"AssetType":"Task","Name":"Task-1 in Story-1"},{"AssetType":"Test","Name":"Test-2 in Story-1"},{"AssetType":"Task","Name":"Task-2 in Story-1"}]},{"Scope":"System (All Projects)","AssetType":"Story","Name":"Story-2","Children":[{"AssetType":"Test","Name":"Test-1 in Story-2"},{"AssetType":"Task","Name":"Task-1 in Story-2"},{"AssetType":"Test","Name":"Test-2 in Story-2"},{"AssetType":"Task","Name":"Task-2 in Story-2"}]},{"Scope":"System (All Projects)","AssetType":"Story","Name":"Story-3","Children":[{"AssetType":"Test","Name":"Test-1 in Story-3"},{"AssetType":"Task","Name":"Task-1 in Story-3"},{"AssetType":"Test","Name":"Test-2 in Story-3"},{"AssetType":"Task","Name":"Task-2 in Story-3"}]}]
{ oidTokens: [ 'Story:251153' ], count: 1 }
{ oidTokens:
[ 'Story:251154',
'Test:251155',
'Task:251156',
'Test:251157',
'Task:251158',
'Story:251159',
'Test:251160',
'Task:251161',
'Test:251162',
'Task:251163',
'Story:251164',
'Test:251165',
'Task:251166',
'Test:251167',
'Task:251168' ],
count: 15 }```
// Works with RestSharp NuGet package installed
using System.Collections.Generic;
using RestSharp;
using static System.Console;
using static Newtonsoft.Json.JsonConvert;
namespace CreateStories
{
class Program
{
private const string Uri = "https://www4.v1host.com/VersionOneJG/api";
private const string Resource = "Asset";
private const string AccessToken = "1.BO9wxGpDbI2Oh2OPbhb7wW4ghj0=";
private const string ParentScope = "System (All Projects)";
private static readonly RestClient Client = new RestClient(Uri);
static void Main(string[] args)
{
Create_Single_Story();
Create_Multiple_Stories_with_Child_Task_and_Tests();
WriteLine("Press any key to exit...");
ReadKey();
}
static void Create_Single_Story()
{
var story = new
{
Scope = ParentScope,
AssetType = "Story",
Name = "Story"
};
var payload = SerializeObject(story);
var req = new RestRequest(Resource, Method.POST);
req.AddParameter("application/json", payload, ParameterType.RequestBody);
req.AddHeader("Authorization", $"Bearer {AccessToken}");
var response = Client.Execute(req);
var result = DeserializeObject<dynamic>(response.Content);
WriteLine(result.assetsCreated);
}
static void Create_Multiple_Stories_with_Child_Task_and_Tests()
{
var storyCount = 3;
var taskAndTestCount = 2;
var stories = new List<object>();
for (var storyNum = 1; storyNum <= storyCount; storyNum++)
{
var story = new
{
Scope = ParentScope,
AssetType = "Story",
Name = $"Story-{storyNum}",
Children = new List<object>()
};
for (var childNum = 1; childNum <= taskAndTestCount; childNum++)
{
story.Children.Add(new
{
AssetType = "Test",
Name = $"Test-{childNum} in Story-{storyNum}"
});
story.Children.Add(new
{
AssetType = "Task",
Name = $"Task-{childNum} in Story-{storyNum}"
});
}
stories.Add(story);
}
var payload = SerializeObject(stories);
var req = new RestRequest(Resource, Method.POST);
req.AddParameter("application/json", payload, ParameterType.RequestBody);
req.AddHeader("Authorization", $"Bearer {AccessToken}");
var response = Client.Execute(req);
var result = DeserializeObject<dynamic>(response.Content);
WriteLine(result.assetsCreated);
}
}
}
import requests
uri = 'https://www4.v1host.com/VersionOneJG/api/asset'
accessToken = '1.BO9wxGpDbI2Oh2OPbhb7wW4ghj0='
parentScope = 'System (All Projects)'
session = requests.Session()
session.headers.update({'Authorization': f'Bearer {accessToken}'})
def create_single_Story():
story = {
'Scope': parentScope,
'AssetType': 'Story',
'Name': 'Python Story'
}
result = session.post(uri, json=story)
print(result.json()['assetsCreated'])
def create_multiple_Stories_with_child_Task_and_Tests():
storyCount = 3
taskAndTestCount = 2
stories = []
for storyNum in range(1, storyCount + 1):
story = {
'Scope': parentScope,
'AssetType': 'Story',
'Name': f'Story-{storyNum}',
'Children': []
}
for childNum in range(1, taskAndTestCount + 1):
story['Children'].append({
'AssetType': 'Test',
'Name': f'Test-{childNum}'
})
story['Children'].append({
'AssetType': 'Task',
'Name': f'Task-{childNum}'
})
stories.append(story)
result = session.post(uri, json=stories)
print(result.json()['assetsCreated'])
create_single_Story()
create_multiple_Stories_with_child_Task_and_Tests()

Raj Notes

  • Initializers don't know about the Relation you are concerned with, like in our example "Subs" implies "Super" on the other side, which would work out with StoryInitializer, but not necessarily for other types of relationships.
  • You could be creating an Epic inside another Epic, this is currently failing because we're not actually using Initializers:
[
      {
        "AssetType": "Epic",
        "Name": "First Epic",
        "Description": "First epic description",        
        "Super": "Epic:1111"
      }
]

However, a better syntax might be this to identify the "container" asset:

[
  { 
    "Oid": "Epic:1111",
    "Name": "New Name",
    "Subs": [ 
          {
          "AssetType": "Epic",
          "Name": "First Epic",
          "Description": "First epic description"
        }
    ]
  }
]
  • He liked idea of being able to Swap one mukticalue reference with another but he didn't like the key I had, which was "replace":

The scenario comes from support tickets where people ask for a way to quickly reassign all items from Member:500 to "Member:600" , for example when an employee gets fired.

Not sure what a good syntax would be there...

Maybe:

Owners:
 find: Member:500
 replace: Member:600

Perhaps that could even support multi value find, like:

Owners:
 find:
 - Member:500
 - Member:550
 replace: Member:600

Where it would assign 600 anywhere it finds 500 or 550.

URL: http://host/instance/api/Asset

Note: You can preview with: http://host/instance/api/Asset?previewOnly=true

Step 0: Figuring out the epics with NON-deleted Subs (meaning Epics that have "children relationships")

from: Epic
select:
- Name
- Subs[AssetState!="255"].@Count
- from: Subs
  select:
  - AssetType
  - Name
  - CreatedBy.Name
where:
 CreatedBy.Name: ALM Connect
filter:
- Subs[AssetState!="255"].@Count>"0"
sort:
- +Subs[AssetState!="255"].@Count

Step 1: Release relationships from the Subs toward their Supers

from: Epic
where: 
 Super.CreatedBy.Name: ALM Connect
update:
 Super: ""

Step 2: Delete the remaining top-level (and now Subs-free) Epics

from: Epic
where:
 CreatedBy.Name: ALM Connect
execute: Delete

Scenario

I have the need to do a bulk add to a List Type I need to add well over 2000 values to a field to have it assign to different projects for linking purposes. I know I can perform an api call to update a list but it is 1 at a time. What are the steps to add this?

Steps

With this Bulk endpoint, this can be done with a single HTTP request. Assuming you've already added a type that will be accessible with the Custom_ prefix, here is an example for how to populate it with many items in one HTTP request.

Step 1: Create the items

First, we need to create the items. This can be done with JSON or with YAML payloads:

cURL example with JSON payload

curl -i -X POST \
   -H "Content-Type:application/json" \
   -H "Authorization:Bearer 1.pyqxc+RopbviBaPVSa/zoFogFoM=" \
   -d \
'[
  {"AssetType":"Custom_JoshType", "Name":"Value 1"},
  {"AssetType":"Custom_JoshType", "Name":"Value 2"},
  {"AssetType":"Custom_JoshType", "Name":"Value 3"},
  {"AssetType":"Custom_JoshType", "Name":"Value 4"},
  {"AssetType":"Custom_JoshType", "Name":"Value 5"}  
]' \
 'http://localhost/VersionOne.Web/api/Asset'

cURL example with YAML payload

curl -i -X POST \
   -H "Content-Type:text/yaml" \
   -H "Authorization:Bearer 1.pyqxc+RopbviBaPVSa/zoFogFoM=" \
   -d \
'AssetType: Custom_JoshType
Name: Value 1
---
AssetType: Custom_JoshType
Name: Value 2
---
AssetType: Custom_JoshType
Name: Value 3
---
AssetType: Custom_JoshType
Name: Value 4
---
AssetType: Custom_JoshType
Name: Value 5' \
 'http://localhost/VersionOne.Web/api/Asset'

Step 2: Sample result for either JSON or YAML

The result is always in JSON format, and should look something like this:

{
  "requestId": "7a3235db-b647-4db0-b417-caedbfec06e8",
  "createdDate": "2018-03-13T15:15:22.1206285Z",
  "completedDate": "2018-03-13T15:15:22.2016276Z",
  "duration": "00:00:00.0809991",
  "durationSeconds": 0.080999099999999991,
  "complete": true,
  "processing": false,
  "assetsCreated": {
    "oidTokens": [
      "Custom_JoshType:317336",
      "Custom_JoshType:317337",
      "Custom_JoshType:317338",
      "Custom_JoshType:317339",
      "Custom_JoshType:317340"
    ],
    "count": 5
  },
  "assetsModified": {
    "oidTokens": [],
    "count": 0
  },
  "assetsOperatedOn": {
    "oidTokens": [],
    "count": 0
  },
  "queryResult": {
    "results": [],
    "count": -1
  }
}

Step 3: Verify results

You should now be able to verify the existence of these multiple values via rest-1.v1 or query.v1 like this:

rest-1.v1

URL: http://localhost/VersionOne.Web/rest-1.v1/Data/Custom_JoshType

Sample result

<Assets total="5" pageSize="2147483647" pageStart="0">
	<Asset href="/VersionOne.Web/rest-1.v1/Data/Custom_JoshType/317331" id="Custom_JoshType:317331">
		<Attribute name="AssetType">Custom_JoshType</Attribute>
		<Relation name="Team" />
		<Attribute name="ColorName">fuschia</Attribute>
		<Attribute name="AssetState">64</Attribute>
		<Attribute name="Order">5994128</Attribute>
		<Attribute name="Description" />
		<Attribute name="Name">Value 1</Attribute>
		<Attribute name="Team.Name" />
	</Asset>
	<Asset href="/VersionOne.Web/rest-1.v1/Data/Custom_JoshType/317332" id="Custom_JoshType:317332">
		<Attribute name="AssetType">Custom_JoshType</Attribute>
		<Relation name="Team" />
		<Attribute name="ColorName">watermelon</Attribute>
		<Attribute name="AssetState">64</Attribute>
		<Attribute name="Order">5996144</Attribute>
		<Attribute name="Description" />
		<Attribute name="Name">Value 2</Attribute>
		<Attribute name="Team.Name" />
	</Asset>
	<Asset href="/VersionOne.Web/rest-1.v1/Data/Custom_JoshType/317333" id="Custom_JoshType:317333">
		<Attribute name="AssetType">Custom_JoshType</Attribute>
		<Relation name="Team" />
		<Attribute name="ColorName">wisteria</Attribute>
		<Attribute name="AssetState">64</Attribute>
		<Attribute name="Order">5997677</Attribute>
		<Attribute name="Description" />
		<Attribute name="Name">Value 3</Attribute>
		<Attribute name="Team.Name" />
	</Asset>
	<Asset href="/VersionOne.Web/rest-1.v1/Data/Custom_JoshType/317334" id="Custom_JoshType:317334">
		<Attribute name="AssetType">Custom_JoshType</Attribute>
		<Relation name="Team" />
		<Attribute name="ColorName">denim</Attribute>
		<Attribute name="AssetState">64</Attribute>
		<Attribute name="Order">5999199</Attribute>
		<Attribute name="Description" />
		<Attribute name="Name">Value 4</Attribute>
		<Attribute name="Team.Name" />
	</Asset>
	<Asset href="/VersionOne.Web/rest-1.v1/Data/Custom_JoshType/317335" id="Custom_JoshType:317335">
		<Attribute name="AssetType">Custom_JoshType</Attribute>
		<Relation name="Team" />
		<Attribute name="ColorName">marine</Attribute>
		<Attribute name="AssetState">64</Attribute>
		<Attribute name="Order">6001228</Attribute>
		<Attribute name="Description" />
		<Attribute name="Name">Value 5</Attribute>
		<Attribute name="Team.Name" />
	</Asset>
</Assets>  

query.v1

curl -i -X POST \
   -H "Content-Type:text/yaml" \
   -H "Authorization:Bearer 1.pyqxc+RopbviBaPVSa/zoFogFoM=" \
   -d \
'from: Custom_JoshType
select:
- Name' \
 'http://localhost/VersionOne.Web/query.v1'

Sample result

[
  [
    {
      "_oid": "Custom_JoshType:317331",
      "Name": "Value 1"
    },
    {
      "_oid": "Custom_JoshType:317332",
      "Name": "Value 2"
    },
    {
      "_oid": "Custom_JoshType:317333",
      "Name": "Value 3"
    },
    {
      "_oid": "Custom_JoshType:317334",
      "Name": "Value 4"
    },
    {
      "_oid": "Custom_JoshType:317335",
      "Name": "Value 5"
    }
  ]
]

Step 4: Cleanup (when necessary)

Suppose you want to delete a bunch of the items you just created because you messed up. That's also easy. Here's are examples with JSON and YAML:

JSON payload

curl -i -X POST \
   -H "Content-Type:application/json" \
   -H "Authorization:Bearer 1.pyqxc+RopbviBaPVSa/zoFogFoM=" \
   -d \
'{
	"from": "Custom_JoshType",
	"execute": "Delete"
}' \
 'http://localhost/VersionOne.Web/api/Asset'

YAML payload

curl -i -X POST \
   -H "Content-Type:text/yaml" \
   -H "Authorization:Bearer 1.pyqxc+RopbviBaPVSa/zoFogFoM=" \
   -d \
'from: Custom_JoshType
execute: Delete' \
 'http://localhost/VersionOne.Web/api/Asset'

In either case, you'll get a JSON result similar to the example above that shows you the total count of assetsOperatedOn along with a list of the specific OID Tokens upon which, in this case, the Delete operation was invoked.

Provided your Lifecycle account is an IdeaSpace Administrator, which can be set on the user's dropdown menu in their Member Profile, then you can do things like this:

Suspend one or more users by Email

filter:
- Email='jos.gou@v1.com','jos.ber@v1.com'
where:
 IsSuspended: false
execute: MarkAsSuspended

YAML payload example

curl -i -X POST \
   -H "Authorization:Bearer <access token here>" \
   -H "Content-Type:text/yaml" \
   -d \
'from: IdeaUser
filter:
- Email=\''jos.gou@v1.com\'',\''jos.ber@v1.com\''
where:
 IsSuspended: false
execute: MarkAsSuspended' \
 'https://host/v1instance/api/Asset'

Enable one or more users by Email

filter:
- Email='jos.gou@v1.com','jos.ber@v1.com'
where:
 IsSuspended: true
execute: MarkAsEnabled

Note that the where clause takes care of trimming down the Email-based filter to the set that is, in the first case, not already suspended, and in the second case still suspended. This way you could keep a single script and just update it and run it without issue.

Scenario

I have thousands of inbox items (MessageReceipt assets) that I don't want to delete by hand. Help!

How To

Using the script below, I deleted 17,156 "MessageReceipt" objects where I was the Recipient in 163 seconds against our production instance:

Payload

from: MessageReceipt
where:
 Recipient: Member:333850
execute: Delete

By my estimates, that's maybe 50 to 100 times faster than what it takes in rest-1.v1 due to the one-to-one nature and need to manually loop from the client side!

cURL Command

curl -i -X POST \
   -H "Content-Type:text/yaml" \
   -H "Authorization:Bearer mysecretgoeshere" \
   -d \
from: MessageReceipt
where:
 Recipient: Member:333850
execute: Delete
' \
 'https://www77.v1host.com/ourV1instance/api/Asset'

In JSON, it would be similar, but you'd use an application/json header and payload like this:

{
  "from": "MessageReceipt",
  "where": {
    "Recipient": "Member:333850"
  },
  "execute": "Delete"
}

Notes

You can use any kind of where or filter criteria that works for query.v1 endpoint queries to narrow down the set of items you'd like to delete, including dates. The meta for a MessageReceipt asset looks like this:

image

# This creates a brand new Project (Scope), a new Member, and a TeamRoom for that Member
AssetType: Scope
Parent: System (All Projects)
Name: zoozoo's Project
Schedule: Default Schedule
BeginDate: 8/20/2017
---
AssetType: Member
Name: zoozoo
Password: zoozoo
Nickname: zoozoo
Username: zoozoo
Scopes: zoozoo's Project
---
AssetType: TeamRoom
Name: zoozoo's Room
Schedule: Default Schedule
Scope: zoozoo's Project
[
{
"AssetType": "Scope",
"Parent": "System (All Projects)",
"Name": "zoozoo's Project",
"Schedule": "Default Schedule",
"BeginDate": "8/20/2017"
},
{
"AssetType": "Member",
"Name": "zoozoo",
"Password": "zoozoo",
"Nickname": "zoozoo",
"Username": "zoozoo",
"Scopes": "zoozoo's Project"
},
{
"AssetType": "TeamRoom",
"Name": "zoozoo's Room",
"Schedule": "Default Schedule",
"Scope": "zoozoo's Project"
}
]
{
"requestId": "83888d06-cf18-4ae3-acf3-7a21f17ea20c",
"createdDate": "2017-08-14T19:24:39.2620487Z",
"completedDate": "2017-08-14T19:24:39.6115464Z",
"duration": "00:00:00.3494977",
"durationSeconds": 0.34949769999999997,
"complete": true,
"processing": false,
"assetsCreated": {
"oidTokens": [
"Scope:1669",
"Member:1670",
"TeamRoom:1671"
],
"count": 3
},
"assetsModified": {
"oidTokens": [],
"count": 0
},
"assetsOperatedOn": {
"oidTokens": [],
"count": 0
},
"queryResult": {
"results": [],
"count": -1
}
}
# Create an Epic, multiple Story "Subs", and for each Story, create multiple Tasks
AssetType: Epic
Scope: zoozoo's Project
Description: Beginners writing games with HTML5 and JavaScript of today (ES5) must
master challenging recursion, asynchronous game loops, and callback hell. See how
ES2015 and ES2016's powerful new features simplify all of this, reducing the cognitive
burden for beginners and seasoned pros!
Name: ES2015 and Beyond
Subs:
- AssetType: Story
Name: Powered up JavaScript
Description: Learn about how ES2015 and beyond will powerup your code!
Children:
- AssetType: Task
Name: Write and run tomorrow's code today
- AssetType: Task
Name: How Meteor already uses Babel
- AssetType: Task
Name: Babel already supports async / await
- AssetType: Task
Name: Painful asynchrony in today's JavaScript
- AssetType: Story
Name: ES5 and callback hell
Description: Passing functions to functions to functions gets old and cumbersome
very quickly. See how ES2015 mitigates this problem with simpler syntax for declaring
functions.
Children:
- AssetType: Task
Name: Asynchronous code and callback passing
- AssetType: Task
Name: Replace <code>function() {</code> with <code>() => {</code>
- AssetType: Task
Name: Curlyless arrow functions
- AssetType: Task
Name: Inline arrow functions
- AssetType: Task
Name: Can't get no...
- AssetType: Story
Name: Promises, Promises
Description: Promises allow us to flatten the callback chain out, a vast improvement.
However, there is still a lot of boilerplate.
Children:
- AssetType: Task
Name: Promises in SpaceMiner
- AssetType: Task
Name: Less boilerplate in ES2015 for Promise chains
- AssetType: Task
Name: What about picking up all gems?
- AssetType: Story
Name: 'ES2016: async / await magic'
Description: So far we've seen just how to pick up one group of gems, but now let's
see how to grab all of them, starting with doing it in ES5 and then show the amazing
features of ES2016 that will dramatically simplify this.
Children:
- AssetType: Task
Name: Beginner's very logical for loop
- AssetType: Task
Name: Using Promises in ES5 to solve the problem
- AssetType: Task
Name: 'ES2015: Sugary, but not sweet'
- AssetType: Task
Name: 'ES2016: It''s magic!'
- AssetType: Task
Name: ES2016's awesome async / await
/* Same thing as above, but with JSON syntax */
{
"AssetType": "Epic",
"Scope": "zoozoo's Project",
"Description": "Beginners writing games with HTML5 and JavaScript of today (ES5) must master challenging recursion, asynchronous game loops, and callback hell. See how ES2015 and ES2016's powerful new features simplify all of this, reducing the cognitive burden for beginners and seasoned pros!",
"Name": "ES2015 and Beyond",
"Subs": [
{
"AssetType": "Story",
"Name": "Powered up JavaScript",
"Description": "Learn about how ES2015 and beyond will powerup your code!",
"Children": [
{
"AssetType": "Task",
"Name": "Write and run tomorrow's code today"
},
{
"AssetType": "Task",
"Name": "How Meteor already uses Babel"
},
{
"AssetType": "Task",
"Name": "Babel already supports async \/ await"
},
{
"AssetType": "Task",
"Name": "Painful asynchrony in today's JavaScript"
}
]
},
{
"AssetType": "Story",
"Name": "ES5 and callback hell",
"Description": "Passing functions to functions to functions gets old and cumbersome very quickly. See how ES2015 mitigates this problem with simpler syntax for declaring functions.",
"Children": [
{
"AssetType": "Task",
"Name": "Asynchronous code and callback passing"
},
{
"AssetType": "Task",
"Name": "Replace <code>function() {<\/code> with <code>() => {<\/code>"
},
{
"AssetType": "Task",
"Name": "Curlyless arrow functions"
},
{
"AssetType": "Task",
"Name": "Inline arrow functions"
},
{
"AssetType": "Task",
"Name": "Can't get no..."
}
]
},
{
"AssetType": "Story",
"Name": "Promises, Promises",
"Description": "Promises allow us to flatten the callback chain out, a vast improvement. However, there is still a lot of boilerplate.",
"Children": [
{
"AssetType": "Task",
"Name": "Promises in SpaceMiner"
},
{
"AssetType": "Task",
"Name": "Less boilerplate in ES2015 for Promise chains"
},
{
"AssetType": "Task",
"Name": "What about picking up all gems?"
}
]
},
{
"AssetType": "Story",
"Name": "ES2016: async \/ await magic",
"Description": "So far we've seen just how to pick up one group of gems, but now let's see how to grab all of them, starting with doing it in ES5 and then show the amazing features of ES2016 that will dramatically simplify this.",
"Children": [
{
"AssetType": "Task",
"Name": "Beginner's very logical for loop"
},
{
"AssetType": "Task",
"Name": "Using Promises in ES5 to solve the problem"
},
{
"AssetType": "Task",
"Name": "ES2015: Sugary, but not sweet"
},
{
"AssetType": "Task",
"Name": "ES2016: It's magic!"
},
{
"AssetType": "Task",
"Name": "ES2016's awesome async \/ await"
}
]
}
]
}
{
"requestId": "25fc82b2-14d1-440f-acdd-e99194742e47",
"createdDate": "2017-08-14T19:34:12.5358281Z",
"completedDate": "2017-08-14T19:34:13.0363258Z",
"duration": "00:00:00.5004977",
"durationSeconds": 0.50049769999999993,
"complete": true,
"processing": false,
"assetsCreated": {
"oidTokens": [
"Epic:1673",
"Story:1674",
"Task:1675",
"Task:1676",
"Task:1677",
"Task:1678",
"Story:1679",
"Task:1680",
"Task:1681",
"Task:1682",
"Task:1683",
"Task:1684",
"Story:1685",
"Task:1686",
"Task:1687",
"Task:1688",
"Story:1689",
"Task:1690",
"Task:1691",
"Task:1692",
"Task:1693",
"Task:1694"
],
"count": 22
},
"assetsModified": {
"oidTokens": [],
"count": 0
},
"assetsOperatedOn": {
"oidTokens": [],
"count": 0
},
"queryResult": {
"results": [],
"count": -1
}
}
# Find all Workitems of type Task or Test that have an AssetState = 64, and execute QuickClose on them
from: Workitem
filter:
- AssetType="Task","Test"
- AssetState="64"
execute: QuickClose
---
# Find all Story assets in 64 and QuickClose them
from: Story
filter:
- AssetState='64'
execute: QuickClose
[
{
"from": "Workitem",
"filter": [
"AssetType=\"Task\",\"Test\"",
"AssetState=\"64\""
],
"execute": "QuickClose"
},
{
"from": "Story",
"filter": [
"AssetState='64'"
],
"execute": "QuickClose"
}
]
# For all Stories inside of zoozoo's Project where they have NO tasks in an In Progress state, set all their statuses to Done
from: Story
where:
Scope.Name: zoozoo's Project
filter:
- Children:Task[Status='TaskStatus:123'].@Count>'0'
update:
Status: Done
{
"from": "Story",
"where": {
"Scope.Name": "zoozoo's Project"
},
"filter": [
"Children:Task[Status='TaskStatus:123'].@Count>'0'"
],
"update": {
"Status": "Done"
}
}
# Delete existing items
from: Workitem
filter:
- AssetType!='Epic'
- Scope.Name='zoozoo''s Project'
execute: Delete
---
from: Epic
where:
Scope.Name: zoozoo's Project
execute: Delete
---
from: TeamRoom
where:
Name: zoozoo's Room
execute: Delete
---
from: Scope
where:
Name: zoozoo's Project
execute: Delete
---
from: Member
where:
Name: zoozoo
execute: Delete
[
{
"from": "Workitem",
"filter": [
"AssetType!='Epic'",
"Scope.Name='zoozoo''s Project'"
],
"execute": "Delete"
},
{
"from": "Epic",
"where": {
"Scope.Name": "zoozoo's Project"
},
"execute": "Delete"
},
{
"from": "TeamRoom",
"where": {
"Name": "zoozoo's Room"
},
"execute": "Delete"
},
{
"from": "Scope",
"where": {
"Name": "zoozoo's Project"
},
"execute": "Delete"
},
{
"from": "Member",
"where": {
"Name": "zoozoo"
},
"execute": "Delete"
}
]
[
{
"@MyScope": {
"AssetType": "Scope",
"Name": "My First New Project",
"Owner": "Member:20",
"Parent": "Scope:0",
"BeginDate": "2/11/2017 5:00:00 PM",
"Description": "This is my new project",
"Schedule": "Schedule:1000"
}
},
{
"@YourScope": {
"AssetType": "Scope",
"Name": "My Second New Project",
"Owner": "Member:20",
"Parent": "Scope:0",
"BeginDate": "2/11/2017 5:00:00 PM",
"Description": "This is my new project",
"Schedule": "Schedule:1000"
}
},
{
"@ae095365-f5e0-40ea-95ca-ec475b0cc930": {
"AssetType": "Epic",
"Name": "First Epic",
"Description": "First epic description",
"Scope": "@MyScope"
}
},
{
"@de095365-f5e0-40ea-95ca-ec475b0cc930": {
"AssetType": "Epic",
"Name": "First Epic",
"Description": "First epic description",
"Scope": "@YourScope"
}
},
{
"AssetType": "Scope",
"Name": "My Third New Project",
"Owner": "Member:20",
"Parent": "Scope:0",
"BeginDate": "2/11/2017 5:00:00 PM",
"Description": "This is my new project",
"Schedule": "Schedule:1000",
"Workitems": [
{
"AssetType": "Epic",
"Name": "First Epic",
"Description": "First epic description",
"Subs": [
{
"AssetType": "Story",
"Name": "First Epic:First Story",
"Children": [
{
"AssetType": "Test",
"Name": "Epic 1:Story 1:Test 1",
"Description": "Epic 1:Story 1:Test Description"
},
{
"AssetType": "Task",
"Name": "Epic 1:Story 1:Task 1",
"Description": "Epic 1:Story 1:Task Description"
}
]
}
]
}
]
}
]

Given a tree of Assets that will create a Scope with an Epic that has a Story which has a child Task and Test, I want A response format that returns the newly created Oids within my original tree structure preserved And I want to be able to send that response back to the system such that no mutations or additions are made as a result Unless I modify one any of the attributes -- which would would result in only those modifications being made to the system

AssetType: Scope
Name: My Project
Parent: Scope:0
BeginDate: 1/1/2002
Workitems:
- AssetType: Epic
  Name: My Epic
  Subs:
  - AssetType: Story
    Name: My First Story
    Children:
    - AssetType: Test
      Name: My First Test
    - AssetType: Task
      Name: My First Task

Possible output format:

oid: Scope:12345
AssetType: Scope
Name: My Project
Parent: Scope:0
BeginDate: 1/1/2002
Workitems:
- oid: Epic:12346
  AssetType: Epic
  Name: My Epic
  Subs:
  - oid: Story:12347
    AssetType: Story
    Name: My First Story
    Children:
    - oid: Test:12348
      AssetType: Test
      Name: My First Test
    - oid: Test:12349
      AssetType: Task
      Name: My First Task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment