From 4bb1863f5113e9a9d0a035c64484c2d9300e138e Mon Sep 17 00:00:00 2001
From: "James R. Griffin III" <1443986+jrgriffiniii@users.noreply.github.com>
Date: Mon, 18 Nov 2024 10:58:11 -0600
Subject: [PATCH] Porting group management functionality into a first-order
Class for import as a ES6 Module (#1986)
---
.circleci/config.yml | 4 +-
app/assets/config/manifest.js | 2 +-
app/assets/javascripts/edit_group_utils.js | 138 -------------
app/javascript/entrypoints/application.js | 11 +
.../entrypoints/edit_group_utils.js | 193 ++++++++++++++++++
app/views/groups/edit.html.erb | 1 -
package.json | 3 +
spec/system/group_edit_spec.rb | 4 +-
8 files changed, 211 insertions(+), 145 deletions(-)
delete mode 100644 app/assets/javascripts/edit_group_utils.js
create mode 100644 app/javascript/entrypoints/edit_group_utils.js
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 22fca716e..45e540da0 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -71,9 +71,7 @@ jobs:
- install_dependencies
- run:
name: Run eslint
- # Quote directory globs so not they are expanded by shell,
- # and every file will be checked, regardless of extension.
- command: yarn run eslint 'app/assets/javascripts/**' 'app/javascript/**'
+ command: yarn lint
test:
working_directory: *root
diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js
index 55d219081..4dd78ed8d 100644
--- a/app/assets/config/manifest.js
+++ b/app/assets/config/manifest.js
@@ -1,4 +1,4 @@
//= link_tree ../images
//= link_directory ../stylesheets .css
//= link edit_work_utils.js
-//= link edit_group_utils.js
+
diff --git a/app/assets/javascripts/edit_group_utils.js b/app/assets/javascripts/edit_group_utils.js
deleted file mode 100644
index ffa8ed955..000000000
--- a/app/assets/javascripts/edit_group_utils.js
+++ /dev/null
@@ -1,138 +0,0 @@
-$(() => {
- // Issues the HTTP DELETE to remove a user's access from a group
- function deleteUserFromGroup(el, url, uid) {
- $.ajax({
- type: 'DELETE',
- url: url.replace('uid-placeholder', uid),
- data: { authenticity_token: pdc.formAuthenticityToken },
- success() {
- // Remove the
for the user
- $(`.li-user-${uid}`).remove();
- },
- error(x) {
- alert(x.responseJSON.message);
- },
- });
- }
-
- // Adds the information for a given user to the lists of administrators/submitters
- // for the group.
- // `elList` is the reference to the HTML element that hosts the list.
- function addUserHtml(elList, uid, groupId, role, canDelete, isYou, isSuperAdmin) {
- const baseUserUrl = pdc.userPath;
- const showUserUrl = baseUserUrl.replace('user-placeholder', uid);
- let html = `- ${uid}`;
- if (isYou) {
- html += ' (you)';
- }
- if (isSuperAdmin) {
- html += ' ';
- }
- if (canDelete) {
- html += `
-
-
-
-
- `;
- }
- $(elList).append(html);
- }
-
- // Issues the HTTP POST to add a user to a group and updates the UI
- function addUserToGroup(url, elTxt, elError, elList, role) {
- const { groupId } = pdc;
- const uid = $(elTxt).val().trim();
- if (uid === '') {
- $(elError).text('Enter netid of user to add');
- return;
- }
-
- $.ajax({
- type: 'POST',
- url: url.replace('uid-placeholder', uid),
- data: { authenticity_token: pdc.formAuthenticityToken },
- success() {
- $(elTxt).val('');
- $(elError).text('');
- const canDelete = true;
- const isYou = false;
- const isSuperAdmin = false;
- addUserHtml(elList, uid, groupId, role, canDelete, isYou, isSuperAdmin);
- },
- error(x) {
- $(elError).text(x.responseJSON.message);
- },
- });
- }
-
- // Adds a submitter to the group
- $('#btn-add-submitter').on('click', () => {
- const url = pdc.addSubmitterUrl;
- addUserToGroup(url, '#submitter-uid-to-add', '#add-submitter-message', '#submitter-list', 'submit');
- $('#submitter-uid-to-add').focus();
- return false;
- });
-
- // Adds an administrator to the group
- $('#btn-add-admin').on('click', () => {
- const url = pdc.addAdminUrl;
- addUserToGroup(url, '#admin-uid-to-add', '#add-admin-message', '#curator-list', 'admin');
- $('#admin-uid-to-add').focus();
- return false;
- });
-
- if ($('#data-loaded').text() !== 'true') {
- // Displays the initial list of submitters.
- $('.submitter-data').each((ix, el) => {
- const elList = $('#submitter-list');
- const uid = $(el).data('uid');
- const groupId = $(el).data('groupId');
- const canDelete = $(el).data('canDelete');
- const isYou = $(el).data('you');
- const isSuperAdmin = false;
- addUserHtml(elList, uid, groupId, 'submit', canDelete, isYou, isSuperAdmin);
- });
-
- // Displays the initial list of curators.
- $('.curator-data').each((ix, el) => {
- const elList = $('#curator-list');
- const uid = $(el).data('uid');
- const groupId = $(el).data('groupId');
- const canDelete = $(el).data('canDelete');
- const isYou = $(el).data('you');
- addUserHtml(elList, uid, groupId, 'admin', canDelete, isYou, false);
- });
-
- // Displays the list of system administrators.
- $('.sysadmin-data').each((ix, el) => {
- const elList = $('#sysadmin-list');
- const uid = $(el).data('uid');
- const groupId = $(el).data('groupId');
- const isYou = $(el).data('you');
- addUserHtml(elList, uid, groupId, 'admin', false, isYou, true);
- });
-
- // Track that we have displayed this information. This prevents re-display when
- // user click the Back/Forward button on their browser.
- $('true').appendTo('body');
- }
-
- // Wire up the delete button for all users listed in the group.
- //
- // Notice the use of $(document).on("click", selector, ...) instead of the
- // typical $(selector).on("click", ...). This syntax is required so that
- // we can detect the click even on HTML elements _added on the fly_ which
- // is the case when a user adds a new submitter or admin to the group.
- // Reference: https://stackoverflow.com/a/17086311/446681
- $(document).on('click', '.delete-user-from-group', (el) => {
- const url = pdc.deleteUserFromGroupUrl;
- const uid = $(el.target).data('uid');
- const message = `Revoke access to user ${uid}`;
- if (window.confirm(message)) {
- deleteUserFromGroup(el, url, uid);
- }
- return false;
- });
-});
diff --git a/app/javascript/entrypoints/application.js b/app/javascript/entrypoints/application.js
index df77f5f6d..e618f5601 100644
--- a/app/javascript/entrypoints/application.js
+++ b/app/javascript/entrypoints/application.js
@@ -17,6 +17,7 @@ import './vendor/jquery-ui-triggeredAutocomplete';
import PdcUiLoader from './pdc/pdc_ui_loader.es6';
import WorksWizardPolicy from './works_wizard_policy';
import WorksWizardReview from './works_wizard_review';
+import EditGroupUtils from './edit_group_utils';
// If using a TypeScript entrypoint file:
// <%= vite_typescript_tag 'application' %>
@@ -35,17 +36,27 @@ import WorksWizardReview from './works_wizard_review';
// Import all channels.
import.meta.glob('../channels/*.js');
+// Load Rails JavaScript API
if (typeof (window._rails_loaded) === 'undefined' || window._rails_loaded == null || !window._rails_loaded) {
Rails.start();
}
+
+// Turbolinks
Turbolinks.start();
+// Integrate JavaScript API with Turbolinks
function ready() {
const loader = new PdcUiLoader();
loader.run();
WorksWizardPolicy.bind('#agreement');
WorksWizardReview.bind('#grant-button');
+
+ // This should be moved into the Rails object
+ if (typeof (pdc) !== 'undefined' && pdc != null && pdc) {
+ const groupUtils = new EditGroupUtils(window.jQuery);
+ groupUtils.bind(pdc);
+ }
}
// Must run the javascript loader on every page even if turbolinks loads it
diff --git a/app/javascript/entrypoints/edit_group_utils.js b/app/javascript/entrypoints/edit_group_utils.js
new file mode 100644
index 000000000..7d37ba51d
--- /dev/null
+++ b/app/javascript/entrypoints/edit_group_utils.js
@@ -0,0 +1,193 @@
+class EditGroupUtils {
+ constructor(jQuery) {
+ this.$ = jQuery;
+
+ this.pdc = null;
+ }
+
+ bind(pdc) {
+ this.pdc = pdc;
+
+ // Adds a submitter to the group
+ $('#btn-add-submitter').on('click', this.onAddSubmitter.bind(this));
+
+ // Adds an administrator to the group
+ $('#btn-add-admin').on('click', this.onAddAdmin.bind(this));
+
+ const $dataLoaded = this.$('#data-loaded');
+ const dataLoaded = $dataLoaded.text();
+
+ if (dataLoaded !== 'true') {
+ // Displays the initial list of submitters.
+ const $submitters = this.$('.submitter-data');
+ $submitters.each((ix, el) => {
+ const elList = this.$('#submitter-list');
+ const $el = this.$(el);
+ const uid = $el.data('uid');
+ const groupId = $el.data('groupId');
+ const canDelete = $el.data('canDelete');
+ const isYou = $el.data('you');
+ const isSuperAdmin = false;
+ this.addUserHtml(elList, uid, groupId, 'submit', canDelete, isYou, isSuperAdmin);
+ });
+
+ // Displays the initial list of curators.
+ const $curators = this.$('.curator-data');
+ $curators.each((ix, el) => {
+ const elList = $('#curator-list');
+ const $el = this.$(el);
+ const uid = $el.data('uid');
+ const groupId = $el.data('groupId');
+ const canDelete = $el.data('canDelete');
+ const isYou = $el.data('you');
+ this.addUserHtml(elList, uid, groupId, 'admin', canDelete, isYou, false);
+ });
+
+ // Displays the list of system administrators.
+ const $sysadmins = this.$('.sysadmin-data');
+ $sysadmins.each((ix, el) => {
+ const elList = $('#sysadmin-list');
+ const $el = this.$(el);
+ const uid = $el.data('uid');
+ const groupId = $el.data('groupId');
+ const isYou = $el.data('you');
+ this.addUserHtml(elList, uid, groupId, 'admin', false, isYou, true);
+ });
+
+ // Track that we have displayed this information. This prevents re-display when
+ // user click the Back/Forward button on their browser.
+ const $newDataLoaded = $('true');
+ $newDataLoaded.appendTo('body');
+ }
+
+ // Wire up the delete button for all users listed in the group.
+ //
+ // Notice the use of $(document).on("click", selector, ...) instead of the
+ // typical $(selector).on("click", ...). This syntax is required so that
+ // we can detect the click even on HTML elements _added on the fly_ which
+ // is the case when a user adds a new submitter or admin to the group.
+ // Reference: https://stackoverflow.com/a/17086311/446681
+ $(document).on('click', '.delete-user-from-group', (event) => {
+ const url = this.pdc.deleteUserFromGroupUrl;
+ const $target = this.$(event.target);
+ const uid = $target.data('uid');
+ const message = `Revoke access to user ${uid}`;
+ const confirmed = window.confirm(message);
+ if (confirmed) {
+ this.deleteUserFromGroup(event, url, uid);
+ }
+ return false;
+ });
+ }
+
+ onAddSubmitter() {
+ const url = this.pdc.addSubmitterUrl;
+ this.addUserToGroup(url, '#submitter-uid-to-add', '#add-submitter-message', '#submitter-list', 'submit');
+ $('#submitter-uid-to-add').focus();
+ return false;
+ }
+
+ onAddAdmin() {
+ const url = this.pdc.addAdminUrl;
+ this.addUserToGroup(url, '#admin-uid-to-add', '#add-admin-message', '#curator-list', 'admin');
+ $('#admin-uid-to-add').focus();
+ return false;
+ }
+
+ // Issues the HTTP DELETE to remove a user's access from a group
+ deleteUserFromGroup(event, url, uid) {
+ const { currentTarget } = event;
+ const $currentTarget = this.$(currentTarget);
+ this.$listItem = $currentTarget.parents(`.li-user-${uid}`);
+
+ const onSuccess = () => {
+ // Remove the
- for the user
+ this.$listItem.remove();
+ };
+
+ const onError = (error) => {
+ const { responseJSON } = error;
+ this.$elError.text(responseJSON.message);
+ };
+
+ this.$.ajax({
+ type: 'DELETE',
+ url: url.replace('uid-placeholder', uid),
+ data: { authenticity_token: this.pdc.formAuthenticityToken },
+ success: onSuccess.bind(this),
+ error: onError.bind(this),
+ });
+ }
+
+ // Adds the information for a given user to the lists of administrators/submitters
+ // for the group.
+ // `elList` is the reference to the
HTML element that hosts the list.
+ addUserHtml(elList, uid, groupId, role, canDelete, isYou, isSuperAdmin) {
+ const { userPath } = this.pdc;
+ const showUserUrl = userPath.replace('user-placeholder', uid);
+ let html = `- ${uid}`;
+ if (isYou) {
+ html += ' (you)';
+ }
+ if (isSuperAdmin) {
+ html += ' ';
+ }
+ if (canDelete) {
+ html += `
+
+
+
+
+ `;
+ }
+ const $elList = this.$(elList);
+ $elList.append(html);
+ }
+
+ // Issues the HTTP POST to add a user to a group and updates the UI
+ addUserToGroup(url, elTxt, elError, elList, role) {
+ const { groupId } = this.pdc;
+ this.groupId = groupId;
+
+ this.elList = elList;
+ this.role = role;
+
+ this.$elTxt = this.$(elTxt);
+ const elValue = this.$elTxt.val();
+ this.uid = elValue.trim();
+
+ this.$elError = this.$(elError);
+
+ if (this.uid === '') {
+ this.$elError.text('Please enter NetID of the user to add.');
+ return;
+ }
+
+ const onSuccess = () => {
+ this.$elTxt.val('');
+ this.$elError.text('');
+ const canDelete = true;
+ const isYou = false;
+ const isSuperAdmin = false;
+ // eslint-disable-next-line max-len
+ this.addUserHtml(this.elList, this.uid, this.groupId, this.role, canDelete, isYou, isSuperAdmin);
+ // eslint-enable-next-line max-len
+ };
+
+ const onError = (error) => {
+ const { responseJSON } = error;
+ this.$elError.text(responseJSON.message);
+ };
+
+ $.ajax({
+ type: 'POST',
+ url: url.replace('uid-placeholder', this.uid),
+ data: { authenticity_token: this.pdc.formAuthenticityToken },
+ success: onSuccess.bind(this),
+ error: onError.bind(this),
+ });
+ }
+}
+
+export default EditGroupUtils;
diff --git a/app/views/groups/edit.html.erb b/app/views/groups/edit.html.erb
index 9b6afbcaf..07dc0de66 100644
--- a/app/views/groups/edit.html.erb
+++ b/app/views/groups/edit.html.erb
@@ -100,4 +100,3 @@ pdc = {
userPath: "<%= user_path('user-placeholder') %>"
}
-<%= javascript_include_tag 'edit_group_utils' %>
diff --git a/package.json b/package.json
index 7f5a19757..0472464db 100644
--- a/package.json
+++ b/package.json
@@ -19,5 +19,8 @@
"postcss-import": "^15.1.0",
"vite": "^5.0.0",
"vite-plugin-ruby": "^5.1.0"
+ },
+ "scripts": {
+ "lint": "yarn run eslint 'app/assets/javascripts/**' 'app/javascript/**'"
}
}
diff --git a/spec/system/group_edit_spec.rb b/spec/system/group_edit_spec.rb
index 25093fd9c..df0be68e2 100644
--- a/spec/system/group_edit_spec.rb
+++ b/spec/system/group_edit_spec.rb
@@ -35,9 +35,9 @@
it "allows a group admin to add a submitter to the group", js: true do
sign_in group_admin_user
visit edit_group_path(group)
- fill_in "submitter-uid-to-add", with: "submiter123"
+ fill_in "submitter-uid-to-add", with: "submitter123"
click_on "Add Submitter"
- expect(page).to have_content "submiter123"
+ expect(page).to have_content "submitter123"
expect(page).not_to have_content "User has already been added"
end