How can I access the results of a questionnaire/s (KMQuestionnaireRun type) of a client to export the results to an XML dynamically.
A sample of the class that I am working on:
while select rmlSomaticMeasures
outer join rmlSomatometryWorker
where rmlSomatometryWorker.RMLRef == rmlTable.RecId
&& rmlSomatometryWorker.SomaticMeasureId == rmlSomaticMeasures.SomaticMeasureId
{
if (rmlSomatometryWorker.Value)
{
nodeMeasure = doc.createElement(strReplace(strUpr(rmlSomaticMeasures.SomaticMeasureId)," ","_"));//.text(strReplace(strUpr(rmlPhysiologicalHabitWorker.Value)," ","_"));
nodeMeasure.text(strReplace(Num2Str(rmlSomatometryWorker.Value,0,5,1,0)," ",""));
nodeSOMATOMETRIA.appendChild(nodeMeasure);
}
else
{
nodeMeasure = doc.createElement(strReplace(strUpr(rmlSomaticMeasures.SomaticMeasureId)," ","_"));
nodeSOMATOMETRIA.appendChild(nodeMeasure);
}
}
Short answer, aka "The fish"
The results are stored in tables KMVirtualNetworkAnswerTable and KMVirtualNetworkAnswerLine
Long answer, aka "Let me tell you how to fish":
You already figured out that each time a questionnaire is done, it is processed by one of the subclasses of abstract class KMQuestionnaireRun. When I did one of the questionnaires in Contoso, I noticed that afterwards a little message pops up "The completed questionnaire has been saved". I figured that is a good place to start, so I jumped to the code line that produces that message (just select the message in the infolog and click "Edit"). This brought me to class KMQuestionnaireSave, method save (which is called by method close in class KMQuestionnaireRun). From there it is fairly easy to navigate to class KMQuestionnaireSaveResult, method saveAll and see how the above tables get written.
I figured this out in Version AX 2012 R2 CU7. I did not check other versions, but I would guess the data model to be similar or identical.
Related
I am currently coding a plugin for ILIAS. The plugin itself is not at all complex but it contains several issues whereas I think we could make it simpler as it is.
The situation is following: We have a global advanced meta data field added in the user defined meta data section with a bijective identifier. The field is activated at a repository objected named course. We have manipulated the GUI with the plugin based on ilUIHookPluginGUI.
The code for this is ... well ... see it for yourself.
First of all we save the ID of the new meta data field in the settings at the ConfigGUI for the plugin:
$field_settings = new ilSetting("foo");
$field_id_value = $field_settings->set("field_id",$_POST["field_id"]);
In our class which extends ilUIHookPluginGUI we are loading the setting as following and we have the ID of the field:
$field_settings = new ilSetting("foo");
$field_id_value = $field_settings->get("field_id");
Now the fun part. With this ID and the ref_id of the object (well, we also load the object to get the ObjId) we can load the value of the meta data field setted at the course:
$object = \ilObjectFactory::getInstanceByRefId($_GET[ 'ref_id' ]);
$obj_id = $object->getId();
$result = $DIC->database()->query("SELECT value FROM adv_md_values_text WHERE obj_id = '".$obj_id."' AND field_id = '".$field_id_value."'");
$value = $DIC->database()->fetchAssoc($result);
$is_active = $value['value'];
The question is ... is there an easier way to achieve my result?
Best,
Laura
Nice question. First of all, note that I consider the advanced metadata service in ILIAS to be lacking a good readme making clear, which hooks the interface is offering for tasks such as yours. Some time ago, I had to deal with this service as well and run into similar issues. Hopefully, your question helps to document this a little better an I myself am looking forward to other suggestions, knowing that mine is not really good as well. If you have any resources, helping pushing the introduction of good readme for services and also pushing services towards using the repository pattern with a clear interface would be highly appreciated.
Concering your question of what can be improved: I see three main issues in the lines of code:
Storing an ID in the config of your plugin. Your plugin will unconfigurable for non-technical people. However, also for you this will be error prone, think about exporting-importing stuff from a test-installation to production.
Access the value by query instead of the service.
Using new and static functions inside your code making it untestable.
Step 1
Lets start with the first one. Note, that I did not manage to solve this one without introducing a new one (a new query). Bad I know. I hope that there is a better solution, I did not find one after quick research. You store the id, since the field title is not securely unique, right? This is correct, however, you could think about storing the tripplet of field_title, record_title and (maybe) scope. Note that you maybe do not need the scope since you want to use this globally. A function return you and array containing field_id and record_id could look like so:
function getFieldAndRecordIdByFieldTitles($field_title, $record_title, $scope_title){
$query = "select field.field_id,field.record_id from adv_mdf_definition as field
INNER JOIN adv_md_record as record ON record.record_id = field.record_id
INNER JOIN adv_md_record_scope as scope ON scope.record_id = field.record_id
INNER JOIN object_reference as ref ON scope.ref_id = ref.ref_id
INNER JOIN object_data as scope_data ON ref.obj_id = scope_data.obj_id
WHERE field.title='$field_title' AND record.title='$record_title' AND scope_data.title = '$scope_title'";
$set = $this->dic()->database()->query($query);
if($row = $this->dic()->database()->fetchAssoc($set))
{
return array_values($row);
}
}
Then get your values like so:
list($field_id,$record_id) = getFieldAndRecordIdByFieldTitles("my_field", "my_record", "my_scope");
Note that I am aware that I am introducing a new query here. Sorry, was the best I could come up with. I am sure there you find a better solution, if your research a bit, let us know if successful. However, we will remove one in the next step.
Step 2
Use the undocumented service, the get your value out of the advance meta data. Since you now have the record id and the field id, you can to that like so:
$record_values = new ilAdvancedMDValues($record_id, $obj_id);
$record_values->read();
$ADTGroup = $ilAdvancedMDValues->getADTGroup();
$ADT = $ilADTGroup->getElement($field_id);
$value = $ADT->getText();
/**if you have text, others are possible, such as:
switch (true) {
case ($ADT instanceof ilADTText):
break;
case ($ADT instanceof ilADTDate):
$value = $ADT->getDate();
break;
case ($ADT instanceof ilADTExternalLink):
$... = $ADT->getUrl();
$... = $ADT->getTitle();
break;
case ($ADT instanceof ilADTInternalLink):
$... = $ADT->setTargetRefId($value);
}
**/
Note that ADT's are also undocumented. There might be a better way, to get a value out of this.
Step 3
Wrap your statics and new into some injectable dependency. I usually use the bloated constructor pattern to do this. Looks like so:
public function __construct(InjectedSettings $mySettings = null)
{
if (!$mySettings) //Case in the default scenario
{
$this->mySettings = new InjectedSettings();
} else //used e.g. for unit tests, where you can stuff the constructor with a mock
{
$this->mySettings = $mySettings;
}
$this->mySettings->doSometing();
}
Note that this is not real dep. injection, still you still use new, but I think a very workable fix to use dep. injection at least for the test context in ilias.
Does this help? I hope there will be other (better answers as well).
I have a window in a Qt application using PostgreSQL 9.3 database. The window is a form used do display, edit and insert new data. t looks like that:
I have data from 3 sql tables in that view. the tables are related with foreign keys:
contractors (main table) - mapped to "personal data" section
contacts (has foreign key to contractors.ID)
addresses (has foreign key to contractors.ID)
So - in my window's class I have 3 main models (+ 2 proxy models to transpose tables in "personal data" an "address data" sections). I use QSqlTableModel for theese sesctions, and a QSqlRelationalTableModel for contactData section. when opening that window "normally" (to view some contractor), i simply pass contractor's ID to the constructor and store it in proper variable. Also, I call the QSqlTableModel::​setFilter(const QString & filter) method for each of the models, and set the proper filtering. When opening that window in "add new" mode i simply pass a "-1" or "0" value to the ID variable, so no data gets loaded to the model.
All 3 models have QSqlTableModel::OnManualSubmit editStrategy. When saving the data (triggered by clicking a proper button), I start a transaction. And then I submit models one-by-one. personalData model gets submitted first, as I need to obtain it's PK after insert (to set in the FK fields in other models).
When submitting of the model fails, I show a messageBox with the QSqlError content, rollback the transaction and return from the method.
When I have an error on the first model being processed - no problem, as nothing was inserted. But when the first model is saved, but the second or third fails - there is a little problem. So I rollback the transacion as before, and return from the function. But after correcting the data and submitting it again - the first model is not trying to submit - as it doesn't know that there was a rollback, and the data needs to be inserted again. What would be a good way to notice such a model, that it needs to be submited once again?
At the moment I ended up with something like that:
void kontrahenciSubWin::on_btnContractorAdd_clicked() {
//QStringList errorList; // when error occurs in one model - whole transacion gets broken, so no need for a list
QString error;
QSqlDatabase db = QSqlDatabase::database();
//backup the data - in case something fails and we have to rollback the transaction
QSqlRecord personalDataModelrec = personalDataModel->record(0); // always one row. will get erased by SubmitAll, as no filter is set, because I don't have its ID.
QList<QSqlRecord> contactDataModelRecList;
for (int i = 0 ; i< contactDataModel->rowCount(); i++) {
contactDataModelRecList.append( contactDataModel->record(i) );
}
QList<QSqlRecord> addressDataModelRecList;
for (int i = 0 ; i< addressDataModel->rowCount(); i++) {
addressDataModelRecList.append( addressDataModel->record(i) );
}
db.transaction();
if ( personalDataModel->isDirty() && error.isEmpty() ) {
if (!personalDataModel->submitAll()) //submitAll calls select() on the model, which destroys the data as the filter is invalid ("where ID = -1")
//errorList.append( personalDataModel->lastError().databaseText() );
error = personalDataModel->lastError().databaseText();
else {
kontrahentid = personalDataModel->query().lastInsertId().toInt(); //only here can I fetch ID
setFilter(ALL); //and pass it to the models
}
}
if ( contactDataModel->isDirty() && error.isEmpty() )
if (!contactDataModel->submitAll()) //slot on_contactDataModel_beforeInsert() sets FK field
//errorList.append( contactDataModel->lastError().databaseText() );
error = contactDataModel->lastError().databaseText();
if ( addressDataModel->isDirty() && error.isEmpty() )
if (!addressDataModel->submitAll()) //slot on_addressDataModel_beforeInsert() sets FK field
//errorList.append( addressDataModel->lastError().databaseText() );
error = addressDataModel->lastError().databaseText();
//if (!errorList.isEmpty()) {
// QMessageBox::critical(this, tr("Data was not saved!"), tr("The following errors occured:") + " \n" + errorList.join("\n"));
if (!error.isEmpty()) {
QMessageBox::critical(this, tr("Data was not saved!"), tr("The following errors occured:") + " \n" + error);
db.rollback();
personalDataModel->clear();
contactDataModel->clear();
addressDataModel->clear();
initModel(ALL); //re-init models: set table and so on.
//re-add data to the models - backup comes handy
personalDataModel->insertRecord(-1, personalDataModelrec);
for (QList<QSqlRecord>::iterator it = contactDataModelRecList.begin(); it != contactDataModelRecList.end(); it++) {
contactDataModel->insertRecord(-1, *it);
}
for (QList<QSqlRecord>::iterator it = addressDataModelRecList.begin(); it != addressDataModelRecList.end(); it++) {
addressDataModel->insertRecord(-1, *it);
}
return;
}
db.commit();
isInEditMode = false;
handleGUIOnEditModeChange();
}
Does anyone have a better idea? I doubt if it's possible to ommit backing-up the records before trying to insert them. But maybe there is a better way to "re-add" them to the model? I tried to use "setRecord", and "remoweRows" & "insertRecord" combo, but no luck. Resetting the whole model seems easiest (I only need to re-init it, as it loses table, filter, sorting and everything else when cleared)
I suggest you to use a function written in the language PLPGSQL. It has one transaction between BEGIN and END. If it goes wrong at a certain point of the code then will it rollback all data flawlessly.
What you are doing now is not a good design, because you handle the control over a certain functionality (rollback) to an external system with regard to the rollback (it is happening in the database). The external system is not designed to do that, while the database on the contrairy is created and designed for dealing with rollbacks and transactions. It is very good at it. Rebuilding and reinventing this functionality, which is quite complex, outside the database is asking for a lot of trouble. You will never get the same flawless rollback handling as you will have using functions within the database.
Let each system do what it can do best.
I have met your problem before and had the same line of thought to work this problem out using Hibernate in my case. Until I stepped back from my efforts and re-evaluated the situation.
There are three teams working on the rollback mechanism of a database:
1. the men and women who are writing the source code of the database itself,
2. the men and women who are writing the Hibernate code, and
3. me.
The first team is dedicated to the creation of a good rollback mechanism. If they fail, they have a bad product. They succeeded. The second team is dedicated to the creation of a good rollback mechanism. Their product is not failing when it is not working in very complex situations.
The last team, me, is not dedicated to this problem. Who am I to write a better solution then the people of team 2 or team 1 based on the work of team 2 who were not able to get it to the level of team 1?
That is when I decided to use database functions instead.
Here's what I'm trying to do:
IEnumerable<OfficeView> allOffices = GetAllOffices(); //this method also uses some linq query
foreach (var office in allOffices)
{
officeSales.Add(
new Tuple<int, decimal>(office.Id, GetSaleAmount(office.Id, someParams)));
}
public decimal GetAgentSaleAmount(int officeRef, someTypes someParams)
{
var q = ObjectQuery.Where
(i => i.officeId == officeRef && i.someOtherStuff == someParams)
.ToList();
return q.Sum(i => i.NetPrice);
}
I can't set MultipleActiveResultSets = true. Instead, as suggested here, I tried to do .ToList() before doing the summation, but got the same error. (and even if I wouldn't get the error, I think it would cause heavy load to fetch everything just to get the summation of one field)
I will be super thankful for any suggestion to solve this problem!
Update: The problem was all about GetAllOffices(), which returned IEnumerable (which leaves the DataReader open). I changed the first line as below and it worked. Thanks a lot guys :)
IEnumerable<OfficeView> allOffices = GetAllOffices().ToList();
Error said that reader is already opened, that means that you have run some query before which needs to close.
Write .ToList() to that linq query and do same for other queries too.
I think you are trying to do both adding and querying at the same time.You need to finish adding "Sale" object to "Sales" collection before trying to calculate the sales value.
In other words, you cannot query until the transaction is committed. Hope that helps.
I'm writing a GUI extension and using the Anquilla framework to get a list of Keywords within a Category. I'm obtaining an XML document for the list of keywords then working with that document within my extension.
My problem is that the returned XML doesn't contain the Keyword's 'Description' value. I have the Title and Key etc.
My original code looks like this:
var category = $models.getItem("CATEGORYTCMID:);
var list = category.getListKeywords();
list.getXml();
A typical node returned is this:
<tcm:Item ID="tcm:4-1749-1024"
Type="1024" Title="rate_one" Lock="0" IsRoot="true"
Modified="2012-12-17T23:01:59" FromPub="010 Schema"
Key="rate_one_value" IsAbstract="false"
CategoryTitle="TagSelector"
CategoryID="tcm:4-469-512" Icon="T1024L0P0"
Allow="268560384" Deny="96" IsNew="false"
Managed="1024"/></tcm:ListKeywords>
So I've tried using a Filter to give me additional column information:
var filter = new Tridion.ContentManager.ListFilter();
filter.columns = Tridion.Constants.ColumnFilter.EXTENDED;
var list = category.getListKeywords(filter);
Unfortunately this only gives the additional XML attributes:
IsShared="true" IsLocalized="false"
I'd really like the description value to be part of this XML without having to create a Keyword object from the XML. Is such a thing possible?
cough any ideas? cough
I'm afraid you'll have to load the Keyword itself to get the Description.
It's not used in any lists, so it's not returned in the XML.
You could always create a List Extender to add this information to the list, but try to be smart about it since this extender will execute everytime a GetList is called.
Won't save you from having to open every keyword in the list, but you'll be doing it server-side (with Core Service/NetTcp for instance) which will probably be easier and faster than opening each keyword with Anguilla.
In this instance I only need the one keyword, so I simply get it from the CMS. Getting an object in Anguilla is a bit weird, here's the code:
In your main code area:
var selectedKy = $models.getItem("TcmUriOfKeywordHere");
if (selectedKy.isLoaded()) {
p.selectedKy = selectedKy;
this.onselectedKyLoaded();
} else {
$evt.addEventHandler(selectedKy, "load", this.onselectedKyLoaded);
selectedKy.load();
}
It's worth noting how I store the keyword in the properties of the item, so I can obtain it in the onselectedKyLoaded function
The function called once the item is loaded
ContentBloom.ExampleGuiExtension.prototype.onselectedKyLoaded = function (event) {
var p = this.properties;
var selectedDescription = p.selectedKy.getDescription();
// do what you need to do with the description :)
};
I resolved this, thanks to the answer here: https://stackoverflow.com/a/12805939/1221032 - Cheers Nuno :)
I recently started this question in another thread (to which Reed Copsey
graciously responded) but I don't feel I framed the question well.
At the core of my question, I would like an illustration of how to gain
access to data AS it is being get/set.
I have Page.aspx.cs and, in the codebehind, I have a loop:
List<ServerVariable> files = new List<ServerVariable>();
for (i = 0; i <= Request.Files.Count - 1; i++)
{
m_objFile = Request.Files[i];
m_strFileName = m_objFile.FileName;
m_strFileName = Path.GetFileName(m_strFileName);
files.Add(new ServerVariable(i.ToString(),
this.m_strFileName, "0"));
}
//CODE TO COPY A FILE FOR UPLOAD TO THE
//WEB SERVER
//WHEN THE UPLOAD IS DONE, SET THE ITEM TO
//COMPLETED
int index = files.FindIndex(p => p.Completed == "0");
files[index] = new ServerVariable(i.ToString(),
this.m_strFileName, "1");
The "ServerVariable" type gets and sets ID, File, and Completed.
Now, I need to show the user the file upload "progress" (in effect,
the time between when the loop adds the ServerVariable item to the
list to when the Completed status changes from 0 to 1.
Now, I have a web service method "GetStatus()" that I would like to
use to return the files list (created above) as a JSON string (via
JQuery). Files with a completed status of 0 are still in progress,
files with a 1 are done.
MY QUESTION IS - what does the code inside GetStatus() look like? How
do I query List **as* it is being populated and
return the results real-time? I have been advised that I need to lock
the working process (setting the ServerVariable data) while I query
the values returned in GetStatus() and then unlock that same process?
If I have explained myself well, I'd appreciate a code illustration of
the logic in GetStatus().
Thanks for reading.
Have a look at this link about multi threading locks.
You need to lock the object in both read and write.