-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement auto-recover functionality [B: 1403] #483
base: dev
Are you sure you want to change the base?
Changes from all commits
ec64c0b
39498e3
de0bcfd
ef3db97
f31e3f6
d0f0de6
12c606a
d852b03
fba4f8a
e7ab764
a227963
68fc58b
25dea35
70a447d
041ba36
73fd8a5
a13063b
10f3ea0
d649a50
48b7d9b
906c5d8
ac6e033
b5579e4
2b85d32
d21d155
73c1c88
c9f845b
4f101b5
9cd46bb
7ac8772
06baf02
1f261a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -178,7 +178,7 @@ jobs: | |
|
||
strategy: | ||
matrix: | ||
node-version: [18.x, 20.x] | ||
node-version: [18.x, 20.x, 22.x] | ||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ | ||
|
||
steps: | ||
|
@@ -187,6 +187,7 @@ jobs: | |
uses: actions/setup-node@v4 | ||
with: | ||
node-version: ${{ matrix.node-version }} | ||
cache: 'yarn' | ||
cache: 'npm' | ||
- run: npx update-browserslist-db@latest | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cache and browsers list for 18.x was "complaining" |
||
- run: yarn | ||
- run: yarn jest --passWithNoTests |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,6 +94,9 @@ const config = { | |
"^validation$": "<rootDir>/src/frontend/js/lib/validation", | ||
"^logging$": "<rootDir>/src/frontend/js/lib/logging", | ||
"^util/(.*)$": "<rootDir>/src/frontend/js/lib/util/$1", | ||
"^components/(.*)$": "<rootDir>/src/frontend/components/$1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mapping was inconsistent between this file and |
||
"^set-field-values$": "<rootDir>/src/frontend/js/lib/set-field-values", | ||
"^guid$": "<rootDir>/src/frontend/js/lib/guid", | ||
}, | ||
|
||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1448,6 +1448,17 @@ any ['get', 'post'] => '/api/users' => require_any_role [qw/useradmin superadmin | |
return encode_json $return; | ||
}; | ||
|
||
get '/api/get_key' => require_login sub { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to API route for consistency |
||
my $user = logged_in_user; | ||
|
||
my $key = $user->encryption_key; | ||
|
||
return to_json { | ||
error => 0, | ||
key => $key | ||
} | ||
}; | ||
|
||
sub _success | ||
{ my $msg = shift; | ||
send_as JSON => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { clearSavedFormValues } from "./common"; | ||
|
||
export default function createCancelButton(el: HTMLElement | JQuery<HTMLElement>) { | ||
const $el = $(el); | ||
if ($el[0].tagName !== 'BUTTON') return; | ||
$el.data('cancel-button', "true"); | ||
$el.on('click', async () => { | ||
const href = $el.data('href'); | ||
await clearSavedFormValues($el.closest('form')); | ||
if (href) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should only work for buttons, and should have the HREF value set (to make the button act like a link where used) - if it isn't, just go back in the history. |
||
window.location.href = href; | ||
else | ||
window.history.back(); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import "../../../testing/globals.definitions"; | ||
import {layoutId, recordId, table_key} from "./common"; | ||
|
||
describe("Common button tests",()=>{ | ||
it("should populate table_key",()=>{ | ||
expect(table_key()).toBe("linkspace-record-change-undefined-0"); // Undefined because $('body').data('layout-identifier') is not defined | ||
}); | ||
|
||
it("should have a layoutId", ()=>{ | ||
$('body').data('layout-identifier', 'layoutId'); | ||
expect(layoutId()).toBe('layoutId'); | ||
}); | ||
|
||
it("should have a recordId", ()=>{ | ||
expect(isNaN(parseInt(location.pathname.split('/').pop() ?? ""))).toBe(true); | ||
expect(recordId()).toBe(0); | ||
}); | ||
|
||
it("should populate table_key fully",()=>{ | ||
$('body').data('layout-identifier', 'layoutId'); | ||
expect(table_key()).toBe("linkspace-record-change-layoutId-0"); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import gadsStorage from "util/gadsStorage"; | ||
|
||
export async function clearSavedFormValues($form: JQuery<HTMLElement>) { | ||
if (!$form || $form.length === 0) return; | ||
const layout = layoutId(); | ||
const record = recordId(); | ||
const ls = storage(); | ||
const item = await ls.getItem(table_key()); | ||
|
||
if (item) ls.removeItem(`linkspace-record-change-${layout}-${record}`); | ||
await Promise.all($form.find(".linkspace-field").map(async (_, el) => { | ||
const field_id = $(el).data("column-id"); | ||
const item = await gadsStorage.getItem(`linkspace-column-${field_id}-${layout}-${record}`); | ||
if (item) gadsStorage.removeItem(`linkspace-column-${field_id}-${layout}-${record}`); | ||
})); | ||
} | ||
|
||
export function layoutId() { | ||
return $('body').data('layout-identifier'); | ||
} | ||
|
||
export function recordId() { | ||
return isNaN(parseInt(location.pathname.split('/').pop())) ? 0 : parseInt(location.pathname.split('/').pop()); | ||
} | ||
|
||
export function table_key() { | ||
return `linkspace-record-change-${layoutId()}-${recordId()}`; | ||
} | ||
|
||
export function storage() { | ||
return location.hostname === 'localhost' || window.test ? localStorage : gadsStorage; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -187,12 +187,14 @@ class RenameButton { | |
} | ||
} | ||
|
||
(function ($) { | ||
$.fn.renameButton = function () { | ||
return this.each(function (_: unknown, el: HTMLButtonElement) { | ||
new RenameButton(el); | ||
}); | ||
}; | ||
})(jQuery); | ||
if(typeof jQuery !== 'undefined') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some of the tests failed here. As this component isn't (currently) tested fully, it is thought that it is valid to remove the plugin functionality should jQuery be undefined. |
||
(function ($) { | ||
$.fn.renameButton = function () { | ||
return this.each(function (_: unknown, el: HTMLButtonElement) { | ||
new RenameButton(el); | ||
}); | ||
}; | ||
})(jQuery); | ||
} | ||
|
||
export { RenameEvent }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,16 @@ | ||
import { clearSavedFormValues } from "./common"; | ||
|
||
/** | ||
* Create a submit draft record button | ||
* @param element {JQuery<HTMLElement>} The button element | ||
*/ | ||
export default function createSubmitDraftRecordButton(element: JQuery<HTMLElement>) { | ||
element.on("click", (ev: JQuery.ClickEvent) => { | ||
element.on("click", async (ev: JQuery.ClickEvent) => { | ||
const $button = $(ev.target).closest('button'); | ||
const $form = $button.closest("form"); | ||
|
||
// Remove the required attribute from hidden required dependent fields | ||
$form.find(".form-group *[aria-required]").removeAttr('required'); | ||
await clearSavedFormValues(ev.target.closest("form")); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
.field--changed { | ||
input, .jstree-container-ul, .form-control { | ||
background-color: $field-highlight; | ||
border-radius: $input-border-radius; | ||
} | ||
} | ||
|
||
.modal-autosave { | ||
max-height: 20rem; | ||
overflow-y: auto; | ||
overflow-x: hidden; | ||
} | ||
|
||
li.li-success { | ||
list-style: none; | ||
&::before { | ||
content: '\2713'; | ||
color: green; | ||
font-size: 1.5em; | ||
margin-right: 0.5em; | ||
} | ||
} | ||
|
||
li.li-error { | ||
list-style: none; | ||
&::before { | ||
content: '\2717'; | ||
color: red; | ||
font-size: 1.5em; | ||
margin-right: 0.5em; | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not entirely sure what the difference here is, but it works like this (as opposed to the previous version) so I'm not worried. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { initializeComponent } from 'component'; | ||
import AutosaveComponent from './lib/component'; | ||
import AutosaveModal from './lib/modal'; | ||
import gadsStorage from 'util/gadsStorage'; | ||
|
||
export default (scope) => { | ||
if (gadsStorage.enabled) { | ||
try { | ||
initializeComponent(scope, '.linkspace-field', AutosaveComponent); | ||
initializeComponent(scope, '#restoreValuesModal', AutosaveModal); | ||
} catch(e) { | ||
console.error(e); | ||
$('.content-block__main-content').prepend('<div class="alert alert-danger">Autosave failed to initialize. ' + e.message ? e.message : e + '</div>'); | ||
} | ||
} else { | ||
$('.content-block__main-content').prepend('<div class="alert alert-warning">Autosave is disabled as your browser does not support encryption</div>'); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import "../../../../testing/globals.definitions"; | ||
import AutosaveBase from './autosaveBase'; | ||
|
||
class TestAutosave extends AutosaveBase { | ||
initAutosave(): void { | ||
console.log('initAutosave'); | ||
} | ||
} | ||
|
||
describe('AutosaveBase', () => { | ||
beforeAll(() => { | ||
document.body.innerHTML = ` | ||
<body> | ||
<div id="test"></div> | ||
</body> | ||
`; | ||
$('body').data('layout-identifier', 1); | ||
}); | ||
|
||
afterAll(()=>{ | ||
document.body.innerHTML = ''; | ||
}); | ||
|
||
it('should return layoutId', () => { | ||
const autosave = new TestAutosave(document.getElementById('test')!); | ||
expect(autosave.layoutId).toBe(1); | ||
}); | ||
|
||
it('should return recordId', () => { | ||
const autosave = new TestAutosave(document.getElementById('test')!); | ||
expect(autosave.recordId).toBe(0); | ||
}); | ||
|
||
it('should return table_key', () => { | ||
const autosave = new TestAutosave(document.getElementById('test')!); | ||
expect(autosave.table_key).toBe('linkspace-record-change-1-0'); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Component } from "component"; | ||
import gadsStorage from "util/gadsStorage"; | ||
|
||
export default abstract class AutosaveBase extends Component { | ||
constructor(element: HTMLElement) { | ||
super(element); | ||
this.initAutosave(); | ||
} | ||
|
||
get isClone() { | ||
return location.search.includes('from'); | ||
} | ||
|
||
get layoutId() { | ||
return $('body').data('layout-identifier'); | ||
} | ||
|
||
get recordId() { | ||
return $('body').find('.form-edit').data('current-id') || 0; | ||
} | ||
|
||
get storage() { | ||
return gadsStorage; | ||
} | ||
|
||
get table_key() { | ||
return `linkspace-record-change-${this.layoutId}-${this.recordId}`; | ||
} | ||
|
||
columnKey($field: JQuery) { | ||
return `linkspace-column-${$field.data('column-id')}-${this.layoutId}-${this.recordId}`; | ||
} | ||
|
||
abstract initAutosave(): void; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update to use all currently supported node versions