IcCube - accessing Report Code with Javascript - iccube

When I am editing a report, I can click on "Report Code" to see information about the report structure. It loks like this:
{
"classID": "ic3.ReportGuts",
"guts_": {
"ic3Version": 12,
"schemaName": "test_schema",
"cubeName": "Cube",
"layout": {
"classID": "ic3.FixedLayout",
"guts_": {
"ic3Version": 12,
"grid": 10,
"boxes": [
{
"classID": "ic3.FixedLayoutBox",
"guts_": {
"ic3Version":...
How can I access this Information with Javascript? context.$report apparently doesn't give this information.
Also is there a way to get the information, what MDX statements are used in the different charts of a report? And can this be altered with Javascript?

To get report guts add this code to the Report Code:
function consumeEvent( context, event ) {
if (event.name == 'ic3-report-init') {
console.log(event.value.state.report);
}
}
As for handling mdx request before send, it's kinda harder. Again in ReportCode:
function consumeEvent( context, event ) {
if (event.name == 'ic3-report-init') {
event.value.widgetMgr().forEach(function(idx,item){
if(item.hasOwnProperty('onVizBeforeRequestSend')){
return;
}
var oldMethod = item.onVizBeforeRequestSend.bind(item);
item.onVizBeforeRequestSend = function(request){
console.log(item, request);
oldMethod(request);
}
});
}
In this function item is widgetAdapter with info about the widget and request is request instance.

Related

How to lable URLs acording to Start URLs in APIFY

Using Web Scraper you can input multiple Start URLs.
You can label them using Glob Patterns or Pseudo-URLs.
I have multiple URLs to crawl, but they can't be distinct using Glob Patterns or Pseudo-URLs.
The only option I was thinking of is to split it into multiple tasks.
Is there a better way?
If you have a page with links that have the same selectors, but you want to distinguish them by the heading that they were under, using simply the selectors, globs and pseudoURLs is usually not enough, so you'll need to write your own logic in the page function.
Let's take an example of docs.apify.com as a page where you might want to do that:
Now let's say you want to scrape the links under Guides and Platform features, but using different handlers. To do that, you have to go through the section boxes one by one, for each check the title, and then eventually enqueue the links. This code in the pageFunction should work:
async function pageFunction(context) {
const { label } = context.request.userData;
if (label === "GUIDES") {
context.log.info(`Handling guide link: ${context.request.url}`);
// ...
return;
}
if (label === "PLATFORM") {
context.log.info(`Handling platform feature link: ${context.request.url}`);
// ...
return;
}
const $ = context.jQuery;
for (const cardElement of $("[data-test=MenuCard]").toArray()) {
const title = $("h2", cardElement).text().trim();
let label;
if (title.toLowerCase() === "guides") {
label = "GUIDES"
} else if (title.toLowerCase() === "platform features") {
label = "PLATFORM"
}
if (label !== undefined) {
for (const link of $(".card-body-wrap li a", cardElement)) {
await context.enqueueRequest({
url: new URL(link.href, context.request.loadedUrl).toString(),
userData: {
label,
}
});
}
}
}
}

Copy Google Analytics custom definitions/dimensions between GA4 properties with the Google Analytics Data API?

I have a number of GA4 Google Analytics properties that need to share the same custom dimensions (custom definitions). Using the web UI to manually copy them is slow and error prone. How can I automate this process?
Using Google Apps Script and the Google Analytics Data API v1 I've created a function that almost does it:
https://script.google.com/d/1Tcf7L4nxU5zAdFiLB2GmVe2R20g33aiy0mhUd8Y851JLipluznoAWpB_/edit?usp=sharing
However the API is in beta and doesn't seem to have a way to add the dimensions to the destination GA property.
Does anyone have a way to complete this final step?
function main(){
gaSourcePropertyId = '12345678';
gaDestinationPropertyId = '87654321';
copyCustomDefinitions(gaSourcePropertyId, gaDestinationPropertyId);
}
function copyCustomDefinitions(gaSourcePropertyId, gaDestinationPropertyId) {
let sourceDimensions = getCustomDimensions(gaSourcePropertyId);
addCustomDimensions(gaDestinationPropertyId, sourceDimensions);
}
function getCustomDimensions(gaPropertyId) {
var sourceDimensions = AnalyticsData.Properties.getMetadata(`properties/${gaPropertyId}/metadata`)['dimensions'];
var sourceCustomDefinitions = [];
sourceDimensions.forEach((dimension)=>{
if (dimension['customDefinition'] == true){ sourceCustomDefinitions.push(dimension)}
});
return sourceCustomDefinitions;
}
function addCustomDimensions(gaPropertyId, dimensions) {
const destinationDimensions = getCustomDimensions(gaPropertyId);
const destinationDimensionApiNames = destinationDimensions.map(dimension=>dimension['apiName']);
dimensions.forEach((dimension)=>{
// Check if the new dimension already exists in the destination.
if ( !destinationDimensionApiNames.includes(dimension['apiName']) ) {
let newDimension = AnalyticsData.newDimension(dimension['apiName']); // The newDimension method doesn't seem to work yet!
// TODO - add the dimension to the destination property
console.warn('There is not yet an API function for adding dimensions to a property. Something like AnalyticsData.Properties.setMetadata is needed!');
} else {
console.info(`Dimension with apiName '${ dimension['apiName'] }' already present in destination! Dimension not added to destination`);
}
})
}
Turns out there is the Google Analytics Admin API which provides methods for manipulating the custom dimensions of a property.
This includes functions for listing custom dimensions and creating custom dimensions
function main(){
gaSourcePropertyId = '1234';
gaDestinationPropertyId = '4321';
copyCustomDimensions(gaSourcePropertyId, gaDestinationPropertyId);
}
function copyCustomDimensions(gaSourcePropertyId, gaDestinationPropertyId) {
let sourceDimensions = getCustomDimensions(gaSourcePropertyId);
addCustomDimensions(gaDestinationPropertyId, sourceDimensions);
}
function getCustomDimensions(gaPropertyId) {
try {
return AnalyticsAdmin.Properties.CustomDimensions.list(`properties/${gaPropertyId}`)['customDimensions'];
} catch (error) {
console.error(error);
}
}
function addCustomDimensions(gaPropertyId, dimensions) {
let destinationCustomDimensions = getCustomDimensions(gaPropertyId);
// The destination may not have any custom dimensions.
if (typeof destinationCustomDimensions == 'undefined') {
console.info(`Could not get custom definitions for property ID '${gaPropertyId}'.`, destinationCustomDimensions);
destinationCustomDimensions = [];
};
const destinationDimensionParameterNames = destinationCustomDimensions.map(dimension=>dimension['parameterName']);
dimensions.forEach((sourceDimension)=>{
// Check if the new dimension already exists in the destination.
if ( !destinationDimensionParameterNames.includes(sourceDimension['parameterName']) ) {
let destinationDimension = {
"parameterName": sourceDimension['parameterName'],
"displayName": sourceDimension['displayName'],
"description": sourceDimension['description'],
"scope": sourceDimension['scope']
};
let result = null;
try {
result = AnalyticsAdmin.Properties.CustomDimensions.create(destinationDimension, `properties/${gaPropertyId}`);
console.log('[COPIED]',result);
} catch (error) {
console.log('[FAILED]', destinationDimension)
console.error(error);
}
} else {
console.info(`[NO ACTION] Dimension with apiName '${ sourceDimension['parameterName'] }' already present in destination! Dimension not added to destination.`, sourceDimension);
}
});
}

How to use QML Model, Delegate, Repeater, View?

I have json result as shown below. In my QML project I want to parse the json which I did help of How to show data in QML json request . Now I want to set 4 flag. Local flag, USA, UK and Europe in one row. And I will add as shown below.
My question is how to use QML Model, Delegate, Repeater, View? And which view is beter solution to have?
{ "tarih": "20171212",
"currency": {
"usa": { "buy": "3,7900", "sell": "3,8800", "e_buy": "3,7900" },
"stg": { "buy": "5,0700", "sell": "5,1650", "e_buy": "5,0700" },
"eur": { "buy": "4,4700", "sell": "4,5600", "e_buy": "4,4700" }
}
}
thank you
UPDATE:
Easiest way to do it is this:
Parse json with;
(var result = JSON.parse(request.responseText))
And I get items with this:
(getUsaBuy.text = result.currency.usa.buy)
I motife my original post from How to show data in QML json request.
Running Code:
if (request.readyState === XMLHttpRequest.DONE) {
if (request.status && request.status === 200) {
var result = JSON.parse(request.responseText)
// BUY
textSTG_BUY.text = **result.currency.stg.buy**
// SELL
textSTG_SELL.text = **result.currency.stg.sell**
}
else {
console.log("Log:", request.status, request.statusText)
}
}

How to attach element to another Ajax component

All, Forgive me I am not familiar with the ASP.NET Ajax. I knew the method Create is attaching an html element to ajax component. But I don't know how to detach it from the current component . and attach another one.
Let's say there is a element ctl00_PlaceHolderMain_UserRegistration_txbPassword1 has been attached to a component type AccelaWebControlExtender.HelperBehavior, and the created component id is ctl00_PlaceHolderMain_UserRegistration_txbPassword1_helper_bhv. The code looks like below. please review it .
Sys.Application.add_init(function() {
$create(AccelaWebControlExtender.HelperBehavior, {"closeTitle":"Close","id":"ctl00_PlaceHolderMain_UserRegistration_txbPassword1_helper_bhv","isRTL":false,"title":"Help"}, null, null, $get("ctl00_PlaceHolderMain_UserRegistration_txbPassword1"));
});
I think firstly I should retrieve the component by id, then do the detach and attach work. Hope someone can give me some help.Thanks.
After doing some research, I found It is called Extend Web server control that encapsulates a client behavior in Asp.net Ajax, And I found the attachment of component is done by Asp.net automatically . We can see the Sys.Application.add_init(function() code is generated in the aspx page by Asp.net automatically. So if we want to customize the original behavior of Web Server Control, I believe it can be made in the Javascript OOP way(old and same).
For example :
If the original behavior code is blow.
// Register the namespace for the control.
Type.registerNamespace('Samples');
//
// Define the behavior properties.
//
Samples.FocusBehavior = function(element) {
Samples.FocusBehavior.initializeBase(this, [element]);
this._highlightCssClass = null;
this._nohighlightCssClass = null;
}
//
// Create the prototype for the behavior.
//
Samples.FocusBehavior.prototype = {
initialize : function() {
Samples.FocusBehavior.callBaseMethod(this, 'initialize');
$addHandlers(this.get_element(),
{ 'focus' : this._onFocus,
'blur' : this._onBlur },
this);
this.get_element().className = this._nohighlightCssClass;
},
dispose : function() {
$clearHandlers(this.get_element());
Samples.FocusBehavior.callBaseMethod(this, 'dispose');
},
//
// Event delegates
//
_onFocus : function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._highlightCssClass;
}
},
_onBlur : function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._nohighlightCssClass;
}
},
//
// Behavior properties
//
get_highlightCssClass : function() {
return this._highlightCssClass;
},
set_highlightCssClass : function(value) {
if (this._highlightCssClass !== value) {
this._highlightCssClass = value;
this.raisePropertyChanged('highlightCssClass');
}
},
get_nohighlightCssClass : function() {
return this._nohighlightCssClass;
},
set_nohighlightCssClass : function(value) {
if (this._nohighlightCssClass !== value) {
this._nohighlightCssClass = value;
this.raisePropertyChanged('nohighlightCssClass');
}
}
}
// Optional descriptor for JSON serialization.
Samples.FocusBehavior.descriptor = {
properties: [ {name: 'highlightCssClass', type: String},
{name: 'nohighlightCssClass', type: String} ]
}
// Register the class as a type that inherits from Sys.UI.Control.
Samples.FocusBehavior.registerClass('Samples.FocusBehavior', Sys.UI.Behavior);
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
I think we can override some of the methods of the Javascript Object Samples.FocusBehavior and it's prototype object to achieve customization.
For example .
I can override Samples.FocusBehavior.prototype._onFocus in the script like this.
Samples.FocusBehavior.prototype._onFocus = function (e) {
alert('test');
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._highlightCssClass;
}
};
Just make sure this code is parsed after original one by Browser.
I am not sure if this is the right way to make it . I hope someone can help to verify it .Thank you very much.
Here is a tutorial of it. please review it .
Cheers.

Persisting json data in jstree through postback via asp:hiddenfield

I've been pouring over this for hours and I've yet to make much headway so I was hoping one of the wonderful denizens of SO could help me out. Here's the problem...
I'm implementing a tree via the jstree plugin for jQuery. I'm pulling the data with which I populate the tree programatically from our webapp via json dumped into an asp:HiddenField, basically like this:
JavaScriptSerializer serializer = new JavaScriptSerializer();
string json = serializer.Serialize(Items);
json = json.ToLower();
data.Value = json;
Then, the tree pulls the json from the hidden field to build itself. This works perfectly fine up until I try to persist data for which nodes are selected/opened. To simplify my problem I've hardcoded some json data into the tree and attempted to use the cookie plugin to persist the tree state data. This does not work for whatever reason. I've seen other issues where people need to load the plugins in a specific order, etc, this did not solve my issue. I tried the same setup with html_data and it works perfectly. With this working persistence I converted the cookie plugin to persist the data in a different asp:hiddenfield (we can't use cookies for this type of thing in our application.)
essentially the cookie operations are identical, it just saves the array of nodes as the value of a hidden field. This works with the html_data, still not with the json and I have yet to be able to put my finger on where it's failing.
This is the jQuery.cookie.js replacement:
jQuery.persist = function(name, value) {
if (typeof value != 'undefined') { // name and value given, set persist
if (value === null) {
value = '';
}
jQuery('#' + name).attr('value', value);
} else { // only name given, get value
var persistValue = null;
persistValue = jQuery('#' + name).attr('value');
return persistValue;
}
};
The jstree.cookie.js code is identical save for a few variable name changes.
And this is my tree:
$(function() {
$("#demo1").jstree({
"json_data": {
"data" : [
{
"data" : "A node",
"children" : [ "Child 1", "Child 2" ]
},
{
"attr": { "id": "li.node.id" },
"data" : {
"title": "li.node.id",
"attr": { "href": "#" }
},
"children": ["Child 1", "Child 2"]
}
]
},
"persistence": {
"save_opened": "<%= open.ClientID %>",
"save_selected": "<%= select.ClientID %>",
"auto_save": true
},
"plugins": ["themes", "ui", "persistence", "json_data"]
});
});
The data -is- being stored appropriately in the hiddenfields, the problem occurs on a postback, it does not reopen the nodes. Any help would be greatly appreciated.
After looking through this some more, I just wanted to explain that it appears to me that the issue is that the tree has not yet been built from the JSON_data when the persistence operations are being attempted. Is there any way to postpone these actions until after the tree is fully loaded?
If anyone is still attempting to perform the same type of operation on a jsTree version 3.0+ there is an easier way to accomplish the same type of functionality, without editing any of the jsTree's core JavaScript, and without relying on the "state" plugin (Version 1.0 - "Persistence"):
var jsTreeControl = $("#jsTreeControl");
//Can be a "asp:HiddenField"
var stateJSONControl = $("#stateJSONControl");
var url = "exampleURL";
jsTreeControl.jstree({
'core': {
"data": function (node, cb) {
var thisVar = this;
//On the initial load, if the "state" already exists in the hidden value
//then simply use that rather than make a AJAX call
if (stateJSONControl.val() !== "" && node.id === "#") {
cb.call(thisVar, { d: JSON.parse(stateJSONControl.val()) });
}
else {
$.ajax({
type: "POST",
url: url,
async: true,
success: function (json) {
cb.call(thisVar, json);
},
contentType: "application/json; charset=utf-8",
dataType: "json"
}).responseText;
}
}
}
});
//If the user changes the jsTree, save the full JSON of the jsTree into the hidden value,
//this will then be restored on postback by the "data" function in the jsTree decleration
jsTreeControl.on("changed.jstree", function (e, data) {
if (typeof (data.node) != 'undefined') {
stateJSONControl.val(JSON.stringify(jsTreeControl.jstree(true).get_json()));
}
});
This code will create a jsTree and save it's "state" into a hidden value, then upon postback when the jsTree is recreated, it will use its old "state" restored from the "HiddenField" rather than make a new AJAX call and lose the expansions/selections that the user has made.
Got it working properly with JSON data. I had to edit the "reopen" and "reselect" functions inside jstree itself.
Here's the new functioning reopen function for anyone who needs it.
reopen: function(is_callback) {
var _this = this,
done = true,
current = [],
remaining = [];
if (!is_callback) { this.data.core.reopen = false; this.data.core.refreshing = true; }
if (this.data.core.to_open.length) {
$.each(this.data.core.to_open, function(i, val) {
val = val.replace(/^#/, "")
if (val == "#") { return true; }
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
else { remaining.push(val); }
});
if (current.length) {
this.data.core.to_open = remaining;
$.each(current, function(i, val) {
_this.open_node(val, function() { _this.reopen(true); }, true);
});
done = false;
}
}
if (done) {
// TODO: find a more elegant approach to syncronizing returning requests
if (this.data.core.reopen) { clearTimeout(this.data.core.reopen); }
this.data.core.reopen = setTimeout(function() { _this.__callback({}, _this); }, 50);
this.data.core.refreshing = false;
}
},
The problem was that it was trying to find the element by a custom attribute. It was just pushing these strings into the array to search when it was expecting node objects. Using this line
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
instead of
if ($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
was all it took. Using a similar process I was able to persist the selected nodes this way as well.
Hope this is of help to someone.

Resources