From 538541cd4dd815895e0a087212f25a4c91687abd Mon Sep 17 00:00:00 2001 From: RubenPX Date: Mon, 25 Apr 2022 11:49:11 +0200 Subject: [PATCH 01/30] filter in out trades from lots selector --- ereuse_devicehub/static/js/api.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ereuse_devicehub/static/js/api.js b/ereuse_devicehub/static/js/api.js index 190e5d42..8163e293 100644 --- a/ereuse_devicehub/static/js/api.js +++ b/ereuse_devicehub/static/js/api.js @@ -5,7 +5,10 @@ const Api = { */ async get_lots() { const request = await this.doRequest(API_URLS.lots, "GET", null); - if (request != undefined) return request.items; + if (request != undefined) { + request.items = request.items.filter(itm => !itm.trade) // Avoid show outgoing or incomming trades + return request.items; + } throw request; }, From c8dc1b11e225103848b3a786c17843259e0178b8 Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 11:22:53 +0200 Subject: [PATCH 02/30] Add trade type on api options --- ereuse_devicehub/resources/lot/views.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ereuse_devicehub/resources/lot/views.py b/ereuse_devicehub/resources/lot/views.py index dce6af62..b99e622f 100644 --- a/ereuse_devicehub/resources/lot/views.py +++ b/ereuse_devicehub/resources/lot/views.py @@ -29,6 +29,7 @@ class LotView(View): """ format = EnumField(LotFormat, missing=None) search = f.Str(missing=None) + type = f.Str(missing=None) def post(self): l = request.get_json() @@ -88,6 +89,7 @@ class LotView(View): else: query = Lot.query query = self.visibility_filter(query) + query = self.type_filter(query, args) if args['search']: query = query.filter(Lot.name.ilike(args['search'] + '%')) lots = query.paginate(per_page=6 if args['search'] else query.count()) @@ -104,6 +106,21 @@ class LotView(View): Lot.owner_id == g.user.id)) return query + def type_filter(self, query, args): + lot_type = args.get('type') + + # temporary + if lot_type == "temporary": + return query.filter(Lot.trade == None) + + if lot_type == "incoming": + return query.filter(Lot.trade and Trade.user_to == g.user) + + if lot_type == "outgoing": + return query.filter(Lot.trade and Trade.user_from == g.user) + + return query + def query(self, args): query = Lot.query.distinct() return query From a945fc085f05f8ed1c1c9fe135dbf70612bf6747 Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 11:31:24 +0200 Subject: [PATCH 03/30] Query using Api instead of javascript filter --- ereuse_devicehub/static/js/api.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ereuse_devicehub/static/js/api.js b/ereuse_devicehub/static/js/api.js index 8163e293..552544d2 100644 --- a/ereuse_devicehub/static/js/api.js +++ b/ereuse_devicehub/static/js/api.js @@ -4,11 +4,8 @@ const Api = { * @returns get lots */ async get_lots() { - const request = await this.doRequest(API_URLS.lots, "GET", null); - if (request != undefined) { - request.items = request.items.filter(itm => !itm.trade) // Avoid show outgoing or incomming trades - return request.items; - } + const request = await this.doRequest(`${API_URLS.lots}?type=temporary`, "GET", null); + if (request != undefined) return request.items; throw request; }, From 2ffa606bc356bc966bf12bd9f2b8f648d18170f1 Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 11:53:49 +0200 Subject: [PATCH 04/30] Documentation API --- docs/lots.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/lots.rst b/docs/lots.rst index e3cc83a3..03943e21 100644 --- a/docs/lots.rst +++ b/docs/lots.rst @@ -9,6 +9,12 @@ dags-with-materialized-paths-using-postgres-ltree/>`_ you have a low-level technical implementation of how lots and their relationships are mapped. +Getting lots +************ + +You can get lots list by ``GET /lots/`` +There are one optional filter ``type``, only works with this 3 values ``temporary``, ``incoming`` and ``outgoing`` + Create lots *********** You create a lot by ``POST /lots/`` a `JSON Lot object /devices/?id=&id=``; idem for removing devices. - Sharing lots ************ Sharing a lot means giving certain permissions to users, like reading From bb284008cc421dc563f4b1d0c5d1de4b0159f4d5 Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 15:01:39 +0200 Subject: [PATCH 05/30] Fix re render table --- ereuse_devicehub/static/js/main_inventory.js | 22 +++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index c230363d..6e0c9d12 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -299,15 +299,13 @@ async function processSelectedDevices() { const tmpDiv = document.createElement("div") tmpDiv.innerHTML = newRequest - const oldTable = Array.from(document.querySelectorAll("table.table > tbody > tr .deviceSelect")).map(x => x.attributes["data-device-dhid"].value) const newTable = Array.from(tmpDiv.querySelectorAll("table.table > tbody > tr .deviceSelect")).map(x => x.attributes["data-device-dhid"].value) - for (let i = 0; i < oldTable.length; i++) { - if (!newTable.includes(oldTable[i])) { - // variable from device_list.html --> See: ereuse_devicehub\templates\inventory\device_list.html (Ln: 411) - table.rows().remove(i) + table.rows().dt.activeRows.forEach(row => { + if (!newTable.includes(row.querySelector("input").attributes["data-device-dhid"].value)) { + row.remove() } - } + }) } } @@ -352,12 +350,12 @@ async function processSelectedDevices() { const listHTML = $("#LotsSelector") - // Get selected devices - const selectedDevicesIDs = $.map($(".deviceSelect").filter(":checked"), (x) => parseInt($(x).attr("data"))); - if (selectedDevicesIDs.length <= 0) { - listHTML.html("
  • No devices selected
  • "); - return; - } + // Get selected devices + const selectedDevicesIDs = $.map($(".deviceSelect").filter(":checked"), (x) => parseInt($(x).attr("data"))); + if (selectedDevicesIDs.length <= 0) { + listHTML.html("
  • No devices selected
  • "); + return; + } // Initialize Actions list, and set checkbox triggers const actions = new Actions(); From 770ab8ea8d598845ca94a946a83767cb2b5532bc Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 15:29:57 +0200 Subject: [PATCH 06/30] improvement sending variables data through methods --- ereuse_devicehub/static/js/main_inventory.js | 57 +++++++++++--------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index 6e0c9d12..187aff80 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -194,12 +194,13 @@ async function processSelectedDevices() { /** * Manage the actions that will be performed when applying the changes - * @param {*} ev event (Should be a checkbox type) - * @param {string} lotID lot id - * @param {number} deviceID device id + * @param {EventSource} ev event (Should be a checkbox type) + * @param {Lot} lot lot id + * @param {Device[]} deviceList device id */ - manage(event, lotID, deviceListID) { + manage(event, lot, deviceList) { event.preventDefault(); + const lotID = lot.id; const srcElement = event.srcElement.parentElement.children[0] const {indeterminate} = srcElement; const checked = !srcElement.checked; @@ -216,18 +217,18 @@ async function processSelectedDevices() { this.list = this.list.filter(list => list.lotID != lotID); } } else { - this.list.push({ type: "Add", lotID, devices: deviceListID, isFromIndeterminate: indeterminate }); + this.list.push({ type: "Add", lot, devices: deviceList, isFromIndeterminate: indeterminate }); } } else if (found != undefined && found.type == "Add") { - if (found.isFromIndeterminate == true) { - found.type = "Remove"; - this.list[foundIndex] = found; - } else { - this.list = this.list.filter(list => list.lotID != lotID); - } + if (found.isFromIndeterminate == true) { + found.type = "Remove"; + this.list[foundIndex] = found; } else { - this.list.push({ type: "Remove", lotID, devices: deviceListID, isFromIndeterminate: indeterminate }); + this.list = this.list.filter(list => list.lotID != lotID); } + } else { + this.list.push({ type: "Remove", lot, devices: deviceList, isFromIndeterminate: indeterminate }); + } if (this.list.length > 0) { document.getElementById("ApplyDeviceLots").classList.remove("disabled"); @@ -268,14 +269,14 @@ async function processSelectedDevices() { this.list.forEach(async action => { if (action.type == "Add") { try { - await Api.devices_add(action.lotID, action.devices); + await Api.devices_add(action.lot.id, action.devices.map(dev => dev.data)); this.notifyUser("Devices sucefully aded to selected lot/s", "", false); } catch (error) { this.notifyUser("Failed to add devices to selected lot/s", error.responseJSON.message, true); } } else if (action.type == "Remove") { try { - await Api.devices_remove(action.lotID, action.devices); + await Api.devices_remove(action.lot.id, action.devices.map(dev => dev.data)); this.notifyUser("Devices sucefully removed from selected lot/s", "", false); } catch (error) { this.notifyUser("Fail to remove devices from selected lot/s", error.responseJSON.message, true); @@ -343,19 +344,25 @@ async function processSelectedDevices() { break; } - doc.children[0].addEventListener("mouseup", (ev) => actions.manage(ev, id, selectedDevicesIDs)); - doc.children[1].addEventListener("mouseup", (ev) => actions.manage(ev, id, selectedDevicesIDs)); + doc.children[0].addEventListener("mouseup", (ev) => actions.manage(ev, lot, selectedDevices)); + doc.children[1].addEventListener("mouseup", (ev) => actions.manage(ev, lot, selectedDevices)); elementTarget.append(doc); } const listHTML = $("#LotsSelector") - // Get selected devices - const selectedDevicesIDs = $.map($(".deviceSelect").filter(":checked"), (x) => parseInt($(x).attr("data"))); - if (selectedDevicesIDs.length <= 0) { - listHTML.html("
  • No devices selected
  • "); - return; - } + // Get selected devices + const selectedDevices = table.rows().dt.activeRows.filter(item => item.querySelector("input").checked).map(item => { + const child = item.childNodes[0].children[0] + const info = {} + Object.values(child.attributes).forEach(attrib => { info[attrib.nodeName] = attrib.nodeValue }) + return info + }) + + if (selectedDevices.length <= 0) { + listHTML.html("
  • No devices selected
  • "); + return; + } // Initialize Actions list, and set checkbox triggers const actions = new Actions(); @@ -367,7 +374,7 @@ async function processSelectedDevices() { try { listHTML.html("
  • ") - const devices = await Api.get_devices(selectedDevicesIDs); + const devices = await Api.get_devices(selectedDevices.map(dev => dev.data)); let lots = await Api.get_lots(); lots = lots.map(lot => { @@ -375,11 +382,11 @@ async function processSelectedDevices() { .filter(device => device.lots.filter(devicelot => devicelot.id == lot.id).length > 0) .map(device => parseInt(device.id)); - switch (lot.devices.length) { + switch (lot.devices.length) { case 0: lot.state = "false"; break; - case selectedDevicesIDs.length: + case selectedDevices.length: lot.state = "true"; break; default: From 8d8ce634023e71d463f87527097c3ec8a2cdd7cb Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 15:32:35 +0200 Subject: [PATCH 07/30] Optimize manage list --- ereuse_devicehub/static/js/main_inventory.js | 26 ++++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index 187aff80..6cb71534 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -198,37 +198,25 @@ async function processSelectedDevices() { * @param {Lot} lot lot id * @param {Device[]} deviceList device id */ - manage(event, lot, deviceList) { + manage(event, lot, deviceListID) { event.preventDefault(); const lotID = lot.id; const srcElement = event.srcElement.parentElement.children[0] - const {indeterminate} = srcElement; const checked = !srcElement.checked; - const found = this.list.filter(list => list.lotID == lotID)[0]; - const foundIndex = found != undefined ? this.list.findLastIndex(x => x.lotID == found.lotID) : -1; + const found = this.list.filter(list => list.lot.id == lotID)[0]; if (checked) { - if (found != undefined && found.type == "Remove") { - if (found.isFromIndeterminate == true) { - found.type = "Add"; - this.list[foundIndex] = found; - } else { - this.list = this.list.filter(list => list.lotID != lotID); - } + if (found && found.type == "Remove") { + found.type = "Add"; } else { - this.list.push({ type: "Add", lot, devices: deviceList, isFromIndeterminate: indeterminate }); + this.list.push({ type: "Add", lot, devices: deviceListID}); } - } else if (found != undefined && found.type == "Add") { - if (found.isFromIndeterminate == true) { + } else if (found && found.type == "Add") { found.type = "Remove"; - this.list[foundIndex] = found; } else { - this.list = this.list.filter(list => list.lotID != lotID); + this.list.push({ type: "Remove", lot, devices: deviceListID}); } - } else { - this.list.push({ type: "Remove", lot, devices: deviceList, isFromIndeterminate: indeterminate }); - } if (this.list.length > 0) { document.getElementById("ApplyDeviceLots").classList.remove("disabled"); From 4aa024b04191dc682ebf63f6751edf9d6abc4454 Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 16:58:44 +0200 Subject: [PATCH 08/30] checkpoint: add confirmation dialog --- ereuse_devicehub/static/js/main_inventory.js | 52 ++++++++++++++++--- .../inventory/alert_lots_changes.html | 19 +++++++ .../templates/inventory/device_list.html | 3 +- 3 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 ereuse_devicehub/templates/inventory/alert_lots_changes.html diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index 6cb71534..69b09138 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -276,6 +276,7 @@ async function processSelectedDevices() { this.list = []; } }) + $("#confirmLotsModal").modal("hide"); // Hide dialog when click "Save changes" document.getElementById("dropDownLotsSelector").classList.remove("show"); } @@ -302,10 +303,9 @@ async function processSelectedDevices() { /** * Generates a list item with a correspondient checkbox state - * @param {String} lotID - * @param {String} lotName - * @param {Array} selectedDevicesIDs - * @param {HTMLElement} target + * @param {Object} lot Lot model server + * @param {HTMLElement} elementTarget + * @param {Array} actions */ function templateLot(lot, elementTarget, actions) { elementTarget.innerHTML = "" @@ -357,7 +357,48 @@ async function processSelectedDevices() { if (eventClickActions) { document.getElementById("ApplyDeviceLots").removeEventListener(eventClickActions); } - eventClickActions = document.getElementById("ApplyDeviceLots").addEventListener("click", () => actions.doActions()); + eventClickActions = document.getElementById("ApplyDeviceLots").addEventListener("click", () => { + const modal = $("#confirmLotsModal") + modal.modal({ keyboard: false }) + + let list_changes_html = ""; + // {type: ["Remove" | "Add"], "LotID": string, "devices": number[]} + actions.list.forEach(action => { + let type; + let devices; + if (action.type == "Add") { + type = "success"; + devices = action.devices.filter(dev => !action.lot.devices.includes(dev)) // Only show affected devices + } else { + type = "danger"; + devices = action.devices.filter(dev => action.lot.devices.includes(dev)) // Only show affected devices + } + list_changes_html += ` +
    +
    ${action.lot.name}
    +
    +

    + ${devices.map(item => { + const dhid = $(".deviceSelect").filter(`[data=${item}]`)[0].attributes["data-device-dhid"].value; + const name = $(".deviceSelect").filter(`[data=${item}]`)[0].attributes["data-device-vname"].value; + return `${dhid}` + }).join(" ")} +

    +
    +
    `; + }) + + modal.find(".modal-body").html(list_changes_html) + + const el = document.getElementById("SaveAllActions") + const elClone = el.cloneNode(true); + el.parentNode.replaceChild(elClone, el); + elClone.addEventListener("click", () => actions.doActions()) + + modal.modal("show") + + // actions.doActions(); + }); document.getElementById("ApplyDeviceLots").classList.add("disabled"); try { @@ -385,7 +426,6 @@ async function processSelectedDevices() { return lot; }) - let lotsList = []; lotsList.push(lots.filter(lot => lot.state == "true").sort((a,b) => a.name.localeCompare(b.name))); lotsList.push(lots.filter(lot => lot.state == "indetermined").sort((a,b) => a.name.localeCompare(b.name))); diff --git a/ereuse_devicehub/templates/inventory/alert_lots_changes.html b/ereuse_devicehub/templates/inventory/alert_lots_changes.html new file mode 100644 index 00000000..fa2613c9 --- /dev/null +++ b/ereuse_devicehub/templates/inventory/alert_lots_changes.html @@ -0,0 +1,19 @@ + + \ No newline at end of file diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index 827c3b68..79a25330 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -329,7 +329,7 @@