Rally SDK 2.0: How to display multiple data columns with a TimeSeriesCalculator - charts

I would like to display 2 time series of data with columns in the same "Rally.ui.chart.Chart". The config below for "Rally.data.lookback.calculator.TimeSeriesCalculator" stacks the columns on the same X column. Is there an easy way to group the data to be shown side-by-side instead for the same date (like the "accepted" and "time remaining" in the iteration burn-down chart) ?
Perhaps something like this?
getMetrics: function () {
return [
{
"field": "TaskRemainingTotal",
"as": "Hours Remaining",
"f": "sum",
"display": "column"
},
{
"field": "PlanEstimate",
"as": "Story Points Accepted",
"f": "filteredSum",
"filterField": "ScheduleState",
"filterValues": ["Accepted", "Verified"],
"display": "column",
"group": "1" //????? is there a specifier to separate this data?
},
];
},

Here is the code for the calculator used for the burn chart:
https://github.com/RallyApps/app-catalog/blob/master/src/apps/charts/burndown/BurnDownCalculator.js
Writing calculators for generating charts from lookback api is probably the most difficult thing to do in the app platform, so kudos for tackling it!
I'm not an expert either, but hopefully that above code is enough to point you in the right direction. Please post back if you either solve it or run into a new issue.
I was able to get it to work by adding the following to the chartConfig:
plotOptions: {
column: {
stacking: null
}
}

I've found some more on this subject that I believe may be helpful:
The "stack" member of the series config in highcharts allows a series to be stacked by name. We can create a much more flexible system that allow us to specify how to stack the data by using this and overriding some methods in the Rally.data.lookback.calculator.TimeSeriesCalculator to allow the series data to be modified.
prepareChartData returns series data, so we can override the output of that to add series data:
prepareChartData: function(store) {
var snapshots = [];
store.each(function(record) {
snapshots.push(record.raw);
});
var a = this.runCalculation(snapshots);
for (var k in a.series) {
if (a.series[k].name.startsWith("Story")) a.series[k].stack = "Story";
}
return a;
}
we can override the _buildSeriesConfig function to push any properties listed in the seriesConfig array in the metric config to the series config. This allows us to specify the series formatting in a nicer way and also gives us much more power in modifying other attributes of the chart config :
_buildSeriesConfig: function(calculatorConfig) {
var aggregationConfig = [],
metrics = calculatorConfig.metrics,
derivedFieldsAfterSummary = calculatorConfig.deriveFieldsAfterSummary;
for (var i = 0, ilength = metrics.length; i < ilength; i += 1) {
var metric = metrics[i];
var seriesConfig = {
name: metric.as || metric.field,
type: metric.display,
dashStyle: metric.dashStyle || "Solid"
};
for (var k in metric.seriesConfig) {
seriesConfig[k] = metric.seriesConfig[k];
}
aggregationConfig.push(seriesConfig);
}
for (var j = 0, jlength = derivedFieldsAfterSummary.length; j < jlength; j += 1) {
var derivedField = derivedFieldsAfterSummary[j];
var seriesConfig = {
name: derivedField.as,
type: derivedField.display,
dashStyle: derivedField.dashStyle || "Solid"
};
for (var k in derivedField.seriesConfig) {
seriesConfig[k] = derivedField.seriesConfig[k];
}
aggregationConfig.push(seriesConfig);
}
return aggregationConfig;
},
this method allows us to supply a seriesConfig property in getMetrics like so:
getMetrics: function() {
return [{
"field": "TaskRemainingTotal", // the field in the data to operate on
"as": "Hours Remaining", // the label to appear on the chart
"f": "sum", // summing function to use.
"display": "column", // how to display the point on the chart.
seriesConfig: {
"stack": "Hours",
"color": "#005eb8"
}
}, {
"field": "PlanEstimate", // the field in the data to operate on
"as": "Story Points Accepted", // the label to appear on the chart
"f": "filteredSum",
"filterField": "ScheduleState", // Only use points in seduled sate accepted or Verified
"filterValues": ["Accepted", "Verified"],
"display": "column",
seriesConfig: {
"stack": "Points",
"color": "#8dc63f"
}
}, {
"field": "PlanEstimate", // the field in the data to operate on
"as": "Story Points Remaining", // the label to appear on the chart
"f": "filteredSum",
"filterField": "ScheduleState", // Only use points in seduled sate accepted or Verified
"filterValues": ["Idea", "Defined", "In Progress", "Completed"],
"display": "column",
seriesConfig: {
"stack": "Points",
"color": "#c0c0c0"
}
},
];
},
With option #2, we can control and add any series config data in the same context that we are configuring the metrics, without worrying about configuration orders. Option #2 is a little dangerous though as the underscore implies that the method is private and therefore has no contractual guarantee to remain compatible in future revisions. (Maybe the rally guys will see this and extend the functionality for us)

Related

Mongoose - can't insert subDocuments of a Dictionary Type

I have a Mongoose schema for the document Company, that has several fields. One of these (documents_banks) is a "free" field, of dictionary type, because I don't know the names of the keys in advance.
The problem is that, when I save the document (company.save()) even if the resulting saved document has the new sub_docs, in the DB no new sub_docs are actually saved.
var Company = new Schema({
banks: [{ type: String }], // array of Strings
documents_banks: {} // free field
});
Even if documents_banks is not restricted by the Schema, it will have this structure (in my mind):
{
"bank_id1": {
"doc_type1": {
"url": { "type": "String" },
"custom_name": { "type": "String" }
},
"doc_type2": {
"url": { "type": "String" },
"custom_name": { "type": "String" }
}
},
"bank_id2": {
"doc_type1": {
"url": { "type": "String" },
"custom_name": { "type": "String" }
}
}
}
But I don't know in advance names of keys bank_id neither doc_type, so I used the Dictionary type (documents_banks:{}).
Now, this below is the function I use to save new sub_docs in documents_banks. The same logic I always use to save new sub_docs.. Anyway this time, it seems saved, but it's not.
function addBankDocument(company_id, bank_id, doc_type, url, custom_name) {
// retrieve the company document
Company.findById(company_id)
.then(function(company) {
// create empty sub_docs if needed
if (!company.documents_banks) {
company.documents_banks = {};
}
if (!company.documents_banks[bank_id]) {
company.documents_banks[bank_id] = {};
}
// add the new sub_doc
company.documents_bank[bank_id][doc_type] = {
"url": url,
"custom_name": custom_name
};
return company.save();
})
.then(function(saved_company) {
// I try to check if the new obj has been saved
console.log(saved_company.documents_bank[bank_id][doc_type]);
// and it actually prints the new obj!!
});
}
The saved_company returned by the .save() actually has the new sub_docs, but if I check the DB there is not the new sub_doc! I can save just the first one, all the others are not stored.
So, the console.log() always print the new sub_docs, but actually in the DataBase, just the first sub_doc is saved, not the others. So at the end, saved_company always has 1 sub_doc, the first one.
It seems very strange to me, since saved_company has the new sub_docs. What can be happened?
This below is a real extract from by DB, and it will contains forever just the sub_doc "doc_bank#1573807781414", others will be not present in the DB.
{
"_id": "5c6eaf8efdc21500146e289c", // company_id
"banks": [ "MPS" ],
"documents_banks": {
"5c5ac3e025acd98596021a9a": // bank_id
{
"doc_bank#1573807781414": // doc_type
{
"url": "http://...",
"custom_name": "file1"
}
}
}
}
Versions:
$ npm -v
6.4.1
$ npm show mongoose version
5.7.11
$ node -v
v8.16.0
It seems that, since mongoose doesn't know the exact model of the subdoc, it can't know when it changes. So I have to use markModified to notify changes of the "free field" (also known as dictionary or MixedType) with this:
company_doc.documents_banks["bank_id2"]["doc_type3"] = obj; // modify
company_doc.markModified('documents_banks'); // <--- notify changes
company_doc.save(); // save changes
As I understood, markModified force the model to 'update' that field during the save().

Mapbox js - tileset colors assignment

I am trying to create a colored uk areas map - I need the colors to be driven by local data (say for example sales by postcode), which will always change depending on user selections like report date ranges etc. I have a tileset source which I am assigning to a layer, as in the following (sensitive values blanked out):
this.map.addLayer({
"id": "uk-pc-data",
"type": "fill",
"source-layer": "***",
"source": {
type: 'vector',
url: '***'
}
});
I am then able to style the postcode areas within the tileset by addressing a name property on the tileset features, like so:
"paint": {
"fill-color": [
"match",
["get", "name", [{name: "PR"}, {name: "CH"}, {name: "LN"}]],
[
"PR",
"DD",
"AL",
"PO"
],
"hsla(344, 84%, 29%, 0.37)",
"hsla(131, 94%, 34%, 0)"
]
}
The above will assign one color to the matched areas, and a default to all the other (the non matched ones); What I would like to do, is computing the colour value locally (so based on data constantly changing) based on the feature name, like so
"paint": {
"fill-color": function (feature = {}) {
return localSalesByArea(feature.name)
}
}
This though does not seem to be possible: any help or pointers appreciated; I have been through examples from Mapbox
such as choroplet, heatmaps, expressions but these seem to rely on values delivered via the dataset itself (i.e Population), in my case the values which determine the color scale are separated (they come from an internal reporting api)
If the data on which the colours are determined does not exist in the tileset, you basically have to do a gigantic lookup by name.
You will have a function that generates the fill-color expression, something like:
function makeFillExpression(valuesByName) {
function valueToColor(value) {
return /* insert your choroplething special sauce here */;
}
return ret = [
"match",
["get", "name"],
Object.keys(valuesByName).map(name => valueToColor(valuesByName[name])),
'black'
];
}
Yes, it will be a very large and unwieldy expression.

Create Entities and training phrases for values in functions for google action

I have created a trivia game using the SDK, it takes user input and then compares it to a value in my DB to see if its correct.
At the moment, I am just passing a raw input variable through my conversation, this means that it regularly fails when it mishears the user since the exact string which was picked up is rarely == to the value in the DB.
Specifically I would like it to only pick up numbers, and for example realise that it must extract '10' , from a speech input of 'my answer is 10'.
{
"actions": [
{
"description": "Default Welcome Intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "welcome"
},
"intent": {
"name": "actions.intent.MAIN"
}
},
{
"description": "response",
"name": "Raw input",
"fulfillment": {
"conversationName": "rawInput"
},
"intent": {
"name": "raw.input",
"parameters": [{
"name": "number",
"type": "org.schema.type.Number"
}],
"trigger": {
"queryPatterns":[
"$org.schema.type.Number:number is the answer",
"$org.schema.type.Number:number",
"My answer is $org.schema.type.Number:number"
]
}
}
}
],
"conversations": {
"welcome": {
"name": "welcome",
"url": "https://us-central1-triviagame",
"fulfillmentApiVersion": 2
},
"rawInput": {
"name": "rawInput",
"url": "https://us-central1-triviagame",
"fulfillmentApiVersion": 2
}
}
}
app.intent('actions.intent.MAIN', (conv) => {
conv.data.answers = answersArr;
conv.data.questions = questionsArr;
conv.data.counter = answersArr.length;
var thisQuestion = conv.data.questions;
conv.ask((conv.data.answers)[0]));
});
app.intent('raw.input', (conv, input) => {
if(input == ((conv.data.answers)[0])){
conv.ask(nextQuestion());
}
app.intent('actions.intent.TEXT', (conv,input) => {
//verifying if input and db value are equal
// at the moment input is equal to 'my number is 10' (for example) instead of '10'
//therefore the string verification never works
conv.ask(nextQuestion());
});
In a previous project i used the dialogflow UI and I used this #system.entities number parameter along with creating some training phrases so it understands different speech patterns.
This input parameter I am passing through my conv , is only a raw string where I'd like it to be filtered using some sort of entity schema.
How do I create the same effect of training phrases/entities using the JSON file?
You can't do this using just the Action SDK. You need a Natural Language Processing system (such as Dialogflow) to handle this as well. The Action SDK, by itself, will do speech-to-text, and will use the actions.json configuration to help shape how to interpret the text. But it will only return the entire text from the user - it will not try to determine how it might match an Intent, nor what parameters may exist in it.
To do that, you need an NLP/NLU system. You don't need to use Dialogflow, but you will need something that does the parsing. Trying to do it with simple pattern matching or regular expressions will lead to nightmares - find a good system to do it.
If you want to stick to things you can edit yourself, Dialogflow does allow you to download its configuration files (they're just JSON), edit them, and update or replace the configuration through the UI or an API.

Attach external disks at particular position

By using this code i can attach disks sequentially.
device 2, device 3, device 4
But i want to attach disks for a particular position.
I want to directly attach 3rd disk or 4th disk.
Without upgrading the previous disks
code
for disk in external_disks:
obj = {}
obj['id'] = getDiskPriceId(client, disk)
#obj['id'] = 2277
#logger.info("disk %s size: %s --\n" ,(str(disk_num)), (str(disk)))
if obj['id'] == "":
print("Invalid external disk size")
exit(1)
categories = {}
categories['categoryCode'] = "guest_disk"+str(disk_num)
categories['complexType'] = "SoftLayer_Product_Item_Category"
obj['categories'] =[categories]
obj["complexType"] = "SoftLayer_Product_Item_Price"
prices.append(obj)
disk_num = disk_num + 1
response = client.call('SoftLayer_Product_Order','placeOrder', {
"virtualGuests": [{
"id": id
}],
"prices": prices,
"properties": [{
"name": "NOTE_GENERAL",
"value": "adding disks"
},{
"name": "MAINTENANCE_WINDOW",
"value": "now"
}],
"complexType": "SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade"
})
You can attach directly to an specific position through the attribute categoryCode, following is the order:
guest_disk1 for Second Disk
guest_disk2 for Third Disk
guest_disk3 for Fourth Disk
guest_disk4 for Fifth Disk
Make sure that item prices have the categoryCode you need, you can verify it by using the method SoftLayer_Virtual_Guest::getUpgradeItemPrices as following:
https://[username]:[apikey]#api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest/[deviceId]/getUpgradeItemPrices
Following structure about prices allows to attach disks directly to the Third and Fourth position.
prices = [
{
'id': 2299,
'categories': [
{
'categoryCode': 'guest_disk2',
'complexType': 'SoftLayer_Product_Item_Category'
}
],
'complexType': 'SoftLayer_Product_Item_Price'
},
{
'id': 2288,
'categories': [
{
'categoryCode': 'guest_disk3',
'complexType': 'SoftLayer_Product_Item_Category'
}
],
'complexType': 'SoftLayer_Product_Item_Price'
}
]
In your code the disk_num value needs to be between 2 and 3 to attach disks directly to the third and fourth position.
Note:
If you want to change/replace disk by another one with more/less space, you only need to apply the same idea. Set the categoryCode of new disk with the same categoryCode value of current disk.
I hope this help you.

Can I parameterize labels and properties on CREATE or SET? (REST and transaction)

I have a query
1. CREATE (a:%1$s {props}), (b:%2$s {props2}), (b)-[:%3$s {relProps}]->(a)
2. MATCH (a:%1$s { value:{value} })-[:%2$s]->(b) WHERE (b:%3$s) SET (b {props})
I'm using underscore.string to allow string format but would love to just stick with parameters.
Is it possible to parameterize labels such as
{
"query": CREATE (a:{label} {props}),
"params": {
"label":"SomeLabel",
"props":{....}
}
}
and is it also possible to parameterize properties on a SET?
{
"query": "MATCH ..... SET (node {props})"
"params": {
"props":{
"prop1:":"Property Name",
....
}
}
}
Also is there a way to parameterize on a "MERGE"? it gives me 'Parameter maps cannot be used in MERGE patterns (use a literal map instead, eg. "{id: {param}.id}")'
EDIT: what about parameterizing where clause?
MATCH (:Identity%1$s {nodeId:{nodeId})-[r*2..3]-(node1)-[b:%2$s]->(node2) %4$s return *
I have %4$s there for me to put whatever clauses I need to. If I want to have it as
WHERE node1.nodeId= {someNodeId} SET b= {props}
is that possible??
Also when I'm doing a transaction SET node={props} does not seem to work. I tried
statements:[
{
"statement":"..... SET node={props}",
"parameters":{
"props": {
"description":"some description"
}
}
}
]
Any suggestions?? Thank you!
You cannot parameterize labels since the query plan might look different for a different label.
Parameterizing multiple properties using a map is possible, note the slight difference in the SET syntax:
{
"query": "MATCH ..... SET node = {props}"
"params": {
"props":{
"prop1:":"Property Name",
....
}
}
}
Not 100% about MERGE but I guess this should work:
{
"query": "MERGE (n:Label {identifier: {idValue}) ON CREATE SET n = {props}"
"params": {
"identifier": 123,
"props":{
"identifier": 123,
"prop1:":"Property Name",
....
}
}
}
I found out!
CREATE ... SET node = {props}
does the trick to set multiple properties with parameters
doc: http://docs.neo4j.org/chunked/snapshot/cypher-parameters.html