From patchwork Tue Aug 17 21:33:50 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Raxel Gutierrez X-Patchwork-Id: 1517850 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=google.com header.i=@google.com header.a=rsa-sha256 header.s=20161025 header.b=X9tvUQrI; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Gq4733Zg4z9sW8 for ; Wed, 18 Aug 2021 07:34:39 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4Gq4725NvZz3bc4 for ; Wed, 18 Aug 2021 07:34:38 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=google.com header.i=@google.com header.a=rsa-sha256 header.s=20161025 header.b=X9tvUQrI; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=flex--raxel.bounces.google.com (client-ip=2607:f8b0:4864:20::74a; helo=mail-qk1-x74a.google.com; envelope-from=3zcscyqukctsoxubidlldib.zljmxqzetlohifpqp.lwixyp.lod@flex--raxel.bounces.google.com; receiver=) Authentication-Results: lists.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=google.com header.i=@google.com header.a=rsa-sha256 header.s=20161025 header.b=X9tvUQrI; dkim-atps=neutral Received: from mail-qk1-x74a.google.com (mail-qk1-x74a.google.com [IPv6:2607:f8b0:4864:20::74a]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4Gq46t5RJfz2yZd for ; Wed, 18 Aug 2021 07:34:30 +1000 (AEST) Received: by mail-qk1-x74a.google.com with SMTP id l26-20020a05620a0c1ab02903ca414e2173so206612qki.20 for ; Tue, 17 Aug 2021 14:34:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=fnXg1io4FbspISy7PDPPIKGBYJP/fljoV4UVYGsaQEQ=; b=X9tvUQrI+s5UCzAqrA2gi/R8edcqJI8V2ypAPjJDomhEY7y8lrKgMCJk94+a8ruRpv wxZf835MaJ7GX3cTIHQPbfOS/XDTeySKtvDM7xI4SKrCWDbTcACYIgr1ZNlwE+d8OA6q q1PGxXQzdV7RP3fDf1Jpl9qogXVF9XwZ/dWnXuL73oKDrX3uF2nwjBj+VTi7hMPAtLmq Y4qWhNpr97GS/JDhHpN3M/aCo6ncGD4zDW7u5Ahbqf+XSxbgOdPETaEbijNbdUtPJEXv VvcSBeQ4hMhYHjMaqozP5IwkPKQRrwBPB7TLSwNVSa3gpm5pUgOuaRS/FDZ1wacB0r6w zd9w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=fnXg1io4FbspISy7PDPPIKGBYJP/fljoV4UVYGsaQEQ=; b=ByqsK0d0vY8qKJ+TOAVHAn+BXCyBy/Sahx53bjQPYObyQ1WDA67INfa7GWjBhVRzbU jdbfsIn89pBj0oPPrCBTLrEO2Ihp7TgSP9iiZB3uGaIJVbhw3zTsPh7JZp9FXs/EJSdu SvRm0XrIbsjAVVH96wr7DSOABGFkLrsH8VGDcaWLew6hBrxwlXAUmUiaysKIDXQhfypW KqJN6drUDpGjd5zHfXtKKUroymKqOEQd47HUMV6eeecT6i4ctYPAty79/4TPzDK2xc9z kCwEPp/YRRm9i9v7Rd7rfsQZnRzJSdWbTfQ4fGnFtncdh7qBcWQUUaruUj7xHWsJINpa nVBA== X-Gm-Message-State: AOAM530P9GDwjytiZdpYPNJe1P8ZlOyOp5rA9caQ1Urf7Z3fCuCFZb8o NgWFdEVz5CAmUh1DkC0sJ6ys8ptj/TdTeUtb4/XRLm8bdR+8wDZVFdK2F6/KKtHrQi75EZFmbhN QsiwBMvSX9YMSAgvmzl4qJG8A+WpAcUDR66VPZi0WKQk1bzOL3LdVDLXbz5H/FaXS X-Google-Smtp-Source: ABdhPJw+7L1zCNM23n7aOIQ1UdzddI4fdcAVSUndhFXg2+jWIa5Zawrw/NKQx2Th9xlIgR3GKYwscLxIYQ== X-Received: from raxel-pw.c.googlers.com ([fda3:e722:ac3:cc00:14:4d90:c0a8:2fda]) (user=raxel job=sendgmr) by 2002:a05:6214:965:: with SMTP id do5mr5520946qvb.0.1629236068203; Tue, 17 Aug 2021 14:34:28 -0700 (PDT) Date: Tue, 17 Aug 2021 21:33:50 +0000 In-Reply-To: <20210817213421.2963232-1-raxel@google.com> Message-Id: <20210817213421.2963232-2-raxel@google.com> Mime-Version: 1.0 References: <20210817213421.2963232-1-raxel@google.com> X-Mailer: git-send-email 2.33.0.rc1.237.g0d66db33f3-goog Subject: [PATCH v2 3/3] static: add rest.js to handle PATCH requests & respective responses From: Raxel Gutierrez To: patchwork@lists.ozlabs.org X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" Add `rest.js` file to have a utilities JavaScript module that can be reused by any Patchwork JS files that make requests to the REST API. In particular, this patch provides the following function: - `updateProperty`: make PATCH requests that partially update the fields of an object given it's REST API endpoint specified by the caller. Also, the caller can specify the field(s) to modify and the associated content for update messages in the case of both failed successful requests that render to the current webpage. The caller receives whether the request was successful or not. The `rest.js` module can be further expanded to support and provide functions that allow for other requests (e.g. GET, POST, PUT) to the REST API. Also, add functions that handle update & error messages for these PATCH requests that match the Django messages framework format and form error styling. These functions are internal to the module and aren't exposed outside of the `rest.js` file. Error and accompanying failed update messages are replaced by successful update messages and vice versa. Consecutive successful update messages add to a counter of updated objects. Consecutive error messages stack up. Signed-off-by: Raxel Gutierrez Reviewed-by: Daniel Axtens --- htdocs/js/rest.js | 110 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 htdocs/js/rest.js diff --git a/htdocs/js/rest.js b/htdocs/js/rest.js new file mode 100644 index 00000000..5be5682e --- /dev/null +++ b/htdocs/js/rest.js @@ -0,0 +1,110 @@ +/** + * Sends PATCH requests to update objects' properties using the REST API. + * @param {string} url Path to the REST API endpoint. + * @param {{field: string, value: string}} data + * field: Name of the property field to update. + * value: Value to update the property field to. + * @param {{none: string, some: string}} updateMessage + * none: Message when object update failed due to errors. + * some: Message when object update successful. + */ +async function updateProperty(url, data, updateMessage) { + const request = new Request(url, { + method: 'PATCH', + mode: "same-origin", + headers: { + "X-CSRFToken": Cookies.get("csrftoken"), + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + return await fetch(request) + .then(response => { + let message = updateMessage.some; + let success = true; + if (!response.ok) { + response.text().then(text => { + const responseObject = JSON.parse(text); + // Add error messages from response to page + for (const [key,value] of Object.entries(responseObject)) { + if (Array.isArray(value)) { + for (const error of value) { + handleErrorMessages(key + ": " + error); + } + } else { + handleErrorMessages(key + ": " + value); + } + } + }); + // Update message to be unsuccessful + message = updateMessage.none; + success = false; + } + handleUpdateMessages(message, success); + return response.ok + }).catch(error => { + handleErrorMessages(error); + }); +} + +/** + * Populates update messages for API REST requests. + * @param {string} messageContent Text for update message. + */ +function handleUpdateMessages(messageContent, success) { + // Replace error and failure update messages with success update message + const errorContainer = document.getElementById("errors"); + let messages = document.getElementsByClassName("messages")[0]; + if (success && errorContainer.firstChild != null) { + messages.replaceChildren(); + errorContainer.replaceChildren(); + } else if (!success) { + messages.replaceChildren(); + } + + // Increment counter of consecutive success update messages + if (messages.firstChild != null) { + const currentMessageCount = messages.firstChild.textContent.match('^([0-9]+)')[0]; + const newMessageCount = parseInt(currentMessageCount) + 1; + messageContent = newMessageCount + messageContent.slice(1); + } + + // Create new message element and add to list + const message = document.createElement("li"); + message.setAttribute("class", "message"); + if (success) { + message.classList.add("class", "success"); + } else { + message.classList.add("class", "error"); + } + message.textContent = messageContent; + messages.replaceChildren(...[message]); +} + +/** + * Populates error messages for API REST requests. + * @param {string} errorMessage Text for error message. + */ +function handleErrorMessages(errorMessage) { + let errorContainer = document.getElementById("errors"); + let errorHeader = document.getElementById("errors-header"); + let errorList = document.getElementsByClassName("error-list")[0]; + + // Create errors list and error header if container contents removed + if (errorList == null) { + errorHeader = document.createElement("p"); + errorList = document.createElement("ul"); + errorHeader.setAttribute("id", "errors-header") + errorHeader.textContent = "The following errors were encountered while making updates:"; + errorList.setAttribute("class", "error-list"); + errorContainer.appendChild(errorHeader); + errorContainer.appendChild(errorList); + } + + const error = document.createElement("li"); + error.textContent = errorMessage; + errorList.appendChild(error); +} + +export { updateProperty };