GoldenLayout - How can I gracefully handle an unknown component during init - golden-layout

I am working on a webapp that is served up "modules/widgets" that dynamically register themselves to GoldenLayout. The layout config is persisted and applied on startup to retain the user's window layout. Due to a number of different factors, a "module/widget" that was available to them could be removed. If that user had that "module/widget" out on their screen the last time they were logged in it is saved in the goldenlayout config. WHen the user next logs in they receive a "Configuration Error" for an "unknown component". Is there a way for GoldenLayout to just ignore unknown compoenents listed in the config?

I will post my solution for future reference:
layout fails on command layout.init()
this is an edge case for us => happens when users disable some component via feature toggle or when test team loads and old application version that does not yet contain the component
we do not want to implement any complex solution to analyze layout and remove specific view but have the most robust solution => restore default layout
Here is how I solved it:
try {
this.layout.init();
} catch (e) {
console.error('Layout initialization failed', e);
// => delete persisted layout
// => display some toast notification that layout initialization has failed and reload/restore will happen
setTimeout(() => location.reload(), 5000); // reload with default layout (no persisted layout present)
return;
}

Related

PWA creates IndexedDB database, but does not create any object stores

I have a Vue PWA and it stopped creating my IndexDB object stores on first load or upgrade. Here is my code, I am using the latest version of IDB (https://github.com/jakearchibald/idb):
await openDB('dbname', 1, {
upgrade(db, oldVersion, newVersion, transaction) {
switch (newVersion) {
case 0:
// a placeholder case so that the switch block will
// execute when the database is first created
// (oldVersion is 0)
// falls through
case 1:
db.createObjectStore('change_log', {keyPath: 'id'});
db.createObjectStore('person', {keyPath: 'id'})
.createIndex('username', 'username');
break;
}
}
});
I have tried multiple browsers and incognito tabs, etc. and the same thing always happens. The database is created, but no object stores are created. I use developer tools to clear all the data in the PWA and refresh but the same thing happens.
If I increment the version number, the version of my database gets updated in the browser, but the object stores still do not get added.
The upgrade() function does not get called.
I had this happen to me earlier in my development, and I fixed it, but I can't remember how. I feel like it may not actually be a coding issue...
OK, I found the problem. I added a logging mechanism to my App and there was code running BEFORE my upgrade code that was opening the database to create a log entry. Therefore, it was creating the database (with no object stores) before my upgrade method was being called. I changed my open database code to always include the upgrade method to solve my problems.

Wait for backend service response before making changes to ag-grid table

I am using ag-grid/ag-grid-angular to provide an editable grid of data backed by a database. When a user edits a cell I want to be able to post the update to the backend service and if the request is successful update the grid and if not undo the user's changes and show an error.
I have approached this problem from a couple different angles but have yet to find the solution that meets all my requirements and am also curious about what the best practice would be to implement this kind of functionality.
My first thought was to leverage the cellValueChanged event. With this approach I can see the old and new values and then make a call to my service to update the database. If the request is successful then everything is great and works as expected. However, if the request fails for some reason then I need to be able to undo the user's changes. Since I have access to the old value I can easily do something like event.node.setDataValue(event.column, event.oldValue) to revert the user's changes. However, since I am updating the grid again this actually triggers the cellValueChanged event a second time. I have no way of knowing that this is the result of undoing the user's changes so I unnecessarily make a call to my service again to update the data even though the original request was never successful in updating the data.
I have also tried using a custom cell editor to get in between when the user is finished editing a cell and when the grid is actually updated. However, it appears that there is no way to integrate an async method in any of these classes to be able to wait for a response from the server to decide whether or not to actually apply the user's changes. E.g.
isCancelBeforeStart(): boolean {
this.service.updateData(event.data).subscribe(() => {
return false;
}, error => {
return true;
});
}
does not work because this method is synchronous and I need to be able to wait for a response from my service before deciding whether to cancel the edit or not.
Is there something I am missing or not taking in to account? Or another way to approach this problem to get my intended functionality? I realize this could be handled much easier with dedicated edit/save buttons but I am ideally looking for an interactive grid that is saving the changes to the backend as the user is making changes and providing feedback in cases where something went wrong.
Any help/feedback is greatly appreciated!
I understand what you are trying to do, and I think that the best approach is going to be to use a "valueSetter" function on each of your editable columns.
With a valueSetter, the grid's value will not be directly updated - you will have to update your bound data to have it reflected in the grid.
When the valueSetter is called by the grid at the end of the edit, you'll probably want to record the original value somehow, update your bound data (so that the grid will reflect the change), and then kick off the back-end save, and return immediately from the valueSetter function.
(It's important to return immediately from the valueSetter function to keep the grid responsive. Since the valueSetter call from the grid is synchronous, if you try to wait for the server response, you're going to lock up the grid while you're waiting.)
Then, if the back-end update succeeds, there's nothing to do, and if it fails, you can update your bound data to reflect the original value.
With this method, you won't have the problem of listening for the cellValueChanged event.
The one issue that you might have to deal with is what to do if the user changes the cell value, and then changes it again before the first back-end save returns.
onCellValueChanged: (event) => {
if (event.oldValue === event.newValue) {
return;
}
try {
// apiUpdate(event.data)
}
catch {
event.node.data[event.colDef.Field] = event.oldValue;
event.node.setDataValue(event.column, event.oldValue);
}
}
By changing the value back on node.data first, when setDataValue() triggers the change event again, oldValue and newValue are actually the same now and the function returns, avoiding the rather slow infinite loop.
I think it's because you change the data behind the scenes directly without agGrid noticing with node.data = , then make a change that agGrid recognises and rerenders the cell by calling setDataValue. Thereby tricking agGrid into behaving.
I would suggest a slightly better approach than StangerString, but to credit him the idea came from his approach. Rather than using a test of the oldValue/newValue and allowing the event to be called twice, you can go around the change detection by doing the following.
event.node.data[event.colDef.field] = event.oldValue;
event.api.refreshCells({ rowNodes: [event.node], columns: [event.column.colId] });
What that does is sets the data directly in the data store used by aggrid, then you tell it to refresh that grid. That will prevent the onCellValueChanged event from having to be called again.
(if you arent using colIds you can use the field or pass the whole column, I think any of them work)

Protractor Failed: element not interactable

I am running an E2E test for an Angular 7.x app. The test runs straight forward on my local machine. But when I push it on the repo (GitLab), then pipeline fails and throws following error:
USER PROFILE - Check and change PROFILE
- Failed: element not interactable
(Session info: chrome=71.0.3578.80)
(Driver info: chromedriver=2.45.615279 (12b89733300bd268cff3b78fc76cb8f3a7cc44e5),platform=Linux 4.14.74-coreos x86_64)
Test Case:
it('USER PROFILE - Check and change PROFILE', () => {
page.navigateTo();
browser.sleep(1000);
expect(page.getProfileEditTagName()).toMatch('app-edit-profile');
expect(element(by.className('logged-as')).getText()).toBe( testEmail );
browser.actions().mouseMove( element.all( by.id('editIcon-salutation') ).get(0)).click().perform().then(function () {
browser.sleep(4000);
element( by.className('mat-select-arrow') ).click().then(function () {
browser.actions().mouseMove( element.all(by.className('option-value mat-option')).get(0)).click().perform();
browser.sleep(1000);
browser.actions().mouseMove( element.all( by.id('saveButton-salutation') ).get(0)).click().perform();
browser.sleep(1000);
});
});
});
navigateTo() is just a method in profile.po.ts:
navigateTo() {
browser.get('/profileComponentUrl');
}
What's confusing me and where I even can't localize the bug or what's wrong, is that it works fine locally. But once I push to repo, then it fails exactly at that test case. Any Hint please?
The reason element is not interactable could be - performing an action on hidden or obscured element.
You can try -
1. add sleep after by.className('mat-select-arrow') ).click(), as I can see you have not added any waits there.
2. Try to check if you running the test on your local and Jenkins machine with same screen resolution. (this could be the reason of obscured element)
I'd recommend to:
Enable the stacktrace in protractor config: new SpecReporter({ spec: { displayStacktrace: true } }) so you can see exactly what element is throwing the error. This won't solve it but at least should point you in the right direction.
Then if you're using tabs, buttons or other elements that hide/show/disable/enable or change the DOM view, you add a browser.sleep(100) after calling a .click()
I had a same kind of problem and I found this.
I copy pasted that (and some other minor tweaks for example force clicking on previous page in for-loop) and it worked. I believe that the browser.driver.manage().window().maximize(); was part of the solution.
One reason which i figure out is the scroll issue. You need to check the element is displaying properly or not. It may be hidden. So use scrollToTop/scrollToElement/scrollToElementView etc. You can write different scroll methods which suites the condition better.
Another reason is the locator. Try to change the locator, do not trim the locator too much. Just try with full body css locator, if it works then trim properly. Some time in chrome console it may work but not with the test case.

Delphi: How to restore a form's original location when monitor configuration changes?

I have a multi-form application in which a child form is positioned on the second monitor on startup, at which time its BoundsRect is saved.
When the computer's display configuration changes, Windows moves the form to the first (primary) monitor. I can catch this change with WM_DISPLAYCHANGE:
procedure WMDisplayChange(var msg: TWMDisplayChange); message WM_DISPLAYCHANGE;
What I'm interested in doing is moving the child form back to the second monitor when it reappears in the configuration (i.e. Screen.MonitorCount goes from 1 to 2), e.g.:
childForm.BoundsRect := childForm.m_WorkingBounds;
// (or)
childForm.BoundsRect := Screen.Monitors[Screen.MonitorCount-1].BoundsRect;
However this assignment is have no affect -- the child form stays on monitor 0.
I've tried other approaches, such as SetWindowPos(), with no success ...
Root of your problem is in the fact that Delphi VCL does not refresh its internal list of monitors when they actually change. You have to force that refresh yourself.
Monitors are refreshed with TScreen.GetMonitors method that is unfortunately private method so you cannot call it directly.
However, TApplication.WndProc(var Message: TMessage) processes WM_WTSSESSION_CHANGE and upon receiving that message it calls Screen.GetMonitors - this is most benign way to achieve your goal.
When you receive notifications that monitors are changed just send it to Application:
SendMessage(Application.Handle, WM_WTSSESSION_CHANGE, 0, 0);
I tested this with old version Delphi5 and it worked easy just to:
Screen.Free;
Screen := TScreen.Create(Nil);
The screen handling has changed in later versions of Delphi, however a similar approach may work.

WMI and Win32_DeviceChangeEvent - Wrong event type returned?

I am trying to register to a "Device added/ Device removed" event using WMI. When I say device - I mean something in the lines of a Disk-On-Key or any other device that has files on it which I can access...
I am registering to the event, and the event is raised, but the EventType propery is different from the one I am expecting to see.
The documentation (MSDN) states : 1- config change, 2- Device added, 3-Device removed 4- Docking. For some reason I always get a value of 1.
Any ideas ?
Here's sample code :
public class WMIReceiveEvent
{
public WMIReceiveEvent()
{
try
{
WqlEventQuery query = new WqlEventQuery(
"SELECT * FROM Win32_DeviceChangeEvent");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
Console.WriteLine("Waiting for an event...");
watcher.EventArrived +=
new EventArrivedEventHandler(
HandleEvent);
// Start listening for events
watcher.Start();
// Do something while waiting for events
System.Threading.Thread.Sleep(10000);
// Stop listening for events
watcher.Stop();
return;
}
catch(ManagementException err)
{
MessageBox.Show("An error occurred while trying to receive an event: " + err.Message);
}
}
private void HandleEvent(object sender,
EventArrivedEventArgs e)
{
Console.WriteLine(e.NewEvent.GetPropertyValue["EventType"]);
}
public static void Main()
{
WMIReceiveEvent receiveEvent = new WMIReceiveEvent();
return;
}
}
Well, I couldn't find the code. Tried on my old RAC account, nothing. Nothing in my old backups. Go figure. But I tried to work out how I did it, and I think this is the correct sequence (I based a lot of it on this article):
Get all drive letters and cache
them.
Wait for the WM_DEVICECHANGE
message, and start a timer with a
timeout of 1 second (this is done to
avoid a lot of spurious
WM_DEVICECHANGE messages that start
as start as soon as you insert the
USB key/other device and only end
when the drive is "settled").
Compare the drive letters with the
old cache and detect the new ones.
Get device information for those.
I know there are other methods, but that proved to be the only one that would work consistently in different versions of windows, and we needed that as my client used the ActiveX control on a webpage that uploaded images from any kind of device you inserted (I think they produced some kind of printing kiosk).
Oh! Yup, I've been through that, but using the raw Windows API calls some time ago, while developing an ActiveX control that detected the insertion of any kind of media. I'll try to unearth the code from my backups and see if I can tell you how I solved it. I'll subscribe to the RSS just in case somebody gets there first.
Well,
u can try win32_logical disk class and bind it to the __Instancecreationevent.
You can easily get the required info
I tried this on my system and I eventually get the right code. It just takes a while. I get a dozen or so events, and one of them is the device connect code.