From 56c30fcedf93f254f6a2f9f9d405e147aafd6c98 Mon Sep 17 00:00:00 2001 From: ermbutler Date: Fri, 24 Sep 2021 11:30:16 +0000 Subject: [PATCH 01/24] Saving progress of refactor of attribute form patch --- .../project-settings/type-forms/type-form.js | 343 ++++++------- test/test_settings.py | 470 +++++++++--------- 2 files changed, 398 insertions(+), 415 deletions(-) diff --git a/main/static/js/project-settings/type-forms/type-form.js b/main/static/js/project-settings/type-forms/type-form.js index 32c09b24e..a5babbbb3 100644 --- a/main/static/js/project-settings/type-forms/type-form.js +++ b/main/static/js/project-settings/type-forms/type-form.js @@ -170,7 +170,7 @@ class TypeForm extends TatorElement { form.typeId = saveReturnId; // init form with the data - this._fetchByIdPromise({id : saveReturnId}).then(resp => resp.json()).then( data=> { + this._fetchByIdPromise({ id: saveReturnId }).then(resp => resp.json()).then(data => { console.log(data); form._init({ data, @@ -280,7 +280,7 @@ class TypeForm extends TatorElement { this.resetLink.setAttribute("class", `px-5 f1 text-gray hover-text-white`); let resetLinkText = document.createTextNode("Reset"); - this.resetLink.appendChild( resetLinkText ); + this.resetLink.appendChild(resetLinkText); // Form reset event this.resetLink.addEventListener("click", (event) => { @@ -459,156 +459,187 @@ class TypeForm extends TatorElement { }); } - - - async _save({ id = -1, globalAttribute = false } = {}) { - // @TODO add back inline error messaging - // If any fields still have errors don't submit the form. - // const errorList = this._shadow.querySelectorAll(`.errored`); - // if(errorList && errorList.length > 0) return this._modalError("Please fix form errors.");; - - // Start spinner & Get promises list - // console.log("Settings _save method for id: " + id); + async _save({ id = -1 } = {}) { this.loading.showSpinner(); - let promises = [] - let errors = 0; // @TODO + //create form data & post promise array for the attribute forms, and submit + this.successMessages = ""; + this.failedMessages = ""; + this.confirmMessages = ""; + this.nameChanged = false; + this.newName = null; + this.saveModalMessage = ""; + this.requiresConfirmation = false; + this.hasAttributeChanges = this.attributeSection && this.attributeSection.hasChanges ? true : false; + + + if (this.isChanged() || this.hasAttributeChanges) { + try { + if (this.isChanged()) { + // Main type form + await this._typeFormChanged(); + } + if (this.hasAttributeChanges) { + // All attribute forms + await this._attrFormsChanged(); + } - this._nameEdit = { - edited: false, - newName: "", - typeName: this.typeName, - typeId: this.typeId - } + // Compiled messages from above + await this._showSaveCompletModal(); + + // Clean up.................. + // Reset changed flags + this.changed = false; + if (this.hasAttributeChanges) { + const attrFormsChanged = this.attributeSection.attrForms.filter(form => form._changed); + if (attrFormsChanged.length > 0) { + for (let f of attrFormsChanged) { + f.changeReset(); + } + } + } - // Main type form - if (this.isChanged()) { - // console.log("Main form was changed"); - const formData = this._getFormData(); - if (Object.entries(formData).length === 0) { - return console.error("No formData"); - } else { - let patchPromise = await this._fetchPatchPromise({ id, formData }); - promises.push(patchPromise); - if (typeof formData.name !== "undefined") { - this._nameEdit.edited = true; - this._nameEdit.newName = formData.name; + // Update related items with an event if required + if (this.nameChanged) { + this._updateNavEvent("rename", this.newName) } + } catch (err) { + console.error("Error saving.", err); + this.loading.hideSpinner(); + return this._modalError("Error saving changes."); } + } else { + this.loading.hideSpinner(); + return this._modalSuccess("Nothing new to save!"); } + } - let hasAttributeChanges = this.attributeSection && this.attributeSection.hasChanges ? true : false; - const attrPromises = { - promises: [], - attrNamesNew: [], - attrNames: [] - }; + _showSaveCompletModal() { + let promise = Promise.resolve(); - if (hasAttributeChanges) { - const attrFormsChanged = this.attributeSection.attrForms.filter(form => form._changed); - if (attrFormsChanged && attrFormsChanged.length > 0) { + return promise.then(() => { + if (this.successMessages) { + let heading = `
Success
`; + this.saveModalMessage += heading + this.successMessages; + } + if (this.failedMessages) { + let heading = `
Error
`; + this.saveModalMessage += heading + this.failedMessages; + } - for (let form of attrFormsChanged) { - let promiseInfo = await form._getPromise({ id, entityType: this.typeName }); - attrPromises.promises.push(promiseInfo.promise); - attrPromises.attrNamesNew.push(promiseInfo.newName); - attrPromises.attrNames.push(promiseInfo.oldName); - } + if (this.requiresConfirmation) { + let buttonSave = this._getAttrGlobalTrigger(this.typeId); + let confirmHeading = `
Global Change(s) Found
` + let subText = `
Confirm to update across all types. Uncheck and confirm, or cancel to discard.
` - if (attrPromises.promises.length > 0) { - promises = [...promises, ...attrPromises.promises]; - } + let mainText = `${this.saveModalMessage}${confirmHeading}${subText}${this.confirmMessages}`; + this.loading.hideSpinner(); + this._modalConfirm({ + "titleText": "Complete", + mainText, + buttonSave + }); + } else { + let mainText = `${this.saveModalMessage}`; + this.loading.hideSpinner(); + this._modalComplete( + mainText + ); + // Reset forms to the saved data from model + this.resetHard(); } - } + }); - let messageObj = {}; - if (promises.length > 0 && errors === 0) { - // Check if anything changed - Promise.all(promises).then(async (respArray) => { - let responses = []; - respArray.forEach((item, i) => { - responses.push(item.json()) - }); + } - Promise.all(responses) - .then(dataArray => { - messageObj = this._handleResponseWithAttributes({ - id, - dataArray, - hasAttributeChanges, - attrPromises, - respArray - }); + _attrFormsChanged() { + let promise = Promise.resolve(); - let message = ""; - let success = false; - let error = false; - if (messageObj.messageSuccess) { - let heading = `
Success
`; - message += heading + messageObj.messageSuccess; - success = true; - } - if (messageObj.messageError) { - let heading = `
Error
`; - message += heading + messageObj.messageError; - error = true; - } + const attrFormsChanged = this.attributeSection.attrForms.filter(form => form._changed); + if (attrFormsChanged && attrFormsChanged.length > 0) { + for (let form of attrFormsChanged) { + let data = form._attributeFormData({ id: this.typeId, entityType: this.typeName }); + let attributeNewName = data.newName; + let attributeOldName = data.oldName; + let response = null; + console.log(data); - if (messageObj.requiresConfirmation) { - let buttonSave = this._getAttrGlobalTrigger(id); - let confirmHeading = `
Global Change(s) Found
` - let subText = `
Confirm to update across all types. Uncheck and confirm, or cancel to discard.
` - - let mainText = `${message}${confirmHeading}${subText}${messageObj.messageConfirm}`; - this.loading.hideSpinner(); - this._modalConfirm({ - "titleText": "Complete", - mainText, - buttonSave - }); - } else { - let mainText = `${message}`; - this.loading.hideSpinner(); - this._modalComplete( - mainText - ); - // Reset forms to the saved data from model - this.resetHard(); - } - }).then(() => { - // Reset changed flag - this.changed = false; - - if (hasAttributeChanges) { - const attrFormsChanged = this.attributeSection.attrForms.filter(form => form._changed); - if (attrFormsChanged.length > 0) { - for (let f of attrFormsChanged) { - f.changeReset(); + promise += form._fetchAttributePatchPromise(this.typeId, data.formData) + .then(resp => { + response = resp; + return resp.json() + }) + .then(data => { + let obj = { response: response, data: data }; + return obj; + }) + .then(obj => { + let currentMessage = obj.data.message; + let response = obj.response; + let succussIcon = document.createElement("modal-success"); + let iconWrap = document.createElement("span"); + let warningIcon = document.createElement("modal-warning"); + + console.log(`currentMessage::::: ${currentMessage}`); + + if (response.status == 200) { + //console.log("Return Message - It's a 200 response."); + iconWrap.appendChild(succussIcon); + this.successMessages += `
${iconWrap.innerHTML} ${currentMessage}
`; + } else if (response.status != 200) { + if (!this.hasAttributeChanges) { + iconWrap.appendChild(warningIcon); + //console.log("Return Message - It's a 400 response for main form."); + this.failedMessages += `
${iconWrap.innerHTML} ${currentMessage}
`; + } else if (this.hasAttributeChanges && currentMessage.indexOf("without the global flag set") > 0 && currentMessage.indexOf("ValidationError") < 0) { + //console.log("Return Message - It's a 400 response for attr form."); + let input = ``; + let newNameText = (attributeOldName == attributeNewName) ? "" : ` new name "${attributeNewName}"` + this.confirmMessages += `
${input} Attribute "${attributeOldName}" ${newNameText}
` + this.requiresConfirmation = true; + } else { + iconWrap.appendChild(warningIcon); + this.failedMessages += `
${iconWrap.innerHTML} Changes editing ${attributeOldName} not saved.
` + this.failedMessages += `
Error: ${currentMessage}
` } } - } - // Update related items with an event if required - if (this._nameEdit.edited) { - this._updateNavEvent("rename", this._nameEdit.newName) - } + }); + } + } + return promise.then(val => val); + } - }); + _typeFormChanged() { + const formData = this._getFormData(); + let promise = Promise.resolve(); + // console.log("Main form was changed"); - }).catch(err => { - console.error("File " + err.fileName + " Line " + err.lineNumber + "\n" + err); - this.loading.hideSpinner(); - }); - } else if (!promises.length > 0) { - this.loading.hideSpinner(); - console.error("Attempted to save but no promises found."); - return this._modalSuccess("Nothing new to save!"); - } else if (!errors === 0) { - this.loading.hideSpinner(); - return this._modalError("Please fix form errors."); + if (Object.entries(formData).length === 0) { + return console.error("No formData"); } else { - this.loading.hideSpinner(); - return this._modalError("Problem saving form data."); + return promise.then(() => { + // + if (typeof formData.name !== "undefined") { + this.nameChanged = true; + this.newName = formData.name; + } + return this._fetchPatchPromise({ id, formData }); + }) + .then(response => response.json().then(data => ({ response: response, data: data }))) + .then(obj => { + let currentMessage = obj.data.message; + let succussIcon = document.createElement("modal-success"); + let warningIcon = document.createElement("modal-warning"); + let iconWrap = document.createElement("span"); + if (obj.response.ok) { + iconWrap.appendChild(succussIcon); + this.successMessages += `${iconWrap.innerHTML} ${currentMessage}

`; + } else { + iconWrap.appendChild(warningIcon); + this.failedMessages += `${iconWrap.innerHTML} ${currentMessage}

`; + } + }); } } @@ -630,54 +661,6 @@ class TypeForm extends TatorElement { return this.changed = true; } - _handleResponseWithAttributes({ - id = -1, - dataArray = [], - hasAttributeChanges = false, - attrPromises = [], - respArray = [] } - = {}) { - - let messageSuccess = ""; - let messageError = ""; - let messageConfirm = ""; - let requiresConfirmation = false; - - respArray.forEach((item, i) => { - let currentMessage = dataArray[i].message; - let succussIcon = document.createElement("modal-success"); - let iconWrap = document.createElement("span"); - let warningIcon = document.createElement("modal-warning"); - let index = (hasAttributeChanges && respArray[0].url.indexOf("Attribute") > 0) ? i : i - 1; - let formReadable = hasAttributeChanges ? attrPromises.attrNames[index] : ""; - let formReadable2 = hasAttributeChanges ? attrPromises.attrNamesNew[index] : ""; - - if (item.status == 200) { - //console.log("Return Message - It's a 200 response."); - iconWrap.appendChild(succussIcon); - messageSuccess += `
${iconWrap.innerHTML} ${currentMessage}
`; - } else if (item.status != 200) { - if (!hasAttributeChanges) { - iconWrap.appendChild(warningIcon); - //console.log("Return Message - It's a 400 response for main form."); - messageError += `
${iconWrap.innerHTML} ${currentMessage}
`; - } else if (hasAttributeChanges && currentMessage.indexOf("without the global flag set") > 0 && currentMessage.indexOf("ValidationError") < 0) { - //console.log("Return Message - It's a 400 response for attr form."); - let input = ``; - let newName = formReadable == formReadable2 ? "" : ` new name "${formReadable2}"` - messageConfirm += `
${input} Attribute "${formReadable}" ${newName}
` - requiresConfirmation = true; - } else { - iconWrap.appendChild(warningIcon); - messageError += `
${iconWrap.innerHTML} Changes editing ${formReadable} not saved.
` - messageError += `
Error: ${currentMessage}
` - } - } - }); - - return { requiresConfirmation, messageSuccess, messageConfirm, messageError }; - } - _getAttrGlobalTrigger(id) { let buttonSave = document.createElement("button") buttonSave.setAttribute("class", "btn btn-clear f1 text-semibold"); @@ -709,7 +692,7 @@ class TypeForm extends TatorElement { } //run the _save method again with global true - this._save({ "id": id, "globalAttribute": true }) + this._save({ "id": id }) }); return buttonSave; @@ -912,7 +895,7 @@ class TypeForm extends TatorElement { id: detail.typeId, name: detail.newName }; - + this._mediaCheckboxes._newInput(item); } diff --git a/test/test_settings.py b/test/test_settings.py index 4286c97ec..5b1f660f6 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -33,263 +33,263 @@ def test_settings_projectEdit(authenticated, project, image_file): page.click('modal-dialog modal-close .modal__close') -# def test_settings_mediaTypes(authenticated, project): -# print("Test Media types: Opening settings page...") -# page = authenticated.new_page() -# page.goto(f"/{project}/project-settings") -# page.on("pageerror", print_page_error) +def test_settings_mediaTypes(authenticated, project): + print("Test Media types: Opening settings page...") + page = authenticated.new_page() + page.goto(f"/{project}/project-settings") + page.on("pageerror", print_page_error) -# print("Start: Creating Media Types via Project Settings") -# # Create Media types ##todo why are multiple of each being created? -# page.click('.heading-for-MediaType .Nav-action') -# page.fill('#itemDivId-MediaType-New text-input[name="Name"] input', 'My Video Type') -# page.select_option('#itemDivId-MediaType-New enum-input[name="Data Type"] select', label='Video') -# page.fill('#itemDivId-MediaType-New text-input[name="Description"] input', 'Media description for automated test.') -# page.fill('#itemDivId-MediaType-New text-input[name="Default volume"] input', '50') -# page.click('#itemDivId-MediaType-New bool-input[name="Visible"] label[for="on"]') -# page.click('#itemDivId-MediaType-New button[value="Save"]') -# page.wait_for_selector(f'text="Media type created successfully!"') -# print(f"Video Media type created successfully!") -# page.click('modal-dialog modal-close .modal__close') -# page.click('.heading-for-MediaType .Nav-action') -# page.fill('#itemDivId-MediaType-New text-input[name="Name"] input', 'My Image Type') -# page.select_option('#itemDivId-MediaType-New enum-input[name="Data Type"] select', label='Image') -# page.fill('#itemDivId-MediaType-New text-input[name="Description"] input', 'Media description for automated test.') -# page.fill('#itemDivId-MediaType-New text-input[name="Default volume"] input', '50') -# page.click('#itemDivId-MediaType-New bool-input[name="Visible"] label[for="on"]') -# page.click('#itemDivId-MediaType-New button[value="Save"]') -# page.wait_for_selector(f'text="Media type created successfully!"') -# print(f"Image Media type created successfully!") -# page.click('modal-dialog modal-close .modal__close') -# page.click('.heading-for-MediaType .Nav-action') -# page.fill('#itemDivId-MediaType-New text-input[name="Name"] input', 'My Multiview Type') -# page.select_option('#itemDivId-MediaType-New enum-input[name="Data Type"] select', label='Multiview') -# page.fill('#itemDivId-MediaType-New text-input[name="Description"] input', 'Media description for automated test.') -# page.fill('#itemDivId-MediaType-New text-input[name="Default volume"] input', '50') -# page.click('#itemDivId-MediaType-New bool-input[name="Visible"] label[for="on"]') -# page.click('#itemDivId-MediaType-New button[value="Save"]') -# page.wait_for_selector(f'text="Media type created successfully!"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"Multiview Media type created successfully!") + print("Start: Creating Media Types via Project Settings") + # Create Media types ##todo why are multiple of each being created? + page.click('.heading-for-MediaType .Nav-action') + page.fill('#itemDivId-MediaType-New text-input[name="Name"] input', 'My Video Type') + page.select_option('#itemDivId-MediaType-New enum-input[name="Data Type"] select', label='Video') + page.fill('#itemDivId-MediaType-New text-input[name="Description"] input', 'Media description for automated test.') + page.fill('#itemDivId-MediaType-New text-input[name="Default volume"] input', '50') + page.click('#itemDivId-MediaType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-MediaType-New button[value="Save"]') + page.wait_for_selector(f'text="Media type created successfully!"') + print(f"Video Media type created successfully!") + page.click('modal-dialog modal-close .modal__close') + page.click('.heading-for-MediaType .Nav-action') + page.fill('#itemDivId-MediaType-New text-input[name="Name"] input', 'My Image Type') + page.select_option('#itemDivId-MediaType-New enum-input[name="Data Type"] select', label='Image') + page.fill('#itemDivId-MediaType-New text-input[name="Description"] input', 'Media description for automated test.') + page.fill('#itemDivId-MediaType-New text-input[name="Default volume"] input', '50') + page.click('#itemDivId-MediaType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-MediaType-New button[value="Save"]') + page.wait_for_selector(f'text="Media type created successfully!"') + print(f"Image Media type created successfully!") + page.click('modal-dialog modal-close .modal__close') + page.click('.heading-for-MediaType .Nav-action') + page.fill('#itemDivId-MediaType-New text-input[name="Name"] input', 'My Multiview Type') + page.select_option('#itemDivId-MediaType-New enum-input[name="Data Type"] select', label='Multiview') + page.fill('#itemDivId-MediaType-New text-input[name="Description"] input', 'Media description for automated test.') + page.fill('#itemDivId-MediaType-New text-input[name="Default volume"] input', '50') + page.click('#itemDivId-MediaType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-MediaType-New button[value="Save"]') + page.wait_for_selector(f'text="Media type created successfully!"') + page.click('modal-dialog modal-close .modal__close') + print(f"Multiview Media type created successfully!") -# def test_settings_localizationTypes(authenticated, project): -# print("Test Localization types: Opening settings page...") -# page = authenticated.new_page() -# page.goto(f"/{project}/project-settings") -# page.on("pageerror", print_page_error) +def test_settings_localizationTypes(authenticated, project): + print("Test Localization types: Opening settings page...") + page = authenticated.new_page() + page.goto(f"/{project}/project-settings") + page.on("pageerror", print_page_error) -# # Create Localization types -# page.click('.heading-for-LocalizationType .Nav-action') -# page.fill('#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto Box Type') -# page.select_option('#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label='Box') -# page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') -# # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') -# # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') -# page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') -# page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') -# page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') -# page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') -# page.click('#itemDivId-LocalizationType-New span:text("Test Images")') -# page.click('#itemDivId-LocalizationType-New button[value="Save"]') -# page.wait_for_selector(f'text="Localization type created successfully!"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"Box - Localization type created successfully!!") + # Create Localization types + page.click('.heading-for-LocalizationType .Nav-action') + page.fill('#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto Box Type') + page.select_option('#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label='Box') + page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') + # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') + # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') + page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') + page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') + page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') + page.click('#itemDivId-LocalizationType-New span:text("Test Images")') + page.click('#itemDivId-LocalizationType-New button[value="Save"]') + page.wait_for_selector(f'text="Localization type created successfully!"') + page.click('modal-dialog modal-close .modal__close') + print(f"Box - Localization type created successfully!!") -# page.click('.heading-for-LocalizationType .Nav-action') -# page.fill('#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto Line Type') -# page.select_option('#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label='Line') -# page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') -# # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') -# # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') -# page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') -# page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') -# page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') -# page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') -# page.click('#itemDivId-LocalizationType-New span:text("Test Images")') -# page.click('#itemDivId-LocalizationType-New button[value="Save"]') -# page.wait_for_selector(f'text="Localization type created successfully!"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"Line - Localization type created successfully!!") + page.click('.heading-for-LocalizationType .Nav-action') + page.fill('#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto Line Type') + page.select_option('#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label='Line') + page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') + # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') + # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') + page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') + page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') + page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') + page.click('#itemDivId-LocalizationType-New span:text("Test Images")') + page.click('#itemDivId-LocalizationType-New button[value="Save"]') + page.wait_for_selector(f'text="Localization type created successfully!"') + page.click('modal-dialog modal-close .modal__close') + print(f"Line - Localization type created successfully!!") -# page.click('.heading-for-LocalizationType .Nav-action') -# page.fill('#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto Dot Type') -# page.select_option('#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label='Dot') -# page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') -# # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') -# # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') -# page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') -# page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') -# page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') -# page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') -# page.click('#itemDivId-LocalizationType-New span:text("Test Images")') -# page.click('#itemDivId-LocalizationType-New button[value="Save"]') -# page.wait_for_selector(f'text="Localization type created successfully!"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"Dot - Localization type created successfully!!") + page.click('.heading-for-LocalizationType .Nav-action') + page.fill('#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto Dot Type') + page.select_option('#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label='Dot') + page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') + # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') + # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') + page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') + page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') + page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') + page.click('#itemDivId-LocalizationType-New span:text("Test Images")') + page.click('#itemDivId-LocalizationType-New button[value="Save"]') + page.wait_for_selector(f'text="Localization type created successfully!"') + page.click('modal-dialog modal-close .modal__close') + print(f"Dot - Localization type created successfully!!") -# def test_settings_leafType(authenticated, project): -# print("Test Leaf types: Opening settings page...") -# page = authenticated.new_page() -# page.goto(f"/{project}/project-settings") -# page.on("pageerror", print_page_error) +def test_settings_leafType(authenticated, project): + print("Test Leaf types: Opening settings page...") + page = authenticated.new_page() + page.goto(f"/{project}/project-settings") + page.on("pageerror", print_page_error) -# # Create Leaf type -# page.click('.heading-for-LeafType .Nav-action') -# page.fill('#itemDivId-LeafType-New text-input[name="Name"] input', 'Testing Leaf') -# page.fill('#itemDivId-LeafType-New text-input[name="Description"] input', 'Leaf Type description for automated test.') -# page.click('#itemDivId-LeafType-New button[value="Save"]') -# page.wait_for_selector(f'text="Leaf type created successfully!"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"Leaf type created successfully!!") + # Create Leaf type + page.click('.heading-for-LeafType .Nav-action') + page.fill('#itemDivId-LeafType-New text-input[name="Name"] input', 'Testing Leaf') + page.fill('#itemDivId-LeafType-New text-input[name="Description"] input', 'Leaf Type description for automated test.') + page.click('#itemDivId-LeafType-New button[value="Save"]') + page.wait_for_selector(f'text="Leaf type created successfully!"') + page.click('modal-dialog modal-close .modal__close') + print(f"Leaf type created successfully!!") -# def test_settings_stateTypes(authenticated, project): -# print("Test State types: Opening settings page...") -# page = authenticated.new_page() -# page.goto(f"/{project}/project-settings") -# page.on("pageerror", print_page_error) +def test_settings_stateTypes(authenticated, project): + print("Test State types: Opening settings page...") + page = authenticated.new_page() + page.goto(f"/{project}/project-settings") + page.on("pageerror", print_page_error) -# # Create State types #todo hitting error with media values sent as "[None, None]" -# page.click('.heading-for-StateType .Nav-action') -# page.fill('#itemDivId-StateType-New text-input[name="Name"] input', 'Alabama') -# page.fill('#itemDivId-StateType-New text-input[name="Description"] input', 'State Type description for automated test.') -# page.click('#itemDivId-StateType-New bool-input[name="Visible"] label[for="on"]') -# page.click('#itemDivId-StateType-New bool-input[name="Grouping Default"] label[for="on"]') -# page.click('#itemDivId-StateType-New span:text("My Video Type")') -# page.click('#itemDivId-StateType-New span:text("My Image Type")') -# page.select_option('#itemDivId-StateType-New enum-input[name="Association"] select', label='Localization') -# page.select_option('#itemDivId-StateType-New enum-input[name="Interpolation"] select', label='Latest') -# page.click('#itemDivId-StateType-New bool-input[name="Delete Child Localizations"] label[for="on"]') -# page.click('#itemDivId-StateType-New button[value="Save"]') -# page.click('modal-dialog modal-close .modal__close') -# page.wait_for_selector(f'text="State type created successfully!"') -# print(f"State type created successfully - Association: Localization, Interpolation: Latest") + # Create State types #todo hitting error with media values sent as "[None, None]" + page.click('.heading-for-StateType .Nav-action') + page.fill('#itemDivId-StateType-New text-input[name="Name"] input', 'Alabama') + page.fill('#itemDivId-StateType-New text-input[name="Description"] input', 'State Type description for automated test.') + page.click('#itemDivId-StateType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-StateType-New bool-input[name="Grouping Default"] label[for="on"]') + page.click('#itemDivId-StateType-New span:text("My Video Type")') + page.click('#itemDivId-StateType-New span:text("My Image Type")') + page.select_option('#itemDivId-StateType-New enum-input[name="Association"] select', label='Localization') + page.select_option('#itemDivId-StateType-New enum-input[name="Interpolation"] select', label='Latest') + page.click('#itemDivId-StateType-New bool-input[name="Delete Child Localizations"] label[for="on"]') + page.click('#itemDivId-StateType-New button[value="Save"]') + page.click('modal-dialog modal-close .modal__close') + page.wait_for_selector(f'text="State type created successfully!"') + print(f"State type created successfully - Association: Localization, Interpolation: Latest") -# page.click('.heading-for-StateType .Nav-action') -# page.fill('#itemDivId-StateType-New text-input[name="Name"] input', 'Alabama') -# page.fill('#itemDivId-StateType-New text-input[name="Description"] input', 'State Type description for automated test.') -# page.click('#itemDivId-StateType-New bool-input[name="Visible"] label[for="on"]') -# page.click('#itemDivId-StateType-New bool-input[name="Grouping Default"] label[for="on"]') -# page.click('#itemDivId-StateType-New span:text("My Video Type")') -# page.click('#itemDivId-StateType-New span:text("My Image Type")') -# page.select_option('#itemDivId-StateType-New enum-input[name="Association"] select', label='Media') -# page.select_option('#itemDivId-StateType-New enum-input[name="Interpolation"] select', label='Latest') -# page.click('#itemDivId-StateType-New bool-input[name="Delete Child Localizations"] label[for="on"]') -# page.click('#itemDivId-StateType-New button[value="Save"]') -# page.wait_for_selector(f'text="State type created successfully!"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"State type created successfully - Association: Media, Interpolation: Latest") + page.click('.heading-for-StateType .Nav-action') + page.fill('#itemDivId-StateType-New text-input[name="Name"] input', 'Alabama') + page.fill('#itemDivId-StateType-New text-input[name="Description"] input', 'State Type description for automated test.') + page.click('#itemDivId-StateType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-StateType-New bool-input[name="Grouping Default"] label[for="on"]') + page.click('#itemDivId-StateType-New span:text("My Video Type")') + page.click('#itemDivId-StateType-New span:text("My Image Type")') + page.select_option('#itemDivId-StateType-New enum-input[name="Association"] select', label='Media') + page.select_option('#itemDivId-StateType-New enum-input[name="Interpolation"] select', label='Latest') + page.click('#itemDivId-StateType-New bool-input[name="Delete Child Localizations"] label[for="on"]') + page.click('#itemDivId-StateType-New button[value="Save"]') + page.wait_for_selector(f'text="State type created successfully!"') + page.click('modal-dialog modal-close .modal__close') + print(f"State type created successfully - Association: Media, Interpolation: Latest") -# page.click('.heading-for-StateType .Nav-action') -# page.fill('#itemDivId-StateType-New text-input[name="Name"] input', 'Alabama') -# page.fill('#itemDivId-StateType-New text-input[name="Description"] input', 'State Type description for automated test.') -# page.click('#itemDivId-StateType-New bool-input[name="Visible"] label[for="on"]') -# page.click('#itemDivId-StateType-New bool-input[name="Grouping Default"] label[for="on"]') -# page.click('#itemDivId-StateType-New span:text("My Video Type")') -# page.click('#itemDivId-StateType-New span:text("My Image Type")') -# page.select_option('#itemDivId-StateType-New enum-input[name="Association"] select', label='Frame') -# page.select_option('#itemDivId-StateType-New enum-input[name="Interpolation"] select', label='Latest') -# page.click('#itemDivId-StateType-New bool-input[name="Delete Child Localizations"] label[for="on"]') -# page.click('#itemDivId-StateType-New button[value="Save"]') -# page.click('modal-dialog modal-close .modal__close') -# page.wait_for_selector(f'text="State type created successfully!"') -# print(f"State type created successfully - Association: Frame, Interpolation: Latest") + page.click('.heading-for-StateType .Nav-action') + page.fill('#itemDivId-StateType-New text-input[name="Name"] input', 'Alabama') + page.fill('#itemDivId-StateType-New text-input[name="Description"] input', 'State Type description for automated test.') + page.click('#itemDivId-StateType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-StateType-New bool-input[name="Grouping Default"] label[for="on"]') + page.click('#itemDivId-StateType-New span:text("My Video Type")') + page.click('#itemDivId-StateType-New span:text("My Image Type")') + page.select_option('#itemDivId-StateType-New enum-input[name="Association"] select', label='Frame') + page.select_option('#itemDivId-StateType-New enum-input[name="Interpolation"] select', label='Latest') + page.click('#itemDivId-StateType-New bool-input[name="Delete Child Localizations"] label[for="on"]') + page.click('#itemDivId-StateType-New button[value="Save"]') + page.click('modal-dialog modal-close .modal__close') + page.wait_for_selector(f'text="State type created successfully!"') + print(f"State type created successfully - Association: Frame, Interpolation: Latest") -# def test_settings_projectMemberships(authenticated, project): -# print("Test memberships: Opening settings page...") -# page = authenticated.new_page() -# page.goto(f"/{project}/project-settings") -# page.on("pageerror", print_page_error) +def test_settings_projectMemberships(authenticated, project): + print("Test memberships: Opening settings page...") + page = authenticated.new_page() + page.goto(f"/{project}/project-settings") + page.on("pageerror", print_page_error) -# # Test memberships -# page.click('.heading-for-Membership .Nav-action') -# page.wait_for_selector('.subitems-Membership a:nth-child(2)') -# username = page.query_selector('.subitems-Membership a:first-child').textContent -# print(f"username: {username}") -# page.click('.heading-for-Membership .Nav-action') -# page.fill('#itemDivId-Membership-New user-input[name"Search users"]', username) -# page.select_option('#itemDivId-Membership-New enum-input[name="Default version"] select', label='Test Version') -# page.click('#itemDivId-Membership-New button[value="Save"]') -# page.wait_for_selector('text="Failed to create 1 memberships. Membership already exists for project."') -# page.click('modal-dialog modal-close .modal__close') -# print(f"Membership endpoint hit successfully!") + # Test memberships + page.click('.heading-for-Membership .Nav-action') + page.wait_for_selector('.subitems-Membership a:nth-child(2)') + username = page.query_selector('.subitems-Membership a:first-child').textContent + print(f"username: {username}") + page.click('.heading-for-Membership .Nav-action') + page.fill('#itemDivId-Membership-New user-input[name"Search users"]', username) + page.select_option('#itemDivId-Membership-New enum-input[name="Default version"] select', label='Test Version') + page.click('#itemDivId-Membership-New button[value="Save"]') + page.wait_for_selector('text="Failed to create 1 memberships. Membership already exists for project."') + page.click('modal-dialog modal-close .modal__close') + print(f"Membership endpoint hit successfully!") -# def test_settings_versionTests(authenticated, project): -# print("Test Version: Opening settings page...") -# page = authenticated.new_page() -# page.goto(f"/{project}/project-settings") -# page.on("pageerror", print_page_error) +def test_settings_versionTests(authenticated, project): + print("Test Version: Opening settings page...") + page = authenticated.new_page() + page.goto(f"/{project}/project-settings") + page.on("pageerror", print_page_error) -# # Test Version type -# page.click('.heading-for-heading-for-Version .Nav-action') -# page.fill('#itemDivId-Version-New text-input[name="Name"] input', 'New Version') -# page.fill('#itemDivId-Version-New text-input[name="Description"] input', 'Version description for automated test.') -# page.click('#itemDivId-Version-New bool-input[name="Show Empty"] label[for="on"]') -# page.click('#itemDivId-Version-New checkbox-set[name="Media"] label[text="Baseline"]') -# page.click('#itemDivId-Version-New button[value="Save"]') -# page.click('modal-dialog modal-close .modal__close') -# page.wait_for_selector(f'text="Version created successfully!"') -# print(f"Version created successfully!!") + # Test Version type + page.click('.heading-for-heading-for-Version .Nav-action') + page.fill('#itemDivId-Version-New text-input[name="Name"] input', 'New Version') + page.fill('#itemDivId-Version-New text-input[name="Description"] input', 'Version description for automated test.') + page.click('#itemDivId-Version-New bool-input[name="Show Empty"] label[for="on"]') + page.click('#itemDivId-Version-New checkbox-set[name="Media"] label[text="Baseline"]') + page.click('#itemDivId-Version-New button[value="Save"]') + page.click('modal-dialog modal-close .modal__close') + page.wait_for_selector(f'text="Version created successfully!"') + print(f"Version created successfully!!") -# def test_settings_algorithmTests(authenticated, project): -# print("Opening settings page...") -# page = authenticated.new_page() -# page.goto(f"/{project}/project-settings") -# page.on("pageerror", print_page_error) +def test_settings_algorithmTests(authenticated, project): + print("Opening settings page...") + page = authenticated.new_page() + page.goto(f"/{project}/project-settings") + page.on("pageerror", print_page_error) -# # Test Algorithm Type -# page.click('.heading-for-heading-for-Algorithm .Nav-action') -# page.fill('#itemDivId-Algorithm-New text-input[name="Name"] input', 'New Algorithm') -# page.fill('#itemDivId-Algorithm-New text-input[name="Description"] input', 'Algorithm description for automated test.') -# page.click('#itemDivId-Algorithm-New button[value="Save"]') -# page.wait_for_selector(f'text="/.*not valid for schema of type SchemaType.OBJECT: .*/"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"Algorithm endpoint hit successfully!!") + # Test Algorithm Type + page.click('.heading-for-heading-for-Algorithm .Nav-action') + page.fill('#itemDivId-Algorithm-New text-input[name="Name"] input', 'New Algorithm') + page.fill('#itemDivId-Algorithm-New text-input[name="Description"] input', 'Algorithm description for automated test.') + page.click('#itemDivId-Algorithm-New button[value="Save"]') + page.wait_for_selector(f'text="/.*not valid for schema of type SchemaType.OBJECT: .*/"') + page.click('modal-dialog modal-close .modal__close') + print(f"Algorithm endpoint hit successfully!!") -# def test_settings_attributeTests(authenticated, project): -# print("Opening settings page...") -# page = authenticated.new_page() -# page.goto(f"/{project}/project-settings") -# page.on("pageerror", print_page_error) +def test_settings_attributeTests(authenticated, project): + print("Opening settings page...") + page = authenticated.new_page() + page.goto(f"/{project}/project-settings") + page.on("pageerror", print_page_error) -# # Test Attribute Types -# page.click('.heading-for-MediaType') -# page.wait_for_selector('.SideNav-subItem a[text="My Image Type"]') -# page.click('.SideNav-subItem a[text="My Image Type"]') -# page.click('.item-box:not(.hidden) .add-new-in-form') -# page.wait_for_selector('modal-dialog h2[text="New Attribute"]') -# page.fill('modal-dialog h2[text="New Attribute"]') -# page.fill('modal-dialog text-input[name="Name"] input', 'String Type') -# page.select_option('modal-dialog enum-input[name="Data Type"] select', label='string') -# page.fill('modal-dialog text-input[name="Description"] input', 'Attr description for automated test.') -# page.click('modal-dialog input[type="submit"]') -# page.wait_for_selector(f'text="New attribute type \'String Type\' added"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"New string type attribute added to Image!") + # Test Attribute Types + page.click('.heading-for-MediaType') + page.wait_for_selector('.SideNav-subItem a[text="My Image Type"]') + page.click('.SideNav-subItem a[text="My Image Type"]') + page.click('.item-box:not(.hidden) .add-new-in-form') + page.wait_for_selector('modal-dialog h2[text="New Attribute"]') + page.fill('modal-dialog h2[text="New Attribute"]') + page.fill('modal-dialog text-input[name="Name"] input', 'String Type') + page.select_option('modal-dialog enum-input[name="Data Type"] select', label='string') + page.fill('modal-dialog text-input[name="Description"] input', 'Attr description for automated test.') + page.click('modal-dialog input[type="submit"]') + page.wait_for_selector(f'text="New attribute type \'String Type\' added"') + page.click('modal-dialog modal-close .modal__close') + print(f"New string type attribute added to Image!") -# page.click('.heading-for-MediaType') -# page.click('.item-box:not(.hidden) .add-new-in-form') -# page.wait_for_selector('modal-dialog h2[text="New Attribute"]') -# page.fill('modal-dialog h2[text="New Attribute"]') -# page.fill('modal-dialog text-input[name="Name"] input', 'Int Type') -# page.select_option('modal-dialog enum-input[name="Data Type"] select', label='int') -# page.fill('modal-dialog text-input[name="Description"] input', 'Attr description for automated test.') -# page.click('modal-dialog input[type="submit"]') -# page.wait_for_selector(f'text="New attribute type \'Int Type\' added"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"New int type attribute added to Image!") + page.click('.heading-for-MediaType') + page.click('.item-box:not(.hidden) .add-new-in-form') + page.wait_for_selector('modal-dialog h2[text="New Attribute"]') + page.fill('modal-dialog h2[text="New Attribute"]') + page.fill('modal-dialog text-input[name="Name"] input', 'Int Type') + page.select_option('modal-dialog enum-input[name="Data Type"] select', label='int') + page.fill('modal-dialog text-input[name="Description"] input', 'Attr description for automated test.') + page.click('modal-dialog input[type="submit"]') + page.wait_for_selector(f'text="New attribute type \'Int Type\' added"') + page.click('modal-dialog modal-close .modal__close') + print(f"New int type attribute added to Image!") -# page.click('.heading-for-MediaType') -# page.click('.item-box:not(.hidden) .add-new-in-form') -# page.wait_for_selector('modal-dialog h2[text="New Attribute"]') -# page.fill('modal-dialog h2[text="New Attribute"]') -# page.fill('modal-dialog text-input[name="Name"] input', 'Bool Type') -# page.select_option('modal-dialog enum-input[name="Data Type"] select', label='bool') -# page.fill('modal-dialog text-input[name="Description"] input', 'Attr description for automated test.') -# page.click('modal-dialog input[type="submit"]') -# page.wait_for_selector(f'text="New attribute type \'Bool Type\' added"') -# page.click('modal-dialog modal-close .modal__close') -# print(f"New bool type attribute added to Image!") + page.click('.heading-for-MediaType') + page.click('.item-box:not(.hidden) .add-new-in-form') + page.wait_for_selector('modal-dialog h2[text="New Attribute"]') + page.fill('modal-dialog h2[text="New Attribute"]') + page.fill('modal-dialog text-input[name="Name"] input', 'Bool Type') + page.select_option('modal-dialog enum-input[name="Data Type"] select', label='bool') + page.fill('modal-dialog text-input[name="Description"] input', 'Attr description for automated test.') + page.click('modal-dialog input[type="submit"]') + page.wait_for_selector(f'text="New attribute type \'Bool Type\' added"') + page.click('modal-dialog modal-close .modal__close') + print(f"New bool type attribute added to Image!") def test_settings_projectDelete(authenticated, project): From 529b087529e688bb5c528498a6f7df9cd0050acb Mon Sep 17 00:00:00 2001 From: ermbutler Date: Fri, 24 Sep 2021 19:36:37 +0000 Subject: [PATCH 02/24] Attribute form cleanup and commenting; Modal scroll for complete message --- .../attributes/attributes-form.js | 112 ++++++++++++------ .../project-settings/settings-box-helpers.js | 1 + 2 files changed, 78 insertions(+), 35 deletions(-) diff --git a/main/static/js/project-settings/attributes/attributes-form.js b/main/static/js/project-settings/attributes/attributes-form.js index 0de0d998d..949206f53 100644 --- a/main/static/js/project-settings/attributes/attributes-form.js +++ b/main/static/js/project-settings/attributes/attributes-form.js @@ -354,9 +354,15 @@ class AttributesForm extends TatorElement { this._default.setAttribute("on-text", "Yes"); this._default.setAttribute("off-text", "No"); + this._default.default = value; + this._default.setValue(value); + + this._default.addEventListener("change", this._formChanged.bind(this)); } else if (dtype == "enum") { // enum default set in place, hide default input this.placeholderDefault.classList.add("hidden"); + this._default = document.createElement("text-input"); + this.placeholderDefault.appendChild(this._default); return; } else { this._default = document.createElement("text-input"); @@ -364,13 +370,14 @@ class AttributesForm extends TatorElement { this._default.setAttribute("name", "Default"); // attribute endpoint converts to correct type - this._default.setAttribute("type", "text") + this._default.setAttribute("type", "text"); + this._default.default = value; + this._default.setValue(value); + + this._default.addEventListener("change", this._formChanged.bind(this)); } - this._default.default = value; - this._default.setValue(value); - this._default.addEventListener("change", this._formChanged.bind(this)); return this._default; } @@ -700,59 +707,77 @@ class AttributesForm extends TatorElement { }); } + /** + * This will check for changed inputs, but some are required for patch call + * POST - Only required and if changed; UNLESS it is a clone, then pass all values + * PATCH - Only required and if changed + * + * Required: (See: _check_attribute_type in attribute_type.py) + * - Name + * - Dtype + * - Choices (if Dtype == Enum) + * - Default (if Dtype Changes and it isn't null) + * + * + * @returns {formData} as JSON object + */ _getAttributeFormData() { const formData = {}; - - // always send name (re: _check_attribute_type) + + // Name: Always sent formData.name = this._name.getValue(); - - // - if ((this._description.changed() || this.isClone) && this._order.getValue() !== null) { + + // Description: Only when changed, or when it is a Clone pass the value along + // Don't send if the value is null => invalid + if ((this._description.changed() || this.isClone) && this._order.getValue() !== null) { formData.description = this._description.getValue(); } - - // + + // Order: Only when changed, or when it is a Clone pass the value along + // Don't send if the value is null => invalid if ((this._order.changed() || this.isClone) && this._order.getValue() !== null) { formData.order = this._order.getValue(); } - - // + + // Required: Only when changed, or when it is a Clone pass the value along if ((this._required.changed() || this.isClone)) { formData.required = this._required.getValue(); } - - // + + // Visible: Only when changed, or when it is a Clone pass the value along if ((this._visible.changed() || this.isClone)) { formData.visible = this._visible.getValue(); } - - // always send (re: _check_attribute_type) + + // Dtype: Always sent formData.dtype = this._dtype.getValue(); const dtype = this._dtype.getValue(); - - if(dtype === "enum"){ - if ((this._enumDefault.changed || (this.isClone && this._enumDefault.value !== "")) && this._enumDefault.value !== null) { - formData["default"] = this._enumDefault.value; - } - } else { - if ((this._default.changed() || (this.isClone && this._default.getValue() !== "")) && this._default.getValue() !== null) { + + // Default: Send if changed, or if dtype changed (so it can be set to correct type) or if this is a clone + // Don't send if the value is null => invalid + if (this.isClone || this._dtype.changed()) { + if (dtype === "enum" && this._enumDefault.changed && this._enumDefault.value !== null) { + formData["default"] = this._enumDefault.value; + } else if (this._default.changed() && this._default.getValue() !== null) { + let defaultVal = this._default.getValue(); //backend does this but not when value is "" if (dtype == "int" || dtype == "float") defaultVal = Number(defaultVal); formData["default"] = defaultVal; } } - + + // Datetime: Only when changed, or when it is a Clone pass the value along if (dtype === "datetime") { - if (this._useCurrent.changed()) { + if (this._useCurrent.changed() || this.isClone) { formData.use_current = this._useCurrent.getValue(); } } - - - // + + // Min and Max: Only dtype in numeric & (changed, or when it is a Clone pass the value along) + // Don't send if the value is null => invalid if (dtype === "int" || dtype === "float") { - // getValue for text-input int comes back as null when default is undefined bc it is NaN + // getValue for text-input int comes back as null when default is undefined bc it is NaN if ((this._minimum.changed() || this.isClone) && this._minimum.getValue() !== null) { formData.minimum = Number(this._minimum.getValue()); } @@ -760,16 +785,16 @@ class AttributesForm extends TatorElement { formData.maximum = Number(this._maximum.getValue()); } } - + + // Choices: Always send when dtype enum if (dtype === "enum") { - // always send (re: _check_attribute_type) formData.choices = this._choices.getValue(); - + if ((this._labels.changed() || this.isClone)) { formData.labels = this._labels.getValue(); } } - + console.log(formData); return formData; } @@ -827,6 +852,23 @@ class AttributesForm extends TatorElement { }); } + _attributeFormData({ form = this.form, id = -1, entityType = null } = {}) { + const data = {}; + const global = this.isGlobal() ? "true" : "false"; + const formData = { + "entity_type": entityType, + "global": global, + "old_attribute_type_name": this.dataset.oldName, + "new_attribute_type": this._getAttributeFormData(form) + }; + + data.newName = this._name.getValue(); + data.oldName = this.dataset.oldName; + data.formData = formData; + + return data; + } + async _getPromise({ form = this.form, id = -1, entityType = null } = {}) { const promiseInfo = {}; const global = this.isGlobal() ? "true" : "false"; @@ -845,7 +887,7 @@ class AttributesForm extends TatorElement { // Hand of the data, and call this form unchanged formData.new_attribute_type = this._getAttributeFormData(form); - form.classList.remove("changed"); + this.form.classList.remove("changed"); this.changeReset(); promiseInfo.promise = await this._fetchAttributePatchPromise(id, formData); diff --git a/main/static/js/project-settings/settings-box-helpers.js b/main/static/js/project-settings/settings-box-helpers.js index 7bd965208..5626b40ee 100644 --- a/main/static/js/project-settings/settings-box-helpers.js +++ b/main/static/js/project-settings/settings-box-helpers.js @@ -181,6 +181,7 @@ class SettingsBox { this.modal._titleDiv.innerHTML = ""; this.modal._main.innerHTML = ""; this.modal._footer.innerHTML = ""; + this.modal._main.classList.remove("fixed-height-scroll"); return this.modal; } From 8bc659d41d73834e9b76eee58083de2189c32582 Mon Sep 17 00:00:00 2001 From: ermbutler Date: Fri, 24 Sep 2021 20:43:12 +0000 Subject: [PATCH 03/24] Sync calls to attribute patch to fix lock error --- .../attributes/attributes-form.js | 10 +- .../project-settings/data/data-type-form.js | 75 +++++++++++ .../project-settings/type-forms/type-form.js | 126 ++++++++++-------- test/test_settings.py | 2 +- 4 files changed, 152 insertions(+), 61 deletions(-) create mode 100644 main/static/js/project-settings/data/data-type-form.js diff --git a/main/static/js/project-settings/attributes/attributes-form.js b/main/static/js/project-settings/attributes/attributes-form.js index 949206f53..ca83a1530 100644 --- a/main/static/js/project-settings/attributes/attributes-form.js +++ b/main/static/js/project-settings/attributes/attributes-form.js @@ -716,7 +716,7 @@ class AttributesForm extends TatorElement { * - Name * - Dtype * - Choices (if Dtype == Enum) - * - Default (if Dtype Changes and it isn't null) + * - Default (if Dtype Changes and it isn't null will be validated - leave null if it is "") * * * @returns {formData} as JSON object @@ -755,11 +755,11 @@ class AttributesForm extends TatorElement { // Default: Send if changed, or if dtype changed (so it can be set to correct type) or if this is a clone // Don't send if the value is null => invalid + // Don't send "" because it will fail as valid type for the default in some cases if (this.isClone || this._dtype.changed()) { - if (dtype === "enum" && this._enumDefault.changed && this._enumDefault.value !== null) { + if (dtype === "enum" && this._enumDefault !== {} && this._enumDefault.changed && this._enumDefault.value !== null && this._enumDefault.value !== "") { formData["default"] = this._enumDefault.value; - } else if (this._default.changed() && this._default.getValue() !== null) { - + } else if (dtype !== "enum" && this._default.changed() && this._default.getValue() !== null && this._default.getValue() !== "") { let defaultVal = this._default.getValue(); //backend does this but not when value is "" if (dtype == "int" || dtype == "float") defaultVal = Number(defaultVal); @@ -896,7 +896,7 @@ class AttributesForm extends TatorElement { } _fetchAttributePatchPromise(parentTypeId, formData) { - return fetchRetry("/rest/AttributeType/" + parentTypeId, { + return fetch("/rest/AttributeType/" + parentTypeId, { method: "PATCH", mode: "cors", credentials: "include", diff --git a/main/static/js/project-settings/data/data-type-form.js b/main/static/js/project-settings/data/data-type-form.js new file mode 100644 index 000000000..eeac84a59 --- /dev/null +++ b/main/static/js/project-settings/data/data-type-form.js @@ -0,0 +1,75 @@ +/* Class with methods return input types with preset values for editing.*/ +class TypeFormData { + constructor({projectId, typeName, typeId, selectedData}) { + // Feature-related class(es) to customize form element. Applies to all elements. + this.projectId = projectId; + this.typeName = typeName; + this.typeId = typeId; + this.selectedData = selectedData; + + this.responseMessage = ""; + } + + _fetchPatchPromise({formData = null } = {}){ + console.log("Attribute Clone Post Fetch"); + + if(formData != null){ + return fetch("/rest/AttributeType/"+this.typeId, { + method: "POST", + mode: "cors", + credentials: "include", + headers: { + "X-CSRFToken": getCookie("csrftoken"), + "Accept": "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify(formData) + }); + } else { + console.log("Problem with new attribute form data."); + } + } + + createClones() { + //create form data & post promise array for the attribute forms, and submit + this.successMessages = ""; + this.failedMessages = ""; + let promise = Promise.resolve(); + + for(let data of this.selectedData){ + let cloneValue = JSON.parse(data); //parse data attribute + + // console.log("cloneValue"); + // console.log(cloneValue); + + this.attributeForm = new AttributesForm(); + this.attributeForm._getFormWithValues({clone : true, ...cloneValue}); + let formJSON = { + "entity_type": this.typeName, + "addition": this.attributeForm._getAttributeFormData() + }; + + promise = promise.then(() => {return this._fetchPostPromise({ + "formData" : formJSON + });}) + .then(response => response.json().then(data => ({response: response, data: data}))) + .then(obj => { + let currentMessage = obj.data.message; + let succussIcon = document.createElement("modal-success"); + let warningIcon = document.createElement("modal-warning"); + let iconWrap = document.createElement("span"); + if(obj.response.ok){ + iconWrap.appendChild(succussIcon); + this.successMessages += `${iconWrap.innerHTML} ${currentMessage}

`; + } else { + iconWrap.appendChild(warningIcon); + this.failedMessages += `${iconWrap.innerHTML} ${currentMessage}

`; + } + }); + } + promise = promise.then(() => {return this.successMessages + this.failedMessages;}); + return promise; + } + +} + diff --git a/main/static/js/project-settings/type-forms/type-form.js b/main/static/js/project-settings/type-forms/type-form.js index a5babbbb3..0bfd22033 100644 --- a/main/static/js/project-settings/type-forms/type-form.js +++ b/main/static/js/project-settings/type-forms/type-form.js @@ -479,11 +479,28 @@ class TypeForm extends TatorElement { // Main type form await this._typeFormChanged(); } + } catch (err) { + console.error("Error saving.", err); + this.loading.hideSpinner(); + return this._modalError("Error saving type form changes.\nError: " + err); + } + try { if (this.hasAttributeChanges) { // All attribute forms - await this._attrFormsChanged(); + const attrFormsChanged = this.attributeSection.attrForms.filter(form => form._changed); + if (attrFormsChanged && attrFormsChanged.length > 0) { + for (let form of attrFormsChanged) { + await this._attrFormsChanged(form); + } + } } + } catch (err) { + console.error("Error saving.", err); + this.loading.hideSpinner(); + return this._modalError("Error saving attribute changes.\nError: " + err); + } + try { // Compiled messages from above await this._showSaveCompletModal(); @@ -506,7 +523,7 @@ class TypeForm extends TatorElement { } catch (err) { console.error("Error saving.", err); this.loading.hideSpinner(); - return this._modalError("Error saving changes."); + return this._modalError("Error saving.\nError: " + err); } } else { this.loading.hideSpinner(); @@ -552,62 +569,61 @@ class TypeForm extends TatorElement { } - _attrFormsChanged() { + _attrFormsChanged(form) { let promise = Promise.resolve(); + let attributeNewName = ""; + let attributeOldName = ""; + let response = null; + let data = null; - const attrFormsChanged = this.attributeSection.attrForms.filter(form => form._changed); - if (attrFormsChanged && attrFormsChanged.length > 0) { - for (let form of attrFormsChanged) { - let data = form._attributeFormData({ id: this.typeId, entityType: this.typeName }); - let attributeNewName = data.newName; - let attributeOldName = data.oldName; - let response = null; - console.log(data); - - promise += form._fetchAttributePatchPromise(this.typeId, data.formData) - .then(resp => { - response = resp; - return resp.json() - }) - .then(data => { - let obj = { response: response, data: data }; - return obj; - }) - .then(obj => { - let currentMessage = obj.data.message; - let response = obj.response; - let succussIcon = document.createElement("modal-success"); - let iconWrap = document.createElement("span"); - let warningIcon = document.createElement("modal-warning"); - - console.log(`currentMessage::::: ${currentMessage}`); - - if (response.status == 200) { - //console.log("Return Message - It's a 200 response."); - iconWrap.appendChild(succussIcon); - this.successMessages += `
${iconWrap.innerHTML} ${currentMessage}
`; - } else if (response.status != 200) { - if (!this.hasAttributeChanges) { - iconWrap.appendChild(warningIcon); - //console.log("Return Message - It's a 400 response for main form."); - this.failedMessages += `
${iconWrap.innerHTML} ${currentMessage}
`; - } else if (this.hasAttributeChanges && currentMessage.indexOf("without the global flag set") > 0 && currentMessage.indexOf("ValidationError") < 0) { - //console.log("Return Message - It's a 400 response for attr form."); - let input = ``; - let newNameText = (attributeOldName == attributeNewName) ? "" : ` new name "${attributeNewName}"` - this.confirmMessages += `
${input} Attribute "${attributeOldName}" ${newNameText}
` - this.requiresConfirmation = true; - } else { - iconWrap.appendChild(warningIcon); - this.failedMessages += `
${iconWrap.innerHTML} Changes editing ${attributeOldName} not saved.
` - this.failedMessages += `
Error: ${currentMessage}
` - } - } - - }); + return promise.then(() => { + data = form._attributeFormData({ id: this.typeId, entityType: this.typeName }); + attributeNewName = data.newName; + attributeOldName = data.oldName; + response = null; + console.log(data); + return data; + }).then(async (data) => { + return await form._fetchAttributePatchPromise(this.typeId, data.formData) + }) + .then(resp => { + response = resp; + return resp.json() + }) + .then(data => { + let obj = { response: response, data: data }; + return obj; + }) + .then(obj => { + let currentMessage = obj.data.message; + let response = obj.response; + let succussIcon = document.createElement("modal-success"); + let iconWrap = document.createElement("span"); + let warningIcon = document.createElement("modal-warning"); + + console.log(`currentMessage::::: ${currentMessage}`); + + if (response.status == 200) { + //console.log("Return Message - It's a 200 response."); + iconWrap.appendChild(succussIcon); + this.successMessages += `
${iconWrap.innerHTML} ${currentMessage}
`; + } else if (response.status != 200) { + if (!this.hasAttributeChanges) { + iconWrap.appendChild(warningIcon); + //console.log("Return Message - It's a 400 response for main form."); + this.failedMessages += `
${iconWrap.innerHTML} ${currentMessage}
`; + } else if (this.hasAttributeChanges && currentMessage.indexOf("without the global flag set") > 0 && currentMessage.indexOf("ValidationError") < 0) { + //console.log("Return Message - It's a 400 response for attr form."); + let input = ``; + let newNameText = (attributeOldName == attributeNewName) ? "" : ` new name "${attributeNewName}"` + this.confirmMessages += `
${input} Attribute "${attributeOldName}" ${newNameText}
` + this.requiresConfirmation = true; + } else { + iconWrap.appendChild(warningIcon); + this.failedMessages += `
${iconWrap.innerHTML} Changes editing ${attributeOldName} not saved.
Error: ${currentMessage}
` + } } - } - return promise.then(val => val); + }); } _typeFormChanged() { diff --git a/test/test_settings.py b/test/test_settings.py index 7be7f37e6..c795da4e3 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -357,7 +357,7 @@ def test_settings_attributeTests(authenticated, project): successMessages = page.query_selector_all('modal-dialog modal-success') print(f'Changes saved successfully for: {len(successMessages)} attributes') - assert len(successMessages) > 0 # == 7 + assert len(successMessages) == 7 page.click('modal-dialog modal-close .modal__close') From aced9e223c0271689f5ccb2c9f2ce6d77a1bad2e Mon Sep 17 00:00:00 2001 From: Brian Tate Date: Thu, 30 Sep 2021 15:27:21 -0400 Subject: [PATCH 04/24] If a video stalls out. Pause playback. --- main/static/js/annotation/annotation-multi.js | 5 +++++ main/static/js/annotation/annotation-player.js | 5 +++++ main/static/js/annotator/video.js | 1 + 3 files changed, 11 insertions(+) diff --git a/main/static/js/annotation/annotation-multi.js b/main/static/js/annotation/annotation-multi.js index 03892381f..4139d8059 100644 --- a/main/static/js/annotation/annotation-multi.js +++ b/main/static/js/annotation/annotation-multi.js @@ -775,6 +775,11 @@ class AnnotationMulti extends TatorElement { (evt) => { handle_ondemand_load(idx,evt); }); + // When a playback is stalled, pause all the videos. + this._videos[idx].addEventListener("playbackStalled", + (evt) => { + this.pause(); + }); this._videos[idx].addEventListener("frameChange", (evt) => { global_frame_change(idx,evt); diff --git a/main/static/js/annotation/annotation-player.js b/main/static/js/annotation/annotation-player.js index 1ad12fb4c..405ba842a 100644 --- a/main/static/js/annotation/annotation-player.js +++ b/main/static/js/annotation/annotation-player.js @@ -181,6 +181,11 @@ class AnnotationPlayer extends TatorElement { this.checkReady(); }); + // When a playback is stalled, pause the video + this._video.addEventListener("playbackStalled", evt => { + this.pause(); + }); + this._video.addEventListener("rateChange", evt => { this.checkReady(); }); diff --git a/main/static/js/annotator/video.js b/main/static/js/annotator/video.js index 31a02b61e..3abb5acc9 100644 --- a/main/static/js/annotator/video.js +++ b/main/static/js/annotator/video.js @@ -2789,6 +2789,7 @@ class VideoCanvas extends AnnotationCanvas { // Video isn't ready yet, wait and try again console.log(`video buffer not ready for loading - (ID:${this._videoObject.id}) frame: ` + frameIdx); this._loaderTimeout = setTimeout(loader, 250); + this.dispatchEvent(new CustomEvent("playbackStalled", {composed: true})); } else { From 845d1197094418a869b8acee0fa8823928ed2959 Mon Sep 17 00:00:00 2001 From: Brian Tate Date: Thu, 30 Sep 2021 15:38:37 -0400 Subject: [PATCH 05/24] On video stalls pause all videos. --- main/static/js/annotation/annotation-multi.js | 1 + main/static/js/annotation/annotation-player.js | 1 + main/static/js/annotator/video.js | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/main/static/js/annotation/annotation-multi.js b/main/static/js/annotation/annotation-multi.js index 4139d8059..09e5ba962 100644 --- a/main/static/js/annotation/annotation-multi.js +++ b/main/static/js/annotation/annotation-multi.js @@ -778,6 +778,7 @@ class AnnotationMulti extends TatorElement { // When a playback is stalled, pause all the videos. this._videos[idx].addEventListener("playbackStalled", (evt) => { + Utilities.warningAlert("Video playback stalled."); this.pause(); }); this._videos[idx].addEventListener("frameChange", diff --git a/main/static/js/annotation/annotation-player.js b/main/static/js/annotation/annotation-player.js index 405ba842a..99cf02fe3 100644 --- a/main/static/js/annotation/annotation-player.js +++ b/main/static/js/annotation/annotation-player.js @@ -183,6 +183,7 @@ class AnnotationPlayer extends TatorElement { // When a playback is stalled, pause the video this._video.addEventListener("playbackStalled", evt => { + Utilities.warningAlert("Video playback stalled."); this.pause(); }); diff --git a/main/static/js/annotator/video.js b/main/static/js/annotator/video.js index 3abb5acc9..e5f63e5b9 100644 --- a/main/static/js/annotator/video.js +++ b/main/static/js/annotator/video.js @@ -2784,7 +2784,7 @@ class VideoCanvas extends AnnotationCanvas { // and then schedules the next frame to be loaded var pushAndGoToNextFrame=function(frameIdx, source, width, height) { - if (source == null) + if (source == null) // To test stalls: || Math.round(Math.random() * 50) == 5) { // Video isn't ready yet, wait and try again console.log(`video buffer not ready for loading - (ID:${this._videoObject.id}) frame: ` + frameIdx); From 22c0da01025de9351d4706bd2522c06308813d59 Mon Sep 17 00:00:00 2001 From: Brian Tate Date: Thu, 30 Sep 2021 15:53:19 -0400 Subject: [PATCH 06/24] Use best available buffer logic in both directions. If playing, and buffer is unavailable resort to scrub attempt. --- main/static/js/annotator/video.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/main/static/js/annotator/video.js b/main/static/js/annotator/video.js index e5f63e5b9..21c221ecc 100644 --- a/main/static/js/annotator/video.js +++ b/main/static/js/annotator/video.js @@ -2178,6 +2178,8 @@ class VideoCanvas extends AnnotationCanvas { /** * Get the video element associated with the given buffer type and frame number * + * A feature both scrub and play will return the best available buffer for the frame in question. + * * @param {integer} frame - Target frame number * @param {string} bufferType - "scrub" | "play" | "seek" * @returns {video HTMLelement} @@ -2206,18 +2208,27 @@ class VideoCanvas extends AnnotationCanvas { { return this._videoElement[this._seek_idx].returnSeekIfPresent(time, direction); } - else if (bufferType == "play") - { - return this._videoElement[this._play_idx].forTime(time, bufferType, direction, this._numSeconds); - } else { + // Treat play and scrub buffer as best available. let play_attempt = this._videoElement[this._play_idx].forTime(time, "play", direction, this._numSeconds); + + // To test degraded mode (every 10th frame is degraded): + //if (frame % 10 == 0) + //{ + // play_attempt = null; + //} + + // Log every 5 frames if we go to degraded mode. + if (play_attempt == null && bufferType == "play" && frame % 5 == 0) + { + console.warn("Video degraded, attempting scrub buffer."); + } if (play_attempt) { return play_attempt; } - return this._videoElement[this._scrub_idx].forTime(time, bufferType, direction, this._numSeconds); + return this._videoElement[this._scrub_idx].forTime(time, "scrub", direction, this._numSeconds); } } From 18de65c98cb09e1b18a80df5f408acbd04cff0d9 Mon Sep 17 00:00:00 2001 From: Brian Tate Date: Thu, 30 Sep 2021 16:20:45 -0400 Subject: [PATCH 07/24] Fix broken prefetch logic. --- main/static/js/annotator/video.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main/static/js/annotator/video.js b/main/static/js/annotator/video.js index 21c221ecc..efb1d5af7 100644 --- a/main/static/js/annotator/video.js +++ b/main/static/js/annotator/video.js @@ -2182,9 +2182,10 @@ class VideoCanvas extends AnnotationCanvas { * * @param {integer} frame - Target frame number * @param {string} bufferType - "scrub" | "play" | "seek" + * @param {bool} force - true to force "play" over seek fallback. * @returns {video HTMLelement} */ - videoBuffer(frame, bufferType) + videoBuffer(frame, bufferType, force) { if (frame == undefined) { @@ -2224,7 +2225,7 @@ class VideoCanvas extends AnnotationCanvas { { console.warn("Video degraded, attempting scrub buffer."); } - if (play_attempt) + if (play_attempt || force) { return play_attempt; } @@ -2848,7 +2849,7 @@ class VideoCanvas extends AnnotationCanvas { // Skip prefetch if the current frame is already in the buffer // If we're using onDemand, check that buffer. If we're using scrub, check that buffer too. // Always prefetch if we pause after playing backwards. - if (this._direction != Direction.BACKWARDS && this.videoBuffer(this.currentFrame(), "play") != null && reqFrame == this._dispFrame) { + if (this._direction != Direction.BACKWARDS && this.videoBuffer(this.currentFrame(), "play", true) != null && reqFrame == this._dispFrame) { return; } else if (this.videoBuffer(this.currentFrame(), "scrub") && this._play_idx == this._scrub_idx) { From f94bbe38f3d997744c5b465e4ff9be614fe856f6 Mon Sep 17 00:00:00 2001 From: Brian Tate Date: Thu, 30 Sep 2021 16:29:15 -0400 Subject: [PATCH 08/24] Clear stale prefetch checks (single view) --- main/static/js/annotation/annotation-player.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main/static/js/annotation/annotation-player.js b/main/static/js/annotation/annotation-player.js index 99cf02fe3..a9003f35b 100644 --- a/main/static/js/annotation/annotation-player.js +++ b/main/static/js/annotation/annotation-player.js @@ -178,6 +178,8 @@ class AnnotationPlayer extends TatorElement { // When a seek is complete check to make sure the display all set this._video.addEventListener("seekComplete", evt => { + clearTimeout(this._handleNotReadyTimeout) + this._handleNotReadyTimeout = null; this.checkReady(); }); From ec9dfc47fc66cc7ba5cfb7664d3b91efdd4dfb2e Mon Sep 17 00:00:00 2001 From: Brian Tate Date: Fri, 1 Oct 2021 06:33:31 -0400 Subject: [PATCH 09/24] Refactor "am I ready?" so it can be used externally. --- main/static/js/annotator/video.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/main/static/js/annotator/video.js b/main/static/js/annotator/video.js index efb1d5af7..0f11fc30a 100644 --- a/main/static/js/annotator/video.js +++ b/main/static/js/annotator/video.js @@ -2833,6 +2833,16 @@ class VideoCanvas extends AnnotationCanvas { } } + onDemandBufferAvailable(frame) + { + return this.videoBuffer(frame, "play", true) != null; + } + + scrubBufferAvailable(frame) + { + return this.videoBuffer(frame, "scrub") != null; + } + onDemandDownloadPrefetch(reqFrame) { // This function can be called at anytime. If auto-download is disabled, then just stop @@ -2849,7 +2859,7 @@ class VideoCanvas extends AnnotationCanvas { // Skip prefetch if the current frame is already in the buffer // If we're using onDemand, check that buffer. If we're using scrub, check that buffer too. // Always prefetch if we pause after playing backwards. - if (this._direction != Direction.BACKWARDS && this.videoBuffer(this.currentFrame(), "play", true) != null && reqFrame == this._dispFrame) { + if (this._direction != Direction.BACKWARDS && this.onDemandBufferAvailable(reqFrame) && reqFrame == this._dispFrame) { return; } else if (this.videoBuffer(this.currentFrame(), "scrub") && this._play_idx == this._scrub_idx) { From 16f2508a9e946c9fbf56e7604e34e27ec470fc85 Mon Sep 17 00:00:00 2001 From: Mark Taipan Date: Fri, 1 Oct 2021 15:38:51 +0000 Subject: [PATCH 10/24] Fixed notReady call bug in multiview --- main/static/js/annotation/annotation-multi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/static/js/annotation/annotation-multi.js b/main/static/js/annotation/annotation-multi.js index 09e5ba962..e795a4ca8 100644 --- a/main/static/js/annotation/annotation-multi.js +++ b/main/static/js/annotation/annotation-multi.js @@ -1369,7 +1369,7 @@ class AnnotationMulti extends TatorElement { } if (notReady) { - this.handleNotReadyEvent(); + this.handleAllNotReadyEvents(); } } From e810fd8be58ca15665ea21341f9e9a59142bbb5d Mon Sep 17 00:00:00 2001 From: Mark Taipan Date: Fri, 1 Oct 2021 15:39:14 +0000 Subject: [PATCH 11/24] Changed mediaElementCount printout to log when the count changes --- main/static/js/annotator/video.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main/static/js/annotator/video.js b/main/static/js/annotator/video.js index 0f11fc30a..ecff0dcc5 100644 --- a/main/static/js/annotator/video.js +++ b/main/static/js/annotator/video.js @@ -1417,7 +1417,11 @@ class VideoCanvas extends AnnotationCanvas { for (let vidBuffIdx=0; vidBuffIdx < that._videoElement.length; vidBuffIdx++) { totalMediaElementCount += that._videoElement[vidBuffIdx].getMediaElementCount(); } - console.log(`(Media ID: ${that._videoObject.id}) Current mediaElementCount: ${totalMediaElementCount}`); + + if (that._lastMediaElementCount != totalMediaElementCount) { + console.log(`(Media ID: ${that._videoObject.id}) mediaElementCount = ${totalMediaElementCount}`); + that._lastMediaElementCount = totalMediaElementCount; + } //console.log(`....downloaded: ${parseInt(100*e.data["percent_complete"])} (buf_idx: ${e.data["buf_idx"]})`) let video_buffer = that._videoElement[e.data["buf_idx"]]; From 6a3fd7dff7d8b04d1737786f58e95909b1ba7e2d Mon Sep 17 00:00:00 2001 From: Mark Taipan Date: Fri, 1 Oct 2021 20:45:10 +0000 Subject: [PATCH 12/24] Disabling the onDemand seek for now to be fixed later. It's occasionally bugging out. --- main/static/js/annotation/seek-bar.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main/static/js/annotation/seek-bar.js b/main/static/js/annotation/seek-bar.js index e9c287da1..0f6c43816 100644 --- a/main/static/js/annotation/seek-bar.js +++ b/main/static/js/annotation/seek-bar.js @@ -162,6 +162,8 @@ class SeekBar extends TatorElement { onDemandLoaded(evt) { + return; // #TODO Fix this + if (evt.detail.ranges.length == 0) { return; From 9b877d2c0be01a2021a4c27da77342511458d20d Mon Sep 17 00:00:00 2001 From: Mark Taipan Date: Mon, 4 Oct 2021 16:37:33 +0000 Subject: [PATCH 13/24] Added promise sync to pause in multiview. Fixed zoom scrub seek problems. --- main/static/js/annotation/annotation-multi.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/main/static/js/annotation/annotation-multi.js b/main/static/js/annotation/annotation-multi.js index e795a4ca8..aa608a136 100644 --- a/main/static/js/annotation/annotation-multi.js +++ b/main/static/js/annotation/annotation-multi.js @@ -503,7 +503,7 @@ class AnnotationMulti extends TatorElement { let this_frame = Math.round(frame * (this._fps[idx]/prime_fps)); video.stopPlayerThread(); // Don't use video.pause because we are seeking ourselves video.shutdownOnDemandDownload(); - const seekPromise = video.seekFrame(this_frame, video.drawFrame, true); + const seekPromise = video.seekFrame(this_frame, video.drawFrame); seekPromiseList.push(seekPromise); } @@ -521,7 +521,7 @@ class AnnotationMulti extends TatorElement { let this_frame = Math.round(primeFrame * (this._fps[idx]/prime_fps)); if (this_frame != video.currentFrame()) { - video.seekFrame(this_frame, video.drawFrame, true).then(() => { + video.seekFrame(this_frame, video.drawFrame).then(() => { this._lastScrub = Date.now(); }); } @@ -1676,18 +1676,20 @@ class AnnotationMulti extends TatorElement { this.disablePlayUI(); // Wait for playbackReady checks to enable play const paused = this.is_paused(); + var pausePromises = []; if (paused == false) { for (let video of this._videos) { - video.pause(); + pausePromises.push(video.pause()); } this._play.setAttribute("is-paused", ""); } clearTimeout(this._syncThread); - - // If we are in focus mode, sync to the focused video. - // Otherwise, use the video with the most data. - this.goToFrame(this._videos[this._primaryVideoIndex].currentFrame()); + Promise.all(pausePromises).then(() => { + // If we are in focus mode, sync to the focused video. + // Otherwise, use the video with the most data. + this.goToFrame(this._videos[this._primaryVideoIndex].currentFrame()); + }); } disablePlayUI() { From 58ec112a1440a13f26d11d97377899a48b948398 Mon Sep 17 00:00:00 2001 From: Mark Taipan Date: Mon, 4 Oct 2021 21:03:19 +0000 Subject: [PATCH 14/24] Fixed zoomable scrub bar issue in multiview. Prevent check_ready when scrubbing. Utilize playbackReady status in annotation-player. Put back high quality on mouse release when using scrub bar. Put back onDemand seek bar UI. --- main/static/js/annotation/annotation-multi.js | 44 ++++++++++++++----- .../static/js/annotation/annotation-player.js | 40 +++++++++++++++-- main/static/js/annotation/seek-bar.js | 2 - main/static/js/annotator/video.js | 23 +++++++--- 4 files changed, 87 insertions(+), 22 deletions(-) diff --git a/main/static/js/annotation/annotation-multi.js b/main/static/js/annotation/annotation-multi.js index aa608a136..86dcc2326 100644 --- a/main/static/js/annotation/annotation-multi.js +++ b/main/static/js/annotation/annotation-multi.js @@ -257,6 +257,8 @@ class AnnotationMulti extends TatorElement { } }); + this._videoStatus = "paused"; // Possible values: playing | paused | scrubbing + // Start out with play button disabled. this._play._button.setAttribute("disabled",""); // Use some spaces because the tooltip z-index is wrong @@ -275,7 +277,7 @@ class AnnotationMulti extends TatorElement { this._zoomSliderDiv.hidden = false; this._zoomSlider.setAttribute("min", evt.detail.minFrame); this._zoomSlider.setAttribute("max", evt.detail.maxFrame); - this._zoomSlider.value = Number(this._currentFrameText.textContent); + this._zoomSlider.value = this._slider.value; } }); @@ -465,6 +467,9 @@ class AnnotationMulti extends TatorElement { const frame = Number(evt.target.value); const waitOk = now - this._lastScrub > this._scrubInterval; if (waitOk) { + + this._videoStatus = "scrubbing"; + this._play.setAttribute("is-paused",""); let prime_fps = this._fps[this._longest_idx]; for (let idx = 0; idx < this._videos.length; idx++) @@ -495,6 +500,8 @@ class AnnotationMulti extends TatorElement { frame = evt.detail.frame; } + this._videoStatus = "scrubbing"; + var seekPromiseList = []; let prime_fps = this._fps[this._longest_idx]; for (let idx = 0; idx < this._videos.length; idx++) @@ -503,7 +510,7 @@ class AnnotationMulti extends TatorElement { let this_frame = Math.round(frame * (this._fps[idx]/prime_fps)); video.stopPlayerThread(); // Don't use video.pause because we are seeking ourselves video.shutdownOnDemandDownload(); - const seekPromise = video.seekFrame(this_frame, video.drawFrame); + const seekPromise = video.seekFrame(this_frame, video.drawFrame, true); seekPromiseList.push(seekPromise); } @@ -521,12 +528,14 @@ class AnnotationMulti extends TatorElement { let this_frame = Math.round(primeFrame * (this._fps[idx]/prime_fps)); if (this_frame != video.currentFrame()) { - video.seekFrame(this_frame, video.drawFrame).then(() => { + video.seekFrame(this_frame, video.drawFrame, true).then(() => { this._lastScrub = Date.now(); }); } video.onDemandDownloadPrefetch(this_frame); }; + + this._videoStatus = "paused"; this.handleAllNotReadyEvents(); this.dispatchEvent(new Event("hideLoading", {composed: true})); }) @@ -540,6 +549,8 @@ class AnnotationMulti extends TatorElement { */ processFrameInput() { + this._videoStatus = "paused"; + var frame = parseInt(this._currentFrameInput.value); if (isNaN(frame)) { console.log("Provided invalid frame input: " + this._currentFrameInput.value); @@ -569,6 +580,8 @@ class AnnotationMulti extends TatorElement { */ processTimeInput() { + this._videoStatus = "paused"; + var timeTokens = this._currentTimeInput.value.split(":"); if (timeTokens.length != 2) { @@ -723,9 +736,6 @@ class AnnotationMulti extends TatorElement { } }; this._slider.onDemandLoaded(fakeEvt); - - let frame = Math.round(fakeEvt.detail.percent_complete * this._maxFrameNumber); - this._zoomSlider.setLoadProgress(frame); }; let setup_video = (idx, video_info) => { this._slider.setAttribute("min", 0); @@ -1015,7 +1025,7 @@ class AnnotationMulti extends TatorElement { */ setDefaultVideoSettings(idx) { - console.log(`**** Setting default video settings for: ${idx}`) + console.log(`Setting default video settings for: ${idx}`) const seekInfo = this._videos[idx].getQuality("seek"); const scrubInfo = this._videos[idx].getQuality("scrub"); @@ -1460,6 +1470,16 @@ class AnnotationMulti extends TatorElement { this._last_duration = this._videos[videoIndex].playBufferDuration(); let check_ready = (checkFrame) => { + + if (this._videoStatus == "scrubbing") { + console.log(`Player status == scrubbing | Cancelling check_ready for video: ${videoIndex}`); + return; + } + if (this._videoStatus == "playing") { + console.error(`Player status == playing | Cancelling check_ready for video: ${videoIndex}`); + return; + } + timeoutCounter += clock_check; let not_ready = false; @@ -1491,7 +1511,6 @@ class AnnotationMulti extends TatorElement { timeoutIndex = 0; } if (timeoutIndex < timeouts[timeouts.length-1]/clock_check) { - //console.log(`Video ${videoIndex} playback check - Not ready: checking in ${500/1000} seconds [Now: ${new Date().toISOString()}]`); this._handleNotReadyTimeout[videoIndex] = setTimeout(() => { this._handleNotReadyTimeout[videoIndex] = null; check_ready(checkFrame); @@ -1531,7 +1550,6 @@ class AnnotationMulti extends TatorElement { } }; - //console.log(`Video ${videoIndex} playback check - Not ready: checking in ${500/1000} seconds [Now: ${new Date().toISOString()}]`); this._handleNotReadyTimeout[videoIndex] = setTimeout(() => { this._handleNotReadyTimeout[videoIndex] = null; check_ready(this._videos[videoIndex].currentFrame()) @@ -1565,6 +1583,7 @@ class AnnotationMulti extends TatorElement { if (playing) { + this._videoStatus = "playing"; this._play.removeAttribute("is-paused"); this._syncThread = setTimeout(() => {this.syncCheck()}, 500); @@ -1598,7 +1617,8 @@ class AnnotationMulti extends TatorElement { } if (playing) { - this._play.removeAttribute("is-paused"); + this._videoStatus = "playing"; + this._play.removeAttribute("is-paused"); } this.syncCheck(); } @@ -1629,6 +1649,7 @@ class AnnotationMulti extends TatorElement { if (playing) { + this._videoStatus = "playing"; this._play.removeAttribute("is-paused"); this._syncThread = setTimeout(() => {this.syncCheck()}, 500); @@ -1663,6 +1684,7 @@ class AnnotationMulti extends TatorElement { playing |= video.playBackwards(); if (playing) { + this._videoStatus = "playing"; this._play.removeAttribute("is-paused"); } } @@ -1688,6 +1710,8 @@ class AnnotationMulti extends TatorElement { Promise.all(pausePromises).then(() => { // If we are in focus mode, sync to the focused video. // Otherwise, use the video with the most data. + + this._videoStatus = "paused"; this.goToFrame(this._videos[this._primaryVideoIndex].currentFrame()); }); } diff --git a/main/static/js/annotation/annotation-player.js b/main/static/js/annotation/annotation-player.js index a9003f35b..2d690100e 100644 --- a/main/static/js/annotation/annotation-player.js +++ b/main/static/js/annotation/annotation-player.js @@ -291,6 +291,15 @@ class AnnotationPlayer extends TatorElement { this.safeMode(); }); + this._video.addEventListener("playbackReady", () =>{ + if (this.is_paused()) { + this._play._button.removeAttribute("disabled"); + this._rewind.removeAttribute("disabled") + this._fastForward.removeAttribute("disabled"); + this._play.removeAttribute("tooltip"); + } + }); + this._timelineD3.addEventListener("zoomedTimeline", evt => { if (evt.detail.minFrame < 1 || evt.detail.maxFrame < 1) { // Reset the slider @@ -302,7 +311,7 @@ class AnnotationPlayer extends TatorElement { this._zoomSliderDiv.hidden = false; this._zoomSlider.setAttribute("min", evt.detail.minFrame); this._zoomSlider.setAttribute("max", evt.detail.maxFrame); - this._zoomSlider.value = Number(this._currentFrameText.textContent); + this._zoomSlider.value = this._slider.value; } }); @@ -410,9 +419,6 @@ class AnnotationPlayer extends TatorElement { if (this._play._button.hasAttribute("disabled")) { return; } - if (this._playbackDisabled) { - return; - } if (this.is_paused()) { this.play(); @@ -449,6 +455,8 @@ class AnnotationPlayer extends TatorElement { } } }); + + this._videoStatus = "paused"; // Possible values: playing | paused | scrubbing } static get observedAttributes() { @@ -523,6 +531,9 @@ class AnnotationPlayer extends TatorElement { const frame = Number(evt.target.value); const waitOk = now - this._lastScrub > this._scrubInterval; if (waitOk) { + + this._videoStatus = "scrubbing"; + this._play.setAttribute("is-paused",""); this._video.stopPlayerThread(); this._video.shutdownOnDemandDownload(); @@ -543,6 +554,9 @@ class AnnotationPlayer extends TatorElement { { frame = evt.detail.frame; } + + this._videoStatus = "scrubbing"; + this._video.stopPlayerThread(); this._video.shutdownOnDemandDownload(); @@ -550,6 +564,7 @@ class AnnotationPlayer extends TatorElement { this._video.seekFrame(frame, this._video.drawFrame, true).then(() => { this._lastScrub = Date.now() this._video.onDemandDownloadPrefetch(frame); + this._videoStatus = "paused"; this.handleNotReadyEvent(); this.dispatchEvent(new Event("hideLoading", {composed: true})); }).catch((e) => { @@ -564,6 +579,8 @@ class AnnotationPlayer extends TatorElement { */ processFrameInput() { + this._videoStatus = "paused"; + var frame = parseInt(this._currentFrameInput.value); if (isNaN(frame)) { console.log("Provided invalid frame input: " + this._currentFrameInput.value); @@ -593,6 +610,8 @@ class AnnotationPlayer extends TatorElement { */ processTimeInput() { + this._videoStatus = "paused"; + var timeTokens = this._currentTimeInput.value.split(":"); if (timeTokens.length != 2) { @@ -784,6 +803,16 @@ class AnnotationPlayer extends TatorElement { this._last_duration = this._video.playBufferDuration(); let check_ready = (checkFrame) => { + + if (this._videoStatus == "scrubbing") { + console.log(`Player status == scrubbing | Cancelling check_ready`); + return; + } + if (this._videoStatus == "playing") { + console.error(`Player status == playing | Cancelling check_ready`); + return; + } + timeoutCounter += clock_check; this._handleNotReadyTimeout = null; @@ -876,6 +905,7 @@ class AnnotationPlayer extends TatorElement { this._video.rateChange(this._rate); if (this._video.play()) { + this._videoStatus = "playing"; this._play.removeAttribute("is-paused"); } } @@ -914,6 +944,7 @@ class AnnotationPlayer extends TatorElement { this._video.rateChange(this._rate); if (this._video.playBackwards()) { + this._videoStatus = "playing"; this._play.removeAttribute("is-paused"); } } @@ -930,6 +961,7 @@ class AnnotationPlayer extends TatorElement { this._video.pause(); this._play.setAttribute("is-paused", "") } + this._videoStatus = "paused"; this.checkReady(); } diff --git a/main/static/js/annotation/seek-bar.js b/main/static/js/annotation/seek-bar.js index 0f6c43816..e9c287da1 100644 --- a/main/static/js/annotation/seek-bar.js +++ b/main/static/js/annotation/seek-bar.js @@ -162,8 +162,6 @@ class SeekBar extends TatorElement { onDemandLoaded(evt) { - return; // #TODO Fix this - if (evt.detail.ranges.length == 0) { return; diff --git a/main/static/js/annotator/video.js b/main/static/js/annotator/video.js index ecff0dcc5..59f5bcb75 100644 --- a/main/static/js/annotator/video.js +++ b/main/static/js/annotator/video.js @@ -1376,6 +1376,19 @@ class VideoCanvas extends AnnotationCanvas { } } + isPlaybackReady() { + return this._onDemandPlaybackReady; + } + + sendPlaybackReady() { + this.dispatchEvent(new CustomEvent( + "playbackReady", + { + composed: true, + detail: {playbackReadyId: this._waitId}, + })); + } + startDownload(streaming_files, offsite_config) { var that = this; @@ -1546,6 +1559,7 @@ class VideoCanvas extends AnnotationCanvas { detail: {playbackReadyId: this._waitId}, })); that._onDemandPlaybackReady = true; // fake it + that.sendPlaybackReady(); } else if (type == "onDemandInit") { @@ -1557,6 +1571,7 @@ class VideoCanvas extends AnnotationCanvas { console.log("onDemand finished downloading. Reached end of video."); that._onDemandFinished = true; that._onDemandPlaybackReady = true; //if we reached the end, we are done. + that.sendPlaybackReady(); } else if (type == "onDemand") { @@ -2868,6 +2883,7 @@ class VideoCanvas extends AnnotationCanvas { } else if (this.videoBuffer(this.currentFrame(), "scrub") && this._play_idx == this._scrub_idx) { this._onDemandPlaybackReady = true; + this.sendPlaybackReady(); return; } @@ -3118,12 +3134,7 @@ class VideoCanvas extends AnnotationCanvas { { console.log(`(ID:${this._videoObject.id}) onDemandPlaybackReady (start/end/current/timeToEnd): ${start} ${end} ${currentTime} ${timeToEnd}`); this._onDemandPlaybackReady = true; - this.dispatchEvent(new CustomEvent( - "playbackReady", - { - composed: true, - detail: {playbackReadyId: this._waitId}, - })); + this.sendPlaybackReady(); } } } From e2a64733b581fd32f4a8ccd53614e950fb6d9edc Mon Sep 17 00:00:00 2001 From: Mark Taipan Date: Mon, 4 Oct 2021 22:07:40 +0000 Subject: [PATCH 15/24] Fixed multiview quality control not showing the correct playback quality at init --- main/static/js/annotation/annotation-multi.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main/static/js/annotation/annotation-multi.js b/main/static/js/annotation/annotation-multi.js index 86dcc2326..e16328214 100644 --- a/main/static/js/annotation/annotation-multi.js +++ b/main/static/js/annotation/annotation-multi.js @@ -1765,6 +1765,9 @@ class AnnotationMulti extends TatorElement { } else { this._quality = quality; + if (this._qualityControl._select != null) { + this._qualityControl.quality = quality; + } for (let video of this._videos) { video.setQuality(quality, buffer); From 493e4d113ba99ea059c762fee14efb64f75acf45 Mon Sep 17 00:00:00 2001 From: Mark Taipan Date: Mon, 4 Oct 2021 22:21:32 +0000 Subject: [PATCH 16/24] Set available multiview qualities first - related with previous commit --- main/static/js/annotation/annotation-page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/static/js/annotation/annotation-page.js b/main/static/js/annotation/annotation-page.js index b4b137210..256745ae1 100644 --- a/main/static/js/annotation/annotation-page.js +++ b/main/static/js/annotation/annotation-page.js @@ -257,8 +257,8 @@ class AnnotationPage extends TatorPage { playbackQuality = Number(searchParams.get("playQuality")); } this._settings.quality = playbackQuality; - this._player.setQuality(playbackQuality, null, true); this._player.setAvailableQualities(primeMediaData); + this._player.setQuality(playbackQuality, null, true); } ); } else if (type_data.dtype == "live") { From 673a639c94bb5181c3fe243d1819c4c439d06300 Mon Sep 17 00:00:00 2001 From: Jonathan Takahashi Date: Tue, 5 Oct 2021 11:20:11 +0000 Subject: [PATCH 17/24] Fix default localization lookup. Closes #504. --- main/static/js/annotation/annotation-page.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/static/js/annotation/annotation-page.js b/main/static/js/annotation/annotation-page.js index 256745ae1..62ed35db5 100644 --- a/main/static/js/annotation/annotation-page.js +++ b/main/static/js/annotation/annotation-page.js @@ -820,7 +820,8 @@ class AnnotationPage extends TatorPage { if (localizationTypeId === null) { throw "Could not find a localization type to use for track creation!"; } - dataType.localizationType = dataTypes.filter(type => type.id == localizationTypeId)[0]; + dataType.localizationType = dataTypes.filter(type => (type.id == localizationTypeId + || Number(type.id.split('_')[1]) == localizationTypeId))[0]; } } this._data.init(dataTypes, this._version, projectId, mediaId, update, !block_signals); From 6bcdb6379f61234e1cc1955bc049e6bffb6c8a8d Mon Sep 17 00:00:00 2001 From: ermbutler Date: Tue, 5 Oct 2021 13:28:03 +0000 Subject: [PATCH 18/24] added poly to localization type drop down fixes #498 --- .../js/project-settings/type-forms/localization-type-edit.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/static/js/project-settings/type-forms/localization-type-edit.js b/main/static/js/project-settings/type-forms/localization-type-edit.js index e50f3e8d7..f8f59e9bc 100644 --- a/main/static/js/project-settings/type-forms/localization-type-edit.js +++ b/main/static/js/project-settings/type-forms/localization-type-edit.js @@ -29,7 +29,8 @@ class LocalizationEdit extends TypeForm { { "label": "Select", "value": "" }, { "label": "Box", "value": "box" }, { "label": "Line", "value": "line" }, - { "label": "Dot", "value": "dot" } + { "label": "Dot", "value": "dot" }, + { "label": "Poly", "value": "poly" } ]; this.dtypeSelect = document.createElement("enum-input"); this.dtypeSelect.setAttribute("name", "Data Type"); From a1a4ae66e3872f6fd7f8f53b70b69bb68f8c1991 Mon Sep 17 00:00:00 2001 From: ermbutler Date: Tue, 5 Oct 2021 13:47:09 +0000 Subject: [PATCH 19/24] fixes 495 add status field to invitation form --- .../organization-settings/invitation-edit.js | 122 +++++++++++------- .../organization-type-form.js | 2 +- 2 files changed, 77 insertions(+), 47 deletions(-) diff --git a/main/static/js/organization-settings/invitation-edit.js b/main/static/js/organization-settings/invitation-edit.js index 29d0c2021..55ae95678 100644 --- a/main/static/js/organization-settings/invitation-edit.js +++ b/main/static/js/organization-settings/invitation-edit.js @@ -17,11 +17,11 @@ class InvitationEdit extends OrganizationTypeForm { _getEmptyData() { return { - "id" : `New`, - "user" : "", + "id": `New`, + "user": "", "permission": "", - "organization" : this.organizationId, - "form" : "empty" + "organization": this.organizationId, + "form": "empty" }; } @@ -31,11 +31,11 @@ class InvitationEdit extends OrganizationTypeForm { _getExistingForm(data) { // console.log("Get existing form"); - // console.log(data.id); + // console.log(data); - let current = this.boxHelper.boxWrapDefault( { - "children" : "" - } ); + let current = this.boxHelper.boxWrapDefault({ + "children": "" + }); // this._setForm(); @@ -65,9 +65,19 @@ class InvitationEdit extends OrganizationTypeForm { this._statusSelect._select.required = true; this._statusSelect.setValue(data.status); this._statusSelect.default = data.status; - this._statusSelect.permission = "View Only" + this._statusSelect.permission = "View Only"; this._statusSelect.addEventListener("change", this._formChanged.bind(this)); - this._form.appendChild( this._permissionSelect ); + this._form.appendChild(this._permissionSelect); + + // status + this._statusField = document.createElement("text-input"); + this._statusField.setAttribute("name", "Status"); + this._statusField.setAttribute("type", "string"); + this._statusField.setValue(this.data.status); + this._statusField.default = this.data.status; + this._statusField.permission = "View Only"; + // this._statusField.addEventListener("change", this._formChanged.bind(this)); + this._form.appendChild(this._statusField); current.appendChild(this._form); @@ -76,9 +86,9 @@ class InvitationEdit extends OrganizationTypeForm { _getNewForm(data) { // console.log("Get new form"); - let current = this.boxHelper.boxWrapDefault( { - "children" : "" - } ); + let current = this.boxHelper.boxWrapDefault({ + "children": "" + }); this._setForm(); this._emailInput = document.createElement("email-list-input"); @@ -88,8 +98,8 @@ class InvitationEdit extends OrganizationTypeForm { this._permission = document.createElement("enum-input"); this._permission.setAttribute("name", "Permission"); this._permission.choices = [ - {value: "Member"}, - {value: "Admin"}, + { value: "Member" }, + { value: "Admin" }, ]; this._form.appendChild(this._permission); @@ -104,7 +114,7 @@ class InvitationEdit extends OrganizationTypeForm { return this._getExistingForm(data); } } - + _getFormData(id) { let formData; if (id == "New") { @@ -149,44 +159,64 @@ class InvitationEdit extends OrganizationTypeForm { this.sideNav.hide(`itemDivId-${this.typeName}-New`); // Create and show the container with new type + this._updateNavEvent("new", email, data.id); this.sideNav.addItemContainer({ - "type" : this.typeName, - "id" : data.id, - "hidden" : false + "type": this.typeName, + "id": data.id, + "hidden": false }); - let form = document.createElement( this._getTypeClass() ); - form.init(this._data); + this._data = formData; + this._data.status = "PENDING"; + this._data.id = data.id; + this._data.organization = this.organization; - this.sideNav.fillContainer({ - "type" : this.typeName, - "id" : data.id, - "itemContents" : form - }); - - // init form with the data - formData.id = data.id; - formData.organization = this.organization; - form._init({ - "data": formData, - "modal" : this.modal, - "sidenav" : this.sideNav - }); - - // Add the item to navigation - this._updateNavEvent("new", email, data.id); - if (!this._emailEnabled) { let link = data.message.replace('User can register at ', '') emailLinksHTML += `
  • ${email} can register at ${link}
  • `; } - // Increment succeeded. - numSucceeded++; - }).catch((err) => { - console.error(err); - errorMessages = `${errorMessages}

    ${err}

    `; - numFailed++; - }); + + return data.id; + }) + .then((id) => { + return fetch(`/rest/Invitation/${id}`, { + method: "GET", + credentials: "same-origin", + headers: { + "X-CSRFToken": getCookie("csrftoken"), + "Accept": "application/json", + "Content-Type": "application/json" + } + }); + }).then((resp) => { + return resp.json(); + }).then((data) => { + if (typeof data.id !== "undefined" && data.id === this._data.id) { + // if we can can get the status from endpoint, it is hardcoded above as pending (assumed since it was just created) + this._data = data; + } + + let form = document.createElement(this._getTypeClass()); + this.sideNav.fillContainer({ + "type": this.typeName, + "id": this._data.id, + "itemContents": form + }); + + // init form with the data + form._init({ + "data": this._data, + "modal": this.modal, + "sidenav": this.sideNav + }); + + // Increment succeeded. + numSucceeded++; + }).catch((err) => { + console.error(err); + errorMessages = `${errorMessages}

    ${err}

    `; + numFailed++; + }); promises.push(promise); } diff --git a/main/static/js/organization-settings/organization-type-form.js b/main/static/js/organization-settings/organization-type-form.js index d2e31d70d..cb62d3e5f 100644 --- a/main/static/js/organization-settings/organization-type-form.js +++ b/main/static/js/organization-settings/organization-type-form.js @@ -38,7 +38,7 @@ class OrganizationTypeForm extends TatorElement { // Pass modal to helper this.boxHelper = new SettingsBox(this.modal); - console.log(this.organizationId); + // console.log(this.organizationId); this._addNew = new TypeNew({ "type": this.typeName, "projectId": this.organizationId From 8485b962030425946443b402a998de481a81d2a5 Mon Sep 17 00:00:00 2001 From: ermbutler Date: Tue, 5 Oct 2021 14:13:26 +0000 Subject: [PATCH 20/24] Updating tests for project and org settings to cover Poly loc type creation via settings, and to check status is updated for new invitiation --- test/test_organization_settings.py | 10 +++++ test/test_settings.py | 66 ++++++++---------------------- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/test/test_organization_settings.py b/test/test_organization_settings.py index c510426ea..f7cd1776e 100644 --- a/test/test_organization_settings.py +++ b/test/test_organization_settings.py @@ -41,6 +41,7 @@ def test_organization_settings(authenticated, project, launch_time, image_file, response = response_info.value respObject = response.json() registration_link = str(respObject["message"]).replace('User can register at ', '') + new_user_id = respObject["id"] print("Invitation sent successful!") # Note: Existing user gets redirected to /organization, but new user gets form. @@ -69,6 +70,15 @@ def test_organization_settings(authenticated, project, launch_time, image_file, page.goto(f'/{organization_id}/organization-settings') + print("Confirming invitation status") + page.click('.heading-for-Invitation') + page.click(f'text="{user_email}"') + page.wait_for_selector(f'#itemDivId-Invitation-{new_user_id} text-input[name="Status"] input') + statusInputValue = page.eval_on_selector(f'#itemDivId-Invitation-{new_user_id} text-input[name="Status"] input', "i => i.value") + print(statusInputValue) + assert statusInputValue == "Accepted" + print("Invitation status shown as accepted!") + print("Testing affiliation create...") url = base_url + "/rest/Affiliations/" + str(organization_id) page.click('.heading-for-Affiliation') diff --git a/test/test_settings.py b/test/test_settings.py index 7be7f37e6..42a2ca6bf 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -78,54 +78,24 @@ def test_settings_localizationTypes(authenticated, project): page.goto(f"/{project}/project-settings") page.on("pageerror", print_page_error) - # Create Localization types - page.click('.heading-for-LocalizationType .Nav-action') - page.fill('#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto Box Type') - page.select_option('#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label='Box') - page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') - # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') - # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') - page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') - page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') - page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') - page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') - page.click('#itemDivId-LocalizationType-New span:text("Test Images")') - page.click('#itemDivId-LocalizationType-New button[value="Save"]') - page.wait_for_selector(f'text="Localization type created successfully!"') - page.click('modal-dialog modal-close .modal__close') - print(f"Box - Localization type created successfully!!") - - page.click('.heading-for-LocalizationType .Nav-action') - page.fill('#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto Line Type') - page.select_option('#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label='Line') - page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') - # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') - # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') - page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') - page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') - page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') - page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') - page.click('#itemDivId-LocalizationType-New span:text("Test Images")') - page.click('#itemDivId-LocalizationType-New button[value="Save"]') - page.wait_for_selector(f'text="Localization type created successfully!"') - page.click('modal-dialog modal-close .modal__close') - print(f"Line - Localization type created successfully!!") - - page.click('.heading-for-LocalizationType .Nav-action') - page.fill('#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto Dot Type') - page.select_option('#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label='Dot') - page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') - # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') - # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') - page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') - page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') - page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') - page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') - page.click('#itemDivId-LocalizationType-New span:text("Test Images")') - page.click('#itemDivId-LocalizationType-New button[value="Save"]') - page.wait_for_selector(f'text="Localization type created successfully!"') - page.click('modal-dialog modal-close .modal__close') - print(f"Dot - Localization type created successfully!!") + localization_dtypeSet = {"Box","Line","Dot","Poly"} + for dtypeName in localization_dtypeSet: + # Create types + page.click('.heading-for-LocalizationType .Nav-action') + page.fill(f'#itemDivId-LocalizationType-New text-input[name="Name"] input', 'Auto {dtypeName} Type') + page.select_option(f'#itemDivId-LocalizationType-New enum-input[name="Data Type"] select', label=dtypeName) + page.fill('#itemDivId-LocalizationType-New text-input[name="Description"] input', 'Loc Type description for automated test.') + # page.click('#itemDivId-LocalizationType-New text-input[type="color"]') + # page.fill('#itemDivId-LocalizationType-New text-input[type="color"] input', '#FF69B4') + page.click('#itemDivId-LocalizationType-New bool-input[name="Visible"] label[for="on"]') + page.click('#itemDivId-LocalizationType-New bool-input[name="Drawable"] label[for="on"]') + page.fill('#itemDivId-LocalizationType-New text-input[name="Line Width"] input', '5') + page.click('#itemDivId-LocalizationType-New bool-input[name="Grouping Default"] label[for="on"]') + page.click('#itemDivId-LocalizationType-New span:text("Test Images")') + page.click('#itemDivId-LocalizationType-New button[value="Save"]') + page.wait_for_selector(f'text="Localization type created successfully!"') + page.click('modal-dialog modal-close .modal__close') + print(f"{dtypeName} - Localization type created successfully!!") def test_settings_leafType(authenticated, project): print("Leaf Type Tests...") From a76b0d3c7b93a99bf7c78bcd3a2396f9cff2098d Mon Sep 17 00:00:00 2001 From: ermbutler Date: Tue, 5 Oct 2021 16:47:49 +0000 Subject: [PATCH 21/24] Fixing missing id error in typeForm. --- main/static/js/project-settings/type-forms/type-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/static/js/project-settings/type-forms/type-form.js b/main/static/js/project-settings/type-forms/type-form.js index 0bfd22033..4f75b0944 100644 --- a/main/static/js/project-settings/type-forms/type-form.js +++ b/main/static/js/project-settings/type-forms/type-form.js @@ -640,7 +640,7 @@ class TypeForm extends TatorElement { this.nameChanged = true; this.newName = formData.name; } - return this._fetchPatchPromise({ id, formData }); + return this._fetchPatchPromise({ id: this.typeId, formData }); }) .then(response => response.json().then(data => ({ response: response, data: data }))) .then(obj => { From 2e2f8f79e6070aa64b527eacc8769312fabc2fec Mon Sep 17 00:00:00 2001 From: ermbutler Date: Tue, 5 Oct 2021 16:53:08 +0000 Subject: [PATCH 22/24] Bug fix related to attribute tune up resetting text strings --- main/static/js/project-settings/type-forms/type-form.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main/static/js/project-settings/type-forms/type-form.js b/main/static/js/project-settings/type-forms/type-form.js index 4f75b0944..63135e000 100644 --- a/main/static/js/project-settings/type-forms/type-form.js +++ b/main/static/js/project-settings/type-forms/type-form.js @@ -629,13 +629,15 @@ class TypeForm extends TatorElement { _typeFormChanged() { const formData = this._getFormData(); let promise = Promise.resolve(); - // console.log("Main form was changed"); if (Object.entries(formData).length === 0) { return console.error("No formData"); } else { return promise.then(() => { - // + //reset message strings + this.successMessages = ""; + this.failedMessages = ""; + this.confirmMessages = ""; if (typeof formData.name !== "undefined") { this.nameChanged = true; this.newName = formData.name; From 76303951a2250166f7f4a9d3d6a1a4056f0bfd3b Mon Sep 17 00:00:00 2001 From: ermbutler Date: Tue, 5 Oct 2021 17:55:00 +0000 Subject: [PATCH 23/24] Changed line width input to type int to fix it always looking changed; Also removed unhit logic --- .../type-forms/localization-type-edit.js | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/main/static/js/project-settings/type-forms/localization-type-edit.js b/main/static/js/project-settings/type-forms/localization-type-edit.js index f8f59e9bc..71b72a4c7 100644 --- a/main/static/js/project-settings/type-forms/localization-type-edit.js +++ b/main/static/js/project-settings/type-forms/localization-type-edit.js @@ -90,17 +90,16 @@ class LocalizationEdit extends TypeForm { // line_width - if (data.dtype != 'image') { - this._lineWidth = document.createElement("text-input"); - this._lineWidth.setAttribute("name", "Line Width"); - this._lineWidth.setAttribute("type", "number"); - this._lineWidth.setValue(this.data.line_width); - this._lineWidth.default = this.data.line_width; - this._lineWidth._input.min = 1; - this._lineWidth._input.max = 10; - this._lineWidth.addEventListener("change", this._formChanged.bind(this)); - this._form.appendChild(this._lineWidth); - } + this._lineWidth = document.createElement("text-input"); + this._lineWidth.setAttribute("name", "Line Width"); + this._lineWidth.setAttribute("type", "int"); + this._lineWidth.setValue(this.data.line_width); + this._lineWidth.default = this.data.line_width; + this._lineWidth._input.min = 1; + this._lineWidth._input.max = 10; + this._lineWidth.addEventListener("change", this._formChanged.bind(this)); + this._form.appendChild(this._lineWidth); + // grouping default this._groupingDefault = document.createElement("bool-input"); From 4d3693a8adf039d8c4c65d990278e7d9ed59669e Mon Sep 17 00:00:00 2001 From: ermbutler Date: Tue, 5 Oct 2021 18:04:50 +0000 Subject: [PATCH 24/24] Strict check on number and removed sending number --- .../project-settings/type-forms/versions-edit.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/main/static/js/project-settings/type-forms/versions-edit.js b/main/static/js/project-settings/type-forms/versions-edit.js index ed184717e..f9fb1b965 100644 --- a/main/static/js/project-settings/type-forms/versions-edit.js +++ b/main/static/js/project-settings/type-forms/versions-edit.js @@ -14,7 +14,7 @@ class VersionsEdit extends TypeForm { }); this.versionId = data.id; - console.log(this.versionId); + // console.log(this.versionId); // this._setForm(); @@ -52,7 +52,7 @@ class VersionsEdit extends TypeForm { this._number = document.createElement("text-input"); this._number.setAttribute("name", "Number"); this._number.setAttribute("type", "int"); - if (typeof data.number == "undefined" || data.number == "") { + if (typeof data.number === "undefined") { this._number.setValue("Created on Save"); this._number.default = ""; } else { @@ -67,7 +67,7 @@ class VersionsEdit extends TypeForm { // Bases const basesListWithChecked = await this.versionListHandler.getCompiledVersionList(data.bases, data.id); - console.log(basesListWithChecked); + // console.log(basesListWithChecked); this._basesCheckbox = document.createElement("checkbox-set"); this._basesCheckbox.setAttribute("name", "Bases"); @@ -101,9 +101,9 @@ class VersionsEdit extends TypeForm { formData.show_empty = this._showEmpty.getValue(); } - if (this._number.changed() || isNew) { - formData._number = this._number.getValue(); - } + // if (this._number.changed() || isNew) { + // formData.number = this._number.getValue(); + // } if (this._basesCheckbox.changed() || isNew) { formData.bases = this._basesCheckbox.getValue(); @@ -214,7 +214,7 @@ class VersionsEdit extends TypeForm { _saveConfirmed({ id = this.versionId }) { this._modalCloseCallback(); // Start spinner & Get promises list - console.log("Settings _save method for id: " + id); + // console.log("Settings _save method for id: " + id); // this.loading.showSpinner(); let promises = []