I am pretty new to faceted search, so it is kind of difficult for me to wrap my head around this... but here goes:
Pretend I have Item which contains a collection of SubItems and the SubItem has an enum that indicates a status - I have successfully enabled faceted search on categories on both Item and SubItem using this index definition:
public class FacetIndexItems : AbstractIndexCreationTask<Item>
{
public const string FacetId = "facets/Items";
public const string ItemCategoryFacetName = "Category";
public const string SubItemCategoryFacetName = "SubItems_Category";
public FacetIndexItems()
{
Map = items => from item in items
from subItem in item.SubItems
select new
{
Category = item.Category,
SubItems_Category = subItem.Category
};
}
}
and this FacetSetup:
new FacetSetup
{
Id = FacetIndexItems.FacetId,
Facets =
{
new Facet {Name = FacetIndexItems.ItemCategoryFacetName},
new Facet {Name = FacetIndexItems.SubItemCategoryFacetName}
}
}
So far, so good!
Now, pretend that SubItem has a Status property - is there a way to divide the result of each facet into different statuses?
E.g. so that quering this data:
{
Category: "wut",
SubItems: [
{Category: "bim", Status: "good"},
{Category: "bim", Status: "good"},
{Category: "bim", Status: "bad"}
]
}
by item.Category.In("wut") && item.SubItems.Any(s => s.Category.In("bim")) would yield a result like
{
Category: {
"good": 2
"bad": 1
},
SubItems_Category: {
"good": 2
"bad": 1
}
}
I am unsure whether this is actually possible to do with faceted search, and I am definitely open to alternatives if my approach is wrong.
You would have to generate different values for each option, you can't do a facet on more than one field at a time.
But you can generate fields at indexing time, so that does the same thing.
Related
I have a rally grid that shows defects. I want do add a column that shows the number of days a defect has been open.
I know can do that by adding a custom renderer in the column configs, but I would also like to sort on this column. Unfortunately, the renderer does not change the sorting of the column.
I think I might be able to use the convert() function on the store instead to create a new virtual column (in this case openAgeDays), but I'm not sure how to do this from the constructor--I presume I make some changes to storeConfig?
Does anyone have an example of how to use a convert function (assuming that this is the right way to do it) to add a new virtual, sortable column to a rally grid?
this.grid = this.add({
xtype: 'rallygrid',
model: model,
disableColumnMenus: false,
storeConfig: [...]
As is the answer in the duplicate, you can add a doSort to the column:
{dataIndex: 'Parent', name: 'Parent',
doSort: function(state) {
var ds = this.up('grid').getStore();
var field = this.getSortParam();
console.log('field',field);
ds.sort({
property: field,
direction: state,
sorterFn: function(v1, v2){
console.log('v1',v1);
console.log('v2',v2);
if (v1.raw.Parent) {
v1 = v1.raw.Parent.Name;
} else {
v1 = v1.data.Name;
}
if (v2.raw.Parent) {
v2 = v2.raw.Parent.Name;
} else {
v2 = v2.data.Name;
}
return v1.localeCompare(v2);
}
});
},
renderer: function(value, meta, record) {
var ret = record.raw.Parent;
if (ret) {
return ret.Name;
} else {
meta.tdCls = 'invisible';
return record.data.Name;
}
}
},
I search hi and lo but could not find any decent reference how to get paid and outstanding amounts for the invoice in NetSuite using SuiteTalk API.
Documentation is non-existent and so are the samples so I'm poking in the dark here.
When invoice is retrieved using get method, I kind of expected these to be specified (fields amountPaid and amountRemaining) but they are not.
The next port of call, unfortunately, is to search for customer payments that have been applied to the target invoice. Search works but, to make the matter worse, each payment is returned without any apply details so extra call is required to get all apply details and figure out the total payment amount applied to the invoice.
Is that REALLY the only way to do it?!
(And I probably would have to consider deposits and deposit applications as they are separate record types. Sigh)
Any help is appreciated
George
UPDATE: Working code sample
long internalInvoiceId = 42;
TransactionSearchAdvanced tsa = new TransactionSearchAdvanced()
{
columns = new TransactionSearchRow()
{
basic = new TransactionSearchRowBasic()
{
total = new SearchColumnDoubleField[] { new SearchColumnDoubleField() },
amount = new SearchColumnDoubleField[] { new SearchColumnDoubleField() },
amountPaid = new SearchColumnDoubleField[] { new SearchColumnDoubleField() },
amountRemaining = new SearchColumnDoubleField[] { new SearchColumnDoubleField() }
}
},
criteria = new TransactionSearch()
{
basic = new TransactionSearchBasic()
{
mainLine = new SearchBooleanField()
{
searchValue = true,
searchValueSpecified = true
},
type = new SearchEnumMultiSelectField()
{
#operator = SearchEnumMultiSelectFieldOperator.anyOf,
operatorSpecified = true,
searchValue = new string[] { "_invoice" }
},
internalIdNumber = new SearchLongField()
{
#operator = SearchLongFieldOperator.equalTo,
operatorSpecified = true,
searchValue = internalInvoiceId,
searchValueSpecified = true
}
}
}
};
SearchResult sr = nss.search(tsa);
Do a transaction search.
Criteria:
Main Line: Yes
Type: Invoice
(Optional) Internal ID: "The internal id of your Invoice"
Result/Columns:
Amount Paid
Amount Remaining
Translate the above to SuiteTalk API (TransactionSearchAdvanced) and you should be able to get what you want.
Here's a function using the NetSuite ruby bindings that retrieves the amount remaining (due) on an invoice:
def amount_due_for_invoice(ns_invoice)
search = NetSuite::Records::Invoice.search(
criteria: {
basic: [
{
field: 'type',
operator: 'anyOf',
value: %w(_invoice)
},
{
field: 'mainLine',
value: true
},
{
field: 'internalIdNumber',
operator: 'equalTo',
value: ns_invoice.internal_id
}
]
},
columns: {
'tranSales:basic' => {
'platformCommon:internalId/' => {},
'platformCommon:amountRemaining' => {}
}
}
)
if search.results.size > 1
fail "invoice search on internalId should never return more than a single result"
end
search.results.first.attributes[:amount_remaining].to_f
end
An alternative solution here is to create a custom transaction body formula field which pulls the value of the invoice's amountremainingtotalbox field.
I am very new to Dojo (1.7), and I am very excited by the AMD loader and the global philosophy, then thought I have red some dozen of documentation and googled a lot and my brains starts to grill, I am still unable to understand and perform some things : I would like to display a dijit.Tree of any sort of JSON, yes like a JSON editor, because I use also persistent JSON files for storing few datas (not only for GET/.../ transmission) . Here are my expects :
sample JSON : {"infos":{"address":"my address","phone":"my
phone"},"insurance":{"forks":[14,53,123],"prices":[5,8,"3%"]}}
display the differents variables of any JSON : the root child is the
root json variable, children L1 are the root variables, etc...and upon the json variable type (String, Number, Object, Array) I will also display a corresponding icon
not to have to parse the whole json and format it in one big time, would like for exemple to display first the root node, then the well formated children trought a getChildren method for example, so it is done progressively on expando (like a lazy load). I have already made my own Trees classes with javascript, the more flexible way was I gave a dataRoot, a renderItem(dataItem, domItem) and a getChildren(dataItem) to the constructor so I could perform and return all I want, the Tree only performed the rendering only when needed, the Tree had no knowing about datas structure neither modify it, but I am not sure to understand well why the dijit.Tree needs a so restrictive way of build...
Here is my last try, it might totally not the right way, (maybe I have to subclass) but as far as I understand, I need to play with 3 classes (dojo store, tree model and tree widget), but firstly it seems the model can't get the root node, please check my different code comments. So please is there any patient person that can give me a simple example with some clear explanations (yeah I am a bit demanding), at least the list of the right necessary variables for constructor's options I need for start displaying a nice tree view of my json file, there's so much I'm totally lost, many thanks !
...
// before there is the AMD part that load the needed things
Xhr.get({ url:'data/file.json', handleAs:'json',
load: function(data){
console.log('xhr.loaded : ', data);// got my javascript object from the json string
var store = new ItemFileReadStore({// is it the right store I need ??
// or the Memory store ?
// assuming later I'll need to save the data changes
rootId : 'root',//
rootLabel : 'Archive',// useless ? isn't it the model responsability ?
data : {id:'root', items:[data]}// trying to give a root node well formatted
});
var model = new TreeStoreModel({
store : store,
getChildren : function(obj){
// firstly here it seems the root is not found
// I got a 'error loading root' error
// what is missing in my instanciations ??
// what is exactyly the type of the 1st arg : a store ?
console.log('getChildren : ', this.get(obj.id));
},
mayHaveChildren : function(){
console.log('mayHaveChildren ', arguments);
return true;
}
});
var tree = new Tree({
model: model
}, domId);
tree.startup();
}
});
My solution is based on dojo/store/Memory inspired by Connecting a Store to a Tree:
You can find live demo at http://egoworx.com/ or download complete source from dropbox.
Now code. First dojo/store/Memory:
var data = {"infos":{"address":"my address","phone":"my phone", "gift": false, "now": new Date()},"insurance":{"forks":[14,53,123],"prices":[5,8,"3%"]}};
var store = new Memory({
data: data,
mayHaveChildren: function(object) {
var type = this.getType(object);
return (type == "Object" || type == "Array");
},
getChildren: function(object, onComplete, onError) {
var item = this.getData(object);
var type = this.getType(object);
var children = [];
switch(type) {
case "Array":
children = item;
break;
case "Object":
for (i in item) {
children.push({label: i, data: item[i]});
}
break;
}
onComplete(children);
},
getRoot: function(onItem, onError) {
onItem(this.data);
},
getLabel: function(object) {
var label = object.label || object + "";
var type = this.getType(object);
switch(type) {
case "Number":
case "String":
case "Boolean":
case "Date":
var data = this.getData(object);
if (data != label) {
label += ": " + this.getData(object);
}
}
return label;
},
getData: function(object) {
if (object && (object.data || object.data === false) && object.label) {
return object.data;
}
return object;
},
getType: function(object) {
var item = this.getData(object);
if (lang.isObject(item)) {
if (lang.isArray(item)) return "Array";
if (lang.isFunction(item)) return "Function";
if (item instanceof Date) return "Date";
return "Object";
}
if (lang.isString(item)) return "String";
if (item === true || item === false) return "Boolean";
return "Number";
},
getIconClass: function(object, opened) {
return this.getType(object);
}
});
Please note I added a boolean and Date type to your data.
dijit/Tree based on this store:
var tree = new Tree({
model: store,
persist: false,
showRoot: false,
getIconClass: function(object, opened) {
if (lang.isFunction(this.model.getIconClass)) {
return this.model.getIconClass(object, opened);
}
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf";
}
}, "placeholder");
tree.startup();
And finally a stylesheet to display data type icons:
.dijitTreeIcon {
width: 16px;
height: 16px;
}
.Object {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/object.png);
}
.Array {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/array.png);
}
.Date {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/date.png);
}
.Boolean {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/boolean.png);
}
.String {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/string.png);
}
.Number {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/number.png);
}
I cannot access jsFiddle since I'm currently in China, but I'll put the code above there upon my return to Europe and post a link here.
Try somethign like that instead :
store = new dojo.data.ItemFileWriteStore({
url : "",
data: {
identifier: "id",
label : "label",
items : [{
id : "root",
label : "root",
type : "root",
children: [data]
}]
}
});
Also in general avoid overriding the tree functions, you might extend them, but becareful.
If you want to console.log, then rather connect to them...
ItemFileReadStore is a read-only store, so not the one you want for "saving modifications".
You can try the ItemFileWriteStore, or JsonRest, etc.
How do I get access to the columns/datastore fields that are part of the sort set.
I am looking to modify the a grid's sort parameters for remote sorting. I need the remote sort param's sort key to match the column's field's mapping property. I need these things to happen though the normal 'column header click sorts the data' functionality.
Remote sorting and field mapping (ExtJS 4.1)
This functionality seems not to be implemented in ExtJS. Here is a solution using the encodeSorters function provided since ExtJS 4. Accessing fields map throught the model's prototype is a bit dirty but it does the job :
var store = Ext.create('Ext.data.Store', {
...,
proxy: {
...,
encodeSorters: function (sorters) {
var model = store.proxy.model,
map = model.prototype.fields.map;
return Ext.encode(Ext.Array.map(sorters, function (sorter) {
return {
property : map[sorter.property].mapping || sorter.property,
direction: sorter.direction
};
}));
}
}
});
However, it would be more relevant to override the original method :
Ext.data.proxy.Server.override({
encodeSorters: function(sorters) {
var min, map = this.model.prototype.fields.map;
min = Ext.Array.map(sorters, function (sorter) {
return {
property : map[sorter.property].mapping || sorter.property,
direction: sorter.direction
};
});
return this.applyEncoding(min);
}
});
Assuming you are using simpleSortMode, you could do something like this in your store.
listeners: {
beforeload: function( store, operation, eOpts ) {
if (store.sorters.length > 0) {
var sorter = store.sorters.getAt(0),
dir = sorter.direction,
prop = sorter.property,
fields = store.model.getFields(),
i,
applyProp = prop;
for (i = 0; i < fields.length; i++) {
if (fields[i].name == prop) {
applyProp = fields[i].mapping || prop;
break;
}
}
//clearing the sorters since the simpleSortMode is true so there will be only one sorter
store.sorters.clear();
store.sorters.insert(0, applyProp, new Ext.util.Sorter({
property : applyProp,
direction: dir
}));
}
}
},
I would like to start by saying I have read Rally Kanban - hiding Epic Stories but I'm still having trouble on implementing my filter based on the filter process from the Estimation Board app. Currently I'm trying to add an items filter to my query object for my cardboard. The query object calls this._getItems to return an array of items to filter from. As far as I can tell the query calls the function, loads for a second or two, and then displays no results. Any input, suggestions, or alternative solutions are welcomed.
Here's my code
$that._redisplayBoard = function() {
that._getAndStorePrefData(displayBoard);
this._getItems = function(callback) {
//Build types based on checkbox selections
var queries = [];
queries.push({key:"HierarchicalRequirement",
type: "HierarchicalRequirement",
fetch: "Name,FormattedID,Owner,ObjectID,Rank,PlanEstimate,Children,Ready,Blocked",
order: "Rank"
});
function bucketItems(results) {
var items = [];
rally.forEach(queries, function(query) {
if (results[query.key]) {
rally.forEach(results[query.key], function(item) {
//exclude epic stories since estimates cannot be altered
if ((item._type !== 'HierarchicalRequirement') ||
(item._type === 'HierarchicalRequirement' && item.Children.length === 0)) {
items = items.concat(item);
}
});
}
});
callback(items);
}
rallyDataSource.findAll(queries, bucketItems);
};
function displayBoard() {
artifactTypes = [];
var cardboardConfig = {
types: [],
items: that._getItems,
attribute: kanbanField,
sortAscending: true,
maxCardsPerColumn: 200,
order: "Rank",
cardRenderer: KanbanCardRenderer,
cardOptions: {
showTaskCompletion: showTaskCompletion,
showAgeAfter: showAgeAfter
},
columnRenderer: KanbanColumnRenderer,
columns: columns,
fetch: "Name,FormattedID,Owner,ObjectID,Rank,Ready,Blocked,LastUpdateDate,Tags,State,Priority,StoryType,Children"
};
if (showTaskCompletion) {
cardboardConfig.fetch += ",Tasks";
}
if (hideLastColumnIfReleased) {
cardboardConfig.query = new rally.sdk.util.Query("Release = null").or(kanbanField + " != " + '"' + lastState + '"');
}
if (filterByTagsDropdown && filterByTagsDropdown.getDisplayedValue()) {
cardboardConfig.cardOptions.filterBy = { field: FILTER_FIELD, value: filterByTagsDropdown.getDisplayedValue() };
}
cardboardConfig.types.push("HierarchicalRequirement");
if (cardboard) {
cardboard.destroy();
}
artifactTypes = cardboardConfig.types;
cardboard = new rally.sdk.ui.CardBoard(cardboardConfig, rallyDataSource);
cardboard.addEventListener("preUpdate", that._onBeforeItemUpdated);
cardboard.addEventListener("onDataRetrieved", function(cardboard,args){ console.log(args.items); });
cardboard.display("kanbanBoard");
}
};
that.display = function(element) {
//Build app layout
this._createLayout(element);
//Redisplay the board
this._redisplayBoard();
};
};
Per Charles' hint in Rally Kanban - hiding Epic Stories
Here's how I approached this following Charles' hint for the Rally Catalog Kanban. First, modify the fetch statement inside the cardboardConfig so that it includes the Children collection, thusly:
fetch: "Name,FormattedID,Children,Owner,ObjectID,Rank,Ready,Blocked,LastUpdateDate,Tags,State"
Next, in between this statement:
cardboard.addEventListener("preUpdate", that._onBeforeItemUpdated);
And this statement:
cardboard.display("kanbanBoard");
Add the following event listener and callback:
cardboard.addEventListener("onDataRetrieved",
function(cardboard, args){
// Grab items hash
filteredItems = args.items;
// loop through hash keys (states)
for (var key in filteredItems) {
// Grab the workproducts objects (Stories, defects)
workproducts = filteredItems[key];
// Array to hold filtered results, childless work products
childlessWorkProducts = new Array();
// loop through 'em and filter for the childless
for (i=0;i<workproducts.length;i++) {
thisWorkProduct = workproducts[i];
// Check first if it's a User Story, since Defects don't have children
if (thisWorkProduct._type == "HierarchicalRequirement") {
if (thisWorkProduct.Children.length === 0 ) {
childlessWorkProducts.push(thisWorkProduct);
}
} else {
// If it's a Defect, it has no children so push it
childlessWorkProducts.push(thisWorkProduct);
}
}
filteredItems[key] = childlessWorkProducts;
}
// un-necessary call to cardboard.setItems() was here - removed
}
);
This callback should filter for only leaf-node items.
Mark's answer caused an obscure crash when cardboard.setItems(filteredItems) was called. However, since the filtering code is actually manipulating the actual references, it turns out that setItems() method is actually not needed. I pulled it out, and it now filters properly.
Not sure this is your problem but your cardboard config does not set the 'query' field. The fetch is the type of all data to retrieve if you want to filter it you add a "query:" value to the config object.
Something like :
var cardboardConfig = {
types: ["PortfolioItem", "HierarchicalRequirement", "Feature"],
attribute: dropdownAttribute,
fetch:"Name,FormattedID,Owner,ObjectID,ClassofService",
query : fullQuery,
cardRenderer: PriorityCardRenderer
};
Where fullQuery can be constructed using the the Rally query object. You find it by searching in the SDK. Hope that maybe helps.