projectdoc Toolbox

Adds a refactor menu and checks the current document for property issues.

Tags
Identifier
de.smartics.userscripts.confluence.projectdoc-refactor-document
Type
Repository
Since
1.0

The userscriptruns checks on the properties specified in the properties table of the projectdoc document. If the check finds issues, a report is rendered on the top of the current page.

It add a menu with actions to clean this document and its children.

Code

The code of the script for reference.

projectdoc-refactor-document.js
/*
 * Copyright 2019-2024 Kronseder & Reiner GmbH, smartics
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
"use strict";

AJS.toInit(function () {
  const logToConsole = true;

  if (logToConsole) {
    AJS.log("[projectdoc-refactor-document] Refactoring tools ...");
  }

  const $propertiesMarker = AJS.$(".projectdoc-document-element.properties");
  if (!$propertiesMarker.length) {
    if (logToConsole) AJS.log("[projectdoc-refactor-document] Not a projectdoc document. Quitting.");
    return;
  }

  const findMessageContainer = function () {
    let $messageContainer = AJS.$("#messageContainer");
    if ($messageContainer.length) {
      const $li = AJS.$("<li></li>");
      $messageContainer.append($li);
      return $li;
    }

    $messageContainer = AJS.$("#action-messages");
    if ($messageContainer.length) {
      return $messageContainer;
    }

    $messageContainer = AJS.$("#full-height-container");
    return $messageContainer;
  };

  const setTooltip = function ($element, text) {
    AJS.$($element).tooltip({
      title: function () {
        return text;
      }
    });
  };

  const showMessageIn = function ($messages, title, $content, type) {
    if ($messages.length) {
      const $message = AJS.$("<div></div>");
      $message.addClass("aui-message aui-message-" + type);
      const $title = AJS.$("<p></p>");
      $title.addClass("title");
      const $titleSpan = AJS.$("<strong></strong>");
      $titleSpan.text(title);
      $title.append($titleSpan);
      $message.append($title);
      $message.append($content);
      $messages.append($message);
    } else {
      AJS.log("Failed to locate element with " + dialogId + " to render messages");
    }
  };

  const showMessage = function (dialogId, title, $content, type) {
    const $messages = AJS.$("#" + dialogId);
    showMessageIn($messages, title, $content, type);
  };

  const addTableRow = function ($table, typeName, originalValue, cleanedValue) {
    if (originalValue) {
      const $tr = AJS.$('<tr></tr>');
      $tr.append(AJS.$('<th class="confluenceTh">' + typeName + '</th>'));

      const $tdOriginal = AJS.$('<td class="confluenceTd"></td>');
      $tdOriginal.text(originalValue);
      $tr.append($tdOriginal);

      const $tdCleaned = AJS.$('<td class="confluenceTd"></td>');
      $tdCleaned.text(cleanedValue);
      $tr.append($tdCleaned);
      $table.append($tr);
    }
  };

  const removeOldMessage = function ($messageContainer) {
    if ($messageContainer) {
      const $oldMessage = $messageContainer.find("#userscript-document-report");
      if ($oldMessage) {
        $oldMessage.remove();
      }
    }
  };

  const renderReport = function (baseUrl, title, report) {
    if (!report) {
      return;
    }

    const pageReports = report["page-reports"];
    const issueCount = report["issue-count"];

    if (issueCount <= 0) {
      if (pageReports) {
        if (logToConsole) AJS.log(AJS.format("[projectdoc-refactor-document] Checked {0} pages, found no properties with issues according to configured checks!", pageReports.length));
      } else {
        AJS.log("[projectdoc-refactor-document] Invalid report returned.");
      }
      return;
    }

    if (logToConsole) AJS.log(AJS.format("[projectdoc-refactor-document] Checked {0} pages, found {1} properties with issues according to configured checks!", pageReports.length, issueCount));
    const $messageContainer = findMessageContainer();

    if ($messageContainer.length) {
      const isOnePageReport = pageReports.length == 1;
      const $message = AJS.$('<div id="userscript-document-report" class="aui-message aui-message-error">\n' +
        '<p class="title">\n' +
        '  <strong>' + title + '</strong>\n' +
        '</p>\n' +
        // '<p>The following issues have been encountered.</p>\n' +
        // '<h3>Invalid Properties</h3>\n' +
        (isOnePageReport ? '<p>This page contains ' + issueCount + ' properties with issues.</p>\n' : '<p>Checked ' + pageReports.length + ' pages, found ' + issueCount + ' properties with issues according to configured checks.</p>\n') +
        '<div id="userscript-document-report-issues"></div>\n' +
        '</div>');

      const $divReport = AJS.$($message).find("#userscript-document-report-issues");
      if ($divReport.length) {
        AJS.$.each(pageReports, function (index, pageReport) {
          if (logToConsole) AJS.log("[projectdoc-refactor-document] Processing page report: " + JSON.stringify(pageReport));
          const $reportItem = AJS.$('<div></div>');
          if (!isOnePageReport) {
            $reportItem.append(AJS.$('<h3><a href="' + baseUrl + "/pages/viewpage.action?pageId=" + pageReport["page-id"] + '">' + pageReport["page-title"] + '</a></h3>'));
          } else {
            $reportItem.append("<div/>");
          }

          const $propertiesIssues = AJS.$('<div></div>');
          $propertiesIssues.append("<div/>");
          AJS.$.each(pageReport["issues"], function (index, propertyIssues) {
            if (isOnePageReport) {
              $propertiesIssues.append(AJS.$('<h3>' + propertyIssues["property-name"] + '</h3>'));
            } else {
              $propertiesIssues.append(AJS.$('<h4>' + propertyIssues["property-name"] + '</h4>'));
            }
            const $issuesTable = AJS.$('<table class="confluenceTable"></table>');
            addTableRow($issuesTable, "Name", propertyIssues["original-name"], propertyIssues["cleaned-name"]);
            addTableRow($issuesTable, "Value", propertyIssues["original-value"], propertyIssues["cleaned-value"]);
            addTableRow($issuesTable, "Controls", propertyIssues["original-controls"], propertyIssues["cleaned-controls"]);
            $propertiesIssues.append($issuesTable);
          });
          $reportItem.append($propertiesIssues);
          $divReport.append($reportItem);
        });

        $divReport.append(AJS.$('<p>For property values not to be altered by the document cleaning process, apply the <a href="https://www.smartics.eu/confluence/x/DoDsAg">preserve</a> property control.</p>'));

        const $mainButtons = AJS.$("<div class='buttons-container' style='margin-top: 1em;'></div>");
        $divReport.append($mainButtons);
        const $submit = AJS.$("<button class='aui-button aui-button-primary' id='userscript-document-report-clean-button'><span class=\"aui-icon aui-icon-small aui-iconfont-upload\">" + AJS.I18n.getText('de.smartics.userscripts.button.delete.icon') + "</span> Clean now</button>");
        setTooltip($submit, "Clean document, removing the reported issues.");
        AJS.$($submit).on('click', function (e) {
          e.preventDefault();
          removeOldMessage($messageContainer);

          const $spinner = AJS.$('<div id="userscript-document-report"><h4>Cleaning document</h4></div>');
          $spinner.append(AJS.$('<p>Removing issues from document properties ...</p>'));
          $spinner.append(AJS.$('<aui-spinner  size="large"></aui-spinner>'));
          $spinner.append(AJS.$('<p style="margin-bottom: 2em;"><em>(The page will be reloaded once the cleaning process is finished.)</em></p>'));
          $messageContainer.append($spinner);
          cleanDocumentAction();
        });
        $mainButtons.append($submit);

        removeOldMessage($messageContainer);
        $messageContainer.append($message);
      } else {
        AJS.log(AJS.format("[projectdoc-refactor-document] Failed to locate own list by ID #userscript-document-report-issues! Skipping report ..."));
      }
    } else {
      AJS.log(AJS.format("[projectdoc-refactor-document] Failed to locate message container on page with ID #messageContainer! Skipping report ..."));
    }
  };

  const cleanDocument = function (reportOnly, includeChildren, reload) {
    const pageId = AJS.Meta.get('page-id');

    const baseUrl = AJS.Meta.get('base-url');
    const serviceUrl = baseUrl + "/rest/projectdoc/1/service/cleanup?id-list=" + pageId + (reportOnly ? "&report-only=true" : "&comment=Document+clean+process") + (includeChildren ? "&include-children=true" : "");
    AJS.$.ajax({
      url: serviceUrl,
      type: "POST",
      dataType: 'json',
      contentType: "application/json",
      data: ""
    }).success(function (data) {
      if (logToConsole) AJS.log((reportOnly ? 'Checked' : 'Cleaned') + ' document ' + pageId + ' successfully!');

      if (logToConsole) AJS.log('Response: ' + JSON.stringify(data));
      if (reportOnly) {
        const report = data["report"];
        renderReport(baseUrl, "Property Cleaning Report", report);
      }

      if (reload) {
        location.reload();
      }
    }).error(function (jqXHR, textStatus) {
      AJS.log("[projectdoc-refactor-document] Error " + (reportOnly ? "checking" : "cleaning") + " document: " + jqXHR.status + " (" + textStatus + ")");
      if (!reportOnly) {
        const $messageContainer = findMessageContainer();
        removeOldMessage($messageContainer);
        showMessageIn($messageContainer, "Error", AJS.$("<p>Failed to clean document (" + jqXHR.status + " / " + textStatus + ").</p>"), "error");
      }
      // alert("Failed to clean document: " + jqXHR.status + " (" + textStatus + ")");
    });
  };

  const reindexCurrentSpace = function () {
    const spaceKey = AJS.Meta.get('space-key');

    const baseUrl = AJS.Meta.get('base-url');
    const serviceUrl = baseUrl + "/rest/projectdoc-internal/1/indexer/spaces?body-only=true&spaceKeys=" + spaceKey;
    AJS.$.ajax({
      url: serviceUrl,
      type: "POST",
      dataType: 'json',
      contentType: "application/json",
      data: "",
      statusCode: {
        202: function (xhr) {
          AJS.log("[projectdoc-refactor-document] Reindex space " + spaceKey + " successfully started: " + JSON.stringify(xhr));
          const message = "Successfully started reindexing current space (" + spaceKey + "). <p>Job: " + xhr.responseText + "</p>";
          AJS.flag({
            type: 'info',
            close: 'auto',
            body: message
          });
        }
      }
    }).success(function (data) {
      if (logToConsole) AJS.log("[projectdoc-refactor-document] Reindex space " + spaceKey + " successfully started: " + JSON.stringify(data));
    }).error(function (jqXHR, textStatus) {
      if (jqXHR.status != 202) {
        AJS.log("[projectdoc-refactor-document] Error reindexing space " + spaceKey + " (" + jqXHR.status + " / " + textStatus + ")!");
      }
    });
  };

  const cleanDocumentAction = function () {
    cleanDocument(false, false, true);
  }

  const reportAction = function () {
    cleanDocument(true, false, false);
  }

  const cleanDocumentsAction = function () {
    cleanDocument(false, true, true);
  }

  const createMenu = function () {
    const menuId = "refactor";
    const sectionId = "projectdoc-refactor-menu-clean";

    const $mainMenu = USERSCRIPT4C_MENU.createMenu(menuId, "Refactor");
    USERSCRIPT4C_MENU.registerMenu("view.menu", $mainMenu, "inspect");
    // In case you need to append the menu to an element identified by a selector, use this:
    //  USERSCRIPT4C_MENU.registerBySelector($mainMenu, "#my-id");
    USERSCRIPT4C_MENU.addSection(menuId, {
      id: sectionId,
      label: "Clean",
      weight: 10
    });

    USERSCRIPT4C_MENU.addMenuItem(sectionId, {
      id: "projectdoc-menu-refactor-item-document-clean-document",
      label: "Clean document",
      weight: "100"
    }, cleanDocumentAction);
    USERSCRIPT4C_MENU.addMenuItem(sectionId, {
      id: "projectdoc-menu-refactor-item-space-clean-documents",
      label: "Clean with child documents",
      weight: "200"
    }, cleanDocumentsAction);

    const reindexSectionId = "projectdoc-refactor-menu-reindex";
    USERSCRIPT4C_MENU.addSection(menuId, {
      id: reindexSectionId,
      label: "Reindex",
      weight: 20
    });
    USERSCRIPT4C_MENU.addMenuItem(reindexSectionId, {
      id: "projectdoc-menu-refactor-item-space-reindex",
      label: "Reindex current space",
      weight: "100"
    }, reindexCurrentSpace);

    return createMenu;
  }

  if (logToConsole) AJS.log("[projectdoc-refactor-document] Adding refactoring menu ...");
  createMenu();
  reportAction();
});

Details

More information on using this userscript.

Report

The report renders each document property with all issues concerning the name, value , and controls.

The issues found on the current page can be resolve immediately by clicking the "Clean now" button.

Menu

The refactor menu currently shows the following entries.

Related Scripts

NameShort Description
Hide projectdoc Tools
Removes projectdoc tools (blueprints and macros) from the current page.
Inspect Menu for projectdoc
Renders a menu with tools to inspect information from a projectdoc document, shown in the browser.
projectdoc Search Tool
Provides an interface to specify and launch queries for projectdoc documents.

Resources

More information on this topic is available by the following resources.

Document Cleanup
Runs a projectdoc cleanup on the referenced document.
preserve
Prevents cleanup services from applying their changes to name, value, and controls of a property.