Match Items between two Lightswitch Queries - javascript

I have a tile list in a HTML Lightswitch Client (current version) and want to enable functionality for users to be able to click an element and display the addEdit screen for that element.
The addEdit screen uses a query Complaints and the tile list uses a query vw_Upcoming_Complaints. Both queries have a common unique attribute Complant_ID.
I currently have the following code:
myapp.Main.vw_Upcoming_Complaints_Selected_execute = function (screen) {
myapp.showAddEditComplaints(null, {
beforeShown: function (addEditComplaintScreen) {
addEditComplaintScreen.Complaint = screen.Upcoming_Complaints.selectedItem;
},
afterClosed: function (addEditScreen, navigationAction) {
screen.selected_Complaint.details.refresh();
}
});
};
Lightswitch currently shows the correct Complaint_ID on the addEdit screen but does not fetch the remainder of the attributes.
How can I tell lightswitch that the common identifier is complaint_Id and it should find the rest of the attributes in the Complaints data set?
There is no way for me to edit the original Complaints query to contain all the attributes of the vw_Upcoming_Work dataset.

One option would be to update your vw_Upcoming_Complaints_Selected_execute function along the following lines:
myapp.Main.vw_Upcoming_Complaints_Selected_execute = function (screen) {
myapp.showAddEditComplaint(null, {
beforeShown: function (addEditComplaintScreen) {
var id = screen.Upcoming_Complaints.selectedItem.Id;
myapp.activeDataWorkspace.ApplicationData.Complaints_SingleOrDefault(id).execute().then(function onComplete(result) {
if (result && result.results && result.results.length !== 0) {
addEditComplaintScreen.Complaint = result.results[0];
}
});
},
afterClosed: function (addEditScreen, navigationAction) {
screen.selected_Complaint.details.refresh();
}
});
};
This update assumes that your DataSource is called ApplicationData, and the complaint id is the key of your Complaints query.
Based upon these assumptions, the revised version simply locates the matching Complaint entity, by using the _SingleOrDefault method against your Complaints query, and assigns it to the add edit screen.

Related

How to collect and return aggregated data as an Array from a table in Protractor?

I am trying to aggregate a list of dates from a data table, written in Angular, in a Protractor test. I'm doing the aggregation from a PageObject class that is called in the Protractor test. I know that my code is successfully grabbing the text I want, but when I try to console.log the returned array, I get an empty array. I'm still new to Javascript/Typescript, Angular, and Protractor and this may be a result of my newness to the asynchronous nature of this development environment.
Code is as follows,
The PageObject SpecMapper class with method:
import { browser, element, by } from 'protractor';
export class SpecMapperPage {
getImportDateSubmittedColumnValues() {
let stringDatesArray: Array<string> = [];
// currently this css selector gets rows in both import and export tables
// TODO: get better identifiers on the import and export tables and columns
element.all(by.css('md-card-content tbody tr.ng-tns-c3-0')).each(function(row, index){
// check outerHTML for presence of "unclickable", the rows in the export table
row.getAttribute('outerHTML').then(function(outerHTML:string) {
// specifically look for rows without unclickable
if(outerHTML.indexOf("unclickable") < 0){
// grab the columns and get the third column, where the date submitted field is
// TODO: get better identifiers on the import and export columns
row.all(by.css("td.ng-tns-c3-0")).get(2).getText().then(function(text:string) {
stringDatesArray.push(text);
});
}
});
});
return stringDatesArray;
}
}
I know it's not the prettiest code, but it's temporary place holder while my devs make me better attributes/classes/ids to grab my variables. Key things to note is that I create a string Array to hold the values I consider relevant to be returned when the method is finished.
I used WebStorm and put a breakpoint at the stringDatesArray.push(text) and return stringDatesArray lines. The first line shows that the text variable has a string variable that I'm looking for and is successfully getting pushed. I see the success in debug mode as I can see the stringDatesArray and see the values in it. The second line though, the array return, shows that the local variable stringDatesArray is empty. This is echoed in the following code when I try to console.log the array:
The Protractor run Spec class with my test in it:
import { SpecMapperPage } from "./app.po";
import {browser, ExpectedConditions} from "protractor";
describe('spec mapper app', () => {
let page: SpecMapperPage;
let PROJECT_ID: string = '57';
let PROJECT_NAME: string = 'DO NOT DELETE - AUTOMATED TESTING PROJECT';
beforeEach(() => {
page = new SpecMapperPage();
});
describe('import/export page', () => {
it('verify sort order is desc', () => {
browser.waitForAngularEnabled(false);
// Step 1: Launch Map Data from Dashboard
page.navigateTo(PROJECT_ID);
browser.driver.sleep(5000).then(() => {
// Verify: Mapping Screen displays
// Verify on the specmapper page by checking the breadcrumbs
expect(page.getProjectNameBreadCrumbText()).toContain(PROJECT_NAME);
expect(page.getProjectMapperBreadCrumbText()).toEqual("MAPPER");
// Verify: Verify Latest Submitted Date is displayed at the top
// Verify: Verify the Submitted Date column is in descending order
console.log(page.getImportDateSubmittedColumnValues());
});
});
});
});
I acknowledge that this code is not actively using the niceties of Protractor, there's a known issue with our app that will not be addressed for a couple of months, so I am accessing the driver directly 99% of the time.
You'll note that I call the method I posted above as the very last line in the browser.driver.sleep().then() clause, page.getImportDateSubmittedColumnValues().
I thought maybe I was running into asynchronous issues with the call being done before the page was loaded, thus I put it in the .then() clause; but learned with debugging that was not the case. This code should work once I have the array returning properly though.
The console.log is printing an empty [] array. That is synonymous with the results I saw when debugging the above method directly in the PageObject SpecMapper class. I wish to do some verification that the strings are returned properly formatted, and then I'm going to do some date order comparisons. I feel like returning an array of data retrieved from a page is not an unusual request, but I can't seem to find a good way to Google what I'm trying to do.
My apologies if I am hitting some very obvious roadblock, I'm still learning the nuances of Typescript/Angular/Protractor. Thank you for your consideration!
My attempted to used collated promises seemed promising, but fell through on execution.
My Updated PageObject SpecMapper Class
import {browser, element, by, protractor} from 'protractor';
export class SpecMapperPage {
getImportDateSubmittedColumnValues() {
let promisesArray = [];
let stringDatesArray: Array<string> = [];
// This CSS selector grabs the import table and any cells with the label .created-date
element.all(by.css('.import-component .created-date')).each(function(cell, index) {
// cell.getText().then(function(text:string) {
// console.log(text);
// });
promisesArray.push(cell.getText());
});
return protractor.promise.all(promisesArray).then(function(results) {
for(let result of results) {
stringDatesArray.push(result);
}
return stringDatesArray;
});
}
}
My Updated Spec test Using The Updated SpecMapper PO Class
import { SpecMapperPage } from "./specMapper.po";
import {browser, ExpectedConditions} from "protractor";
describe('spec mapper app', () => {
let page: SpecMapperPage;
let PROJECT_ID: string = '57';
let PROJECT_NAME: string = 'DO NOT DELETE - AUTOMATED TESTING PROJECT';
beforeEach(() => {
page = new SpecMapperPage();
});
describe('import/export page', () => {
it('TC2963: ImportComponentGrid_ShouldDefaultSortBySubmittedDateInDescendingOrder_WhenPageIsLoaded', () => {
browser.waitForAngularEnabled(false);
// Step 1: Launch Map Data from Dashboard
page.navigateTo(PROJECT_ID);
browser.driver.sleep(5000).then(() => {
// Verify: Mapping Screen displays
// Verify on the specmapper page by checking the breadcrumbs
expect(page.getProjectNameBreadCrumbText()).toContain(PROJECT_NAME);
expect(page.getProjectMapperBreadCrumbText()).toEqual("MAPPER");
// Verify: Verify Latest Submitted Date is displayed at the top
// Verify: Verify the Submitted Date column is in descending order
page.getImportDateSubmittedColumnValues().then(function(results) {
for(let value of results) {
console.log("a value is: " + value);
}
});
});
});
});
});
When I breakpoint in the PO class at the return stringDatesArray; line, I have the following variables in my differing scopes. Note that the promisesArray has 3 objects, but the results array going into the protractor.promise.all( block has 0 objects. I'm not sure what my disconnect is. :/
I think I'm running into a scopes problem that I am having issues understanding. You'll note the commented out promise resolution on the getText(), and this was my POC proving that I am getting the string values I'm expecting, so I'm not sure why it's not working in the Promise Array structure presented as a solution below.
Only other related question that I could find has to do with grabbing a particular row of a table, not specifically aggregating the data to be returned for test verification in Protractor. You can find it here if you're interested.
As you've alluded to your issue is caused by the console.log returning the value of the variable before its actually been populated.
I've taken a snippet from this answer which should allow you to solve it: Is there a way to resolve multiple promises with Protractor?
var x = element(by.id('x')).sendKeys('xxx');
var y = element(by.id('y')).sendKeys('yyy');
var z = element(by.id('z')).sendKeys('zzz');
myFun(x,y,z);
//isEnabled() is contained in the expect() function, so it'll wait for
// myFun() promise to be fulfilled
expect(element(by.id('myButton')).isEnabled()).toBe(true);
// in a common function library
function myFun(Xel,Yel,Zel) {
return protractor.promise.all([Xel,Yel,Zel]).then(function(results){
var xText = results[0];
var yText = results[1];
var zText = results[2];
});
}
So in your code it would be something like
getImportDateSubmittedColumnValues() {
let promisesArray = [];
let stringDatesArray: Array<string> = [];
// currently this css selector gets rows in both import and export tables
// TODO: get better identifiers on the import and export tables and columns
element.all(by.css('md-card-content tbody tr.ng-tns-c3-0')).each(function(row, index){
// check outerHTML for presence of "unclickable", the rows in the export table
row.getAttribute('outerHTML').then(function(outerHTML:string) {
// specifically look for rows without unclickable
if(outerHTML.indexOf("unclickable") < 0){
// grab the columns and get the third column, where the date submitted field is
// TODO: get better identifiers on the import and export columns
promisesArray.push(row.all(by.css("td.ng-tns-c3-0")).get(2).getText());
}
});
});
return protractor.promise.all(promisesArray).then(function(results){
// In here you'll have access to the results
});
}
Theres quite a few different ways you could do it. You could process the data in that method at the end or I think you could return the array within that "then", and access it like so:
page.getImportDateSubmittedColumnValues().then((res) =>{
//And then here you will have access to the array
})
I don't do the Typescript but if you're just looking to get an array of locator texts back from your method, something resembling this should work...
getImportDateSubmittedColumnValues() {
let stringDatesArray: Array<string> = [];
$$('.import-component .created-date').each((cell, index) => {
cell.getText().then(text => {
stringDatesArray.push(text);
});
}).then(() => {
return stringDatesArray;
});
}
The answer ended up related to the answer posted on How do I return the response from an asynchronous call?
The final PageObject class function:
import {browser, element, by, protractor} from 'protractor';
export class SpecMapperPage {
getImportDateSubmittedColumnValues() {
let stringDatesArray: Array<string> = [];
let promisesArray = [];
// return a promise promising that stringDatesArray will have an array of dates
return new Promise((resolve, reject) => {
// This CSS selector grabs the import table and any cells with the label .created-date
element.all(by.css('.import-component .created-date')).map((cell) => {
// Gather all the getText's we want the text from
promisesArray.push(cell.getText());
}).then(() => {
protractor.promise.all(promisesArray).then((results) => {
// Resolve the getText's values and shove into array we want to return
for(let result of results) {
stringDatesArray.push(result);
}
}).then(() => {
// Set the filled array as the resolution to the returned promise
resolve(stringDatesArray);
});
});
});
}
}
The final test class:
import { SpecMapperPage } from "./specMapper.po";
import {browser, ExpectedConditions} from "protractor";
describe('spec mapper app', () => {
let page: SpecMapperPage;
let PROJECT_ID: string = '57';
let PROJECT_NAME: string = 'DO NOT DELETE - AUTOMATED TESTING PROJECT';
beforeEach(() => {
page = new SpecMapperPage();
});
describe('import/export page', () => {
it('TC2963: ImportComponentGrid_ShouldDefaultSortBySubmittedDateInDescendingOrder_WhenPageIsLoaded', () => {
browser.waitForAngularEnabled(false);
// Step 1: Launch Map Data from Dashboard
page.navigateTo(PROJECT_ID);
browser.driver.sleep(5000).then(() => {
// Verify: Mapping Screen displays
// Verify on the specmapper page by checking the breadcrumbs
expect(page.getProjectNameBreadCrumbText()).toContain(PROJECT_NAME);
expect(page.getProjectMapperBreadCrumbText()).toEqual("MAPPER");
// Verify: Verify Latest Submitted Date is displayed at the top
// Verify: Verify the Submitted Date column is in descending order
page.getImportDateSubmittedColumnValues().then((results) => {
console.log(results);
});
});
});
});
});
The biggest thing was waiting for the different calls to get done running and then waiting for the stringDataArray to be filled. That required the promise(resolve,reject) structure I found in the SO post noted above. I ended up using the lambda (()=>{}) function calls instead of declared (function(){}) for a cleaner look, the method works the same either way. None of the other proposed solutions successfully propagated the array of strings back to my test. I'm working in Typescript, with Protractor.

Why my PostUpdateOrder Plugin executed twice CRM 2013

After the user validate an order, the status of the order is set so validated and it is sent to another system X, the problem is that the plugin is fired twiced in some cases even more than twice and that lead to sending this entity multiple time to the system X. I tried to correct that by using the context.depth, but all the time is equal to 1.
JS Method:
Validate: function () {
Xrm.Page.getAttribute("eoz_validated").setValue(true);
Xrm.Page.data.entity.save();
ABE.Order.HideVisibleField();
Xrm.Page.ui.clearFormNotification('ProductError');
}
}
Plugin Execute method:
protected void ExecutePostOrderUpdate(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
if (localContext.PluginExecutionContext.Depth > 1)
{
return;
}
tracingService = localContext.TracingService;
var order = (Entity)localContext.PluginExecutionContext.InputParameters["Target"];
bool isValidated = order.GetAttributeValue<OptionSetValue>("abe_isValidated").Value : false;
if (isValidated )
{
SendToSystemX(localContext.OrganizationService, order.Id);
SendProductsToOwner(localContext.OrganizationService, order.Id);
}
var statecode = order.Contains("statecode") ? order.GetAttributeValue<OptionSetValue>("statecode").Value : -1;
}
If your plugin is registered to trigger on update of "eoz_validated" and also updates "eoz_validated" then you can have an infinite execution loop.
To avoid this, before updating your context entity, reinstantiate it:
var updatedEntity = new Entity { LogicalName = context.LogicalName, Id = context.Id };
This removes all attributes that would otherwise have been updated such as "eoz_validated" which is contained within the context entity. Note that in your code you store the context entity within a variable called order.
I'm just guessing here (and don't have 50 reputation to ask a question). If this is happening in your code then presumably it's within SendToSystemX(IOrganizationService, Guid) or SendProductsToOwner(IOrganizationService, Guid).

Protractor: Issue using repeater and filter properly

I'm currently trying to iterate through a repeater and delete campaigns which have been created by my test. The name is passed in via the call from the spec file, it should then use this to filter any campaigns in the repeater, and select the delete button when one with that name is found, currently it doesn't seem to work, and I'm not sure why as I'm not getting any errors for element not found etc. Therefore I've assumed my logic is wrong.
I'm using the pageObject.js to support the spec.js file.
pageObject.js
this.campaignDelete = function(name) { // Currently not tested.
element.all('campaign in campaignsName').filter(function(item) {
item.element(by.binding('campaign.Name')).getText().then(function(title) {
item.foreach(function() {
if(title === name) {
this.delete = element(by.css('[ng-click="showDeleteCampaignModel(campaign)"]')).click();
this.confirm = element(by.css('[ng-click="deleteCampaign()"]')).click();
}
});
});
});
};
Spec.js
describe('Deletes the newly created campaign', function() {
beforeEach(function() {
browser.get(browser.params.urls.dashboard);
});
it('Should delete the newly created campaign', function() {
campaignPage.campaignDelete(browser.params.campaign.name);
});
});
The second line in your code is missing the by.repeater locator strategy, it should be - element.all(by.repeater('campaign in campaignsName')).filter(function(item) {
You can directly get all the campaign name elements using - element.all(by.repeater('campaign in campaignsName').column('campaign.Name'))
by.repeater
This below code should be able to return you the matching campaign.
var reqcamp = element.all(by.repeater('campaign in campaignsName').column('campaign.Name')).filter(function(elem, index) {
return elem.getText().then(function(text) {
return text === name;
});
}).first();
I am not sure the delete statement ie. css part, is correct (not sure without html). Should it not include the relevant campaign to begin with, else it will just find the first matching element on the page.

sequelize.js relationship validation

Imagine I have two models:
var Movie = sequelize.define('movies', {
/* model definition */
})
var Genre = sequelize.define('genres', {
/* model definition */
});
Movie.hasMany(Genre);
Genre.hasMany(Movie);
If I wanted to stipulate that a Movie MUST have at least one Genre, how would I go about doing that?
I've looked in the obvious places. My initial idea was to build(), validate() and save(), however looking at the source .validate() only accommodates fields defined in the model definition.
e.g.
Genre.find({where:{'name':'horror})
.success(function (horrorGenre) {
var movie = Movie.build({..});
movie.addGenre(horrorGenre);
if (! movie.validate()) { // This doesn't consider related data
movie.save();
}
});
So I figure I need to implement some kind of custom validation mechanism, but I'm not entirely sure where to start.
NOTE I'm maintaining my own fork of Sequelize, so this is more of a question of how I might go about modifying the Sequelize source to do what I want it to do versus throwing together a hacky solid implementation.
you can try to search for genre objects in database and call addGenre for movie
Genre.findall({where:{'name':["genre1","genre1"]})
.success(function (genres) {
if(genres.length==0){
console.log("Genres were not found!");
// exit somehow maybe res.json(200,{"msg","not ok"});
}
var movie = Movie.build({..});
var queryChainer = new Sequelize.Utils.QueryChainer;
for(var i = 0 ; i != genres.length ; i++){
queryChainer.add(movie.addGenre(genres[i].id));
}
queryChainer.run().success(function(){}).error(function(){});
});
this way you will know that at least 1 genre will be added to submitted movie!

Breeze Partial initializer

I have a Single Page Application that is working pretty well so far but I have run into an issue I am unable to figure out. I am using breeze to populate a list of projects to be displayed in a table. There is way more info than what I actually need so I am doing a projection on the data. I want to add a knockout computed onto the entity. So to accomplish this I registered and entity constructor like so...
metadataStore.registerEntityTypeCtor(entityNames.project, function () { this.isPartial = false; }, initializeProject);
The initializeProject function uses some of the values in the project to determine what the values should be for the computed. For example if the Project.Type == "P" then the rowClass should = "Red".
The problem I am having is that all the properties of Project are null except for the ProjNum which happens to be the key. I believe the issue is because I am doing the projection because I have registered other initializers for other types and they work just fine. Is there a way to make this work?
EDIT: I thought I would just add a little more detail for clarification. The values of all the properties are set to knockout observables, when I interrogate the properties using the javascript debugger in Chrome the _latestValue of any of the properties is null. The only property that is set is the ProjNum which is also the entity key.
EDIT2: Here is the client side code that does the projection
var getProjectPartials = function (projectObservable, username, forceRemote) {
var p1 = new breeze.Predicate("ProjManager", "==", username);
var p2 = new breeze.Predicate("ApprovalStatus", "!=", "X");
var p3 = new breeze.Predicate("ApprovalStatus", "!=", "C");
var select = 'ProjNum,Title,Type,ApprovalStatus,CurrentStep,StartDate,ProjTargetDate,CurTargDate';
var isQaUser = cookies.getCookie("IsQaUser");
if (isQaUser == "True") {
p1 = new breeze.Predicate("QAManager", "==", username);
select = select + ',QAManager';
} else {
select = select + ',ProjManager';
}
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select);
if (!forceRemote) {
var p = getLocal(query);
if (p.length > 1) {
projectObservable(p);
return Q.resolve();
}
}
return manager.executeQuery(query).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
var list = partialMapper.mapDtosToEntities(
manager,
data.results,
model.entityNames.project,
'ProjNum'
);
if (projectObservable) {
projectObservable(list);
}
log('Retrieved projects using breeze', data, true);
}
};
and the code for the partialMapper.mapDtosToEntities function.
var defaultExtension = { isPartial: true };
function mapDtosToEntities(manager,dtos,entityName,keyName,extendWith) {
return dtos.map(dtoToEntityMapper);
function dtoToEntityMapper(dto) {
var keyValue = dto[keyName];
var entity = manager.getEntityByKey(entityName, keyValue);
if (!entity) {
extendWith = $.extend({}, extendWith || defaultExtension);
extendWith[keyName] = keyValue;
entity = manager.createEntity(entityName, extendWith);
}
mapToEntity(entity, dto);
entity.entityAspect.setUnchanged();
return entity;
}
function mapToEntity(entity, dto) {
for (var prop in dto) {
if (dto.hasOwnProperty(prop)) {
entity[prop](dto[prop]);
}
}
return entity;
}
}
EDIT3: Looks like it was my mistake. I found the error when I looked closer at initializeProject. Below is what the function looked like before i fixed it.
function initializeProject(project) {
project.rowClass = ko.computed(function() {
if (project.Type == "R") {
return "project-list-item info";
} else if (project.Type == "P") {
return "project-list-item error";
}
return "project-list-item";
});
}
the issue was with project.Type I should have used project.Type() since it is an observable. It is a silly mistake that I have made too many times since starting this project.
EDIT4: Inside initializeProject some parts are working and others aren't. When I try to access project.ProjTargetDate() I get null, same with project.StartDate(). Because of the Null value I get an error thrown from the moment library as I am working with these dates to determine when a project is late. I tried removing the select from the client query and the call to the partial entity mapper and when I did that everything worked fine.
You seem to be getting closer. I think a few more guard clauses in your initializeProject method would help and, when working with Knockout, one is constantly battling the issue of parentheses.
Btw, I highly recommend the Knockout Context Debugger plugin for Chrome for diagnosing binding problems.
Try toType()
You're working very hard with your DTO mapping, following along with John's code from his course. Since then there's a new way to get projection data into an entity: add toType(...) to the end of the query like this:
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select)
.toType('Project'); // cast to Project
It won't solve everything but you may be able to do away with the dto mapping.
Consider DTOs on the server
I should have pointed this out first. If you're always cutting this data down to size, why not define the client-facing model to suit your client. Create DTO classes of the right shape(s) and project into them on the server before sending data over the wire.
You can also build metadata to match those DTOs so that Project on the client has exactly the properties it should have there ... and no more.
I'm writing about this now. Should have a page on it in a week or so.

Categories

Resources