From b3d8c236ce96ea95765da578a5dc43f0f0512212 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Wed, 28 Jun 2023 02:14:35 +0200 Subject: [PATCH 01/16] Merge pull request #281 from ontoportal-lirmm/fix-bug-in-slices-links fix bug in slices links From c6e9ec505a610e2a19f195afcf0a0c58a51abd1b Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Fri, 10 Mar 2023 17:07:17 +0100 Subject: [PATCH 02/16] Merge pull request #212 from ontoportal-lirmm/fix/signup-fields-validation Fix: Signup page input fields validation From 2b1d9029091d5647cf2f7c1a46b30d379ff42ba8 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 16 Mar 2023 05:41:36 +0100 Subject: [PATCH 03/16] Merge pull request #214 from ontoportal-lirmm/feature/update-account-page-design Feature: Update submited ontologies and created projet sections design From 94da8e1020ac310b53081247ada27b89692695ff Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Mon, 8 May 2023 05:38:47 +0200 Subject: [PATCH 04/16] Merge pull request #237 from ontoportal-lirmm/feature/ecoportal/groups-categories-administration Feature: Add categories and groups administration --- app/assets/javascripts/bp_admin.js | 532 +++++++++++++++++- app/assets/stylesheets/admin.scss | 6 + .../admin/categories_controller.rb | 108 ++++ app/controllers/admin/groups_controller.rb | 107 ++++ app/views/admin/categories/_form.html.haml | 43 ++ app/views/admin/categories/edit.html.haml | 6 + app/views/admin/categories/new.html.haml | 6 + app/views/admin/groups/_form.html.haml | 43 ++ app/views/admin/groups/edit.html.haml | 6 + app/views/admin/groups/new.html.haml | 6 + app/views/admin/index.html.haml | 17 +- config/routes.rb | 2 + 12 files changed, 880 insertions(+), 2 deletions(-) create mode 100644 app/controllers/admin/categories_controller.rb create mode 100644 app/controllers/admin/groups_controller.rb create mode 100644 app/views/admin/categories/_form.html.haml create mode 100644 app/views/admin/categories/edit.html.haml create mode 100644 app/views/admin/categories/new.html.haml create mode 100644 app/views/admin/groups/_form.html.haml create mode 100644 app/views/admin/groups/edit.html.haml create mode 100644 app/views/admin/groups/new.html.haml diff --git a/app/assets/javascripts/bp_admin.js b/app/assets/javascripts/bp_admin.js index 63ac8ae9e0..744a297e9e 100644 --- a/app/assets/javascripts/bp_admin.js +++ b/app/assets/javascripts/bp_admin.js @@ -865,6 +865,220 @@ jQuery(".admin.index").ready(function() { }); // end: BUTTON onclick actions ----------------------------------- + + //============================================================== + // GROUPS MANAGEMENT + //============================================================== + displayGroups({}); + + // allow selecting of rows, except on link clicks + jQuery('#adminGroups tbody').on('click', 'tr', function(event) { + if (event.target.tagName.toLowerCase() != 'a') { + jQuery(this).toggleClass('selected'); + } + }); + + jQuery("div.groups_nav").html(` + + + New + + + Apply to Selected Rows:     + +      + + Go + + `); + + jQuery('#group_admin_action_submit').on('click', function(event) { + var action = jQuery('#group_admin_action').val(); + + if (!action) { + alertify.alert("Please choose an action to perform on the selected groups."); + return; + } + + switch(action) { + case "delete": + DeleteGroups.act(); + break; + } + }); + + jQuery(document).on("reveal.facebox", function (event) { + jQuery("#facebox form[data-collection=groups]").validate({ + errorClass: "groupFormError", + errorElement: "div", + rules: { + "group[name]": "required", + "group[acronym]": "required", + }, + messages: { + "group[name]": "Please enter a name", + "group[acronym]": "Please enter an acronym", + }, + }); + + }); + + jQuery('#group_new_action').on('click', function (event) { + jQuery.facebox({ + ajax: "/admin/groups/new?time=" + new Date().getTime() + }); + }); + + jQuery('#adminGroups').on('click', 'a.edit-group', function(event) { + jQuery.facebox({ + ajax: "/admin/groups/" + encodeURIComponent(event.target.dataset.groupName) + "/edit?time=" + new Date().getTime() + }); + }); + + + //============================================================== + // CATEGORIES MANAGEMENT + //============================================================== + displayCategories({}); + + // allow selecting of rows, except on link clicks + jQuery('#adminCategories tbody').on('click', 'tr', function(event) { + if (event.target.tagName.toLowerCase() != 'a') { + jQuery(this).toggleClass('selected'); + } + }); + + jQuery("div.categories_nav").html(` + + + New + + + Apply to Selected Rows:     + +      + + Go + + `); + + jQuery('#category_admin_action_submit').on('click', function(event) { + var action = jQuery('#category_admin_action').val(); + + if (!action) { + alertify.alert("Please choose an action to perform on the selected categories."); + return; + } + + switch(action) { + case "delete": + DeleteCategories.act(); + break; + } + }); + + jQuery(document).on("reveal.facebox", function (event) { + jQuery("#facebox form[data-collection=categories]").validate({ + errorClass: "categoryFormError", + errorElement: "div", + rules: { + "category[name]": "required", + "category[acronym]": "required", + }, + messages: { + "category[name]": "Please enter a name", + "category[acronym]": "Please enter an acronym", + }, + }); + + }); + + jQuery('#category_new_action').on('click', function (event) { + jQuery.facebox({ + ajax: "/admin/categories/new?time=" + new Date().getTime() + }); + }); + + jQuery('#adminCategories').on('click', 'a.edit-category', function(event) { + jQuery.facebox({ + ajax: "/admin/categories/" + encodeURIComponent(event.target.dataset.categoryName) + "/edit?time=" + new Date().getTime() + }); + }); + + //============================================================== + // MANAGEMENT COMMONS + //============================================================== + + jQuery(document).on("click", "#facebox a.dismiss-dialog", function (event) { + jQuery(document).trigger('close.facebox'); + }); + + jQuery(document).on('ajax:success', "#facebox form.admin-collection-form", (event, response, status, xhr) => { + jQuery(document).trigger('close.facebox'); + if (response && response.success) { + _showStatusMessages([response.success], [], [], false); + } + refreshCollection(event.target.dataset.collection); + }); + jQuery(document).on('ajax:error', "#facebox form.admin-collection-form", (event, xhr, status, error) => { + if (xhr.responseJSON) { + displayDialogErrorMessages(xhr.responseJSON) + } else { + displayDialogErrorMessages(status); + } + }); + + function refreshCollection(collectionName) { + switch (collectionName) { + case "groups": + displayGroups({}); + break; + case "categories": + displayCategories({}); + break; + default: + alertify.alert("Unable to refresh unknown collection '" + collectionName + "'"); + } + } + + function displayDialogErrorMessages(data, settings) { + settings ||= {} + + let append = settings.append || false; + + let errorListNode = jQuery("#facebox .alert-box ul"); + + if (!append) { + errorListNode.empty(); + } + + let messages = []; + if (typeof data == "string" || data instanceof String) { + messages.push(data) + } + if (typeof data == "object" && data.errors) { + messages.push.apply(messages, Object.values(data.errors)); + } + if (typeof data == "object" && data.status && data.status / 200 != 1) { + messages.push("Request error: " + data.statusText); + } + + for (let msg of messages) { + errorListNode.append(jQuery("
  • ").text(msg)) + } + + if (messages.length == 0) { + errorListNode.parents(".alert-box").hide(); + } else { + errorListNode.parents(".alert-box").show(); + } + } }); @@ -1040,4 +1254,320 @@ DeleteUsers.prototype.ajaxCall = function (username){ } DeleteUsers.act = function(user) { new DeleteUsers(user).ajaxCall(user); -}; \ No newline at end of file +}; + +/* groups part */ +function displayGroups(data, group) { + let ontTable = null; + let allRows + if (jQuery.fn.dataTable.isDataTable('#adminGroups')) { + ontTable = jQuery('#adminGroups').DataTable().ajax.reload(); + } else { + ontTable = jQuery("#adminGroups").DataTable({ + "ajax": { + "url": "/admin/groups", + "contentType": "application/json", + "dataSrc": function (json) { + return populateGroupRows(json); + } + }, + "rowCallback": function(row, data, index) { + var acronym = jQuery('td:nth-child(4)', row).text(); + + jQuery(row).attr("id", "tr_" + acronym); + }, + "initComplete": function(settings, json) { + }, + "columnDefs": [ + { + "targets": 0, + "searchable": true, + "title": "Name", + }, + { + "targets": 1, + "searchable": true, + "title": "Description", + }, + { + "targets": 2, + "searchable": true, + "title": "Created", + }, + { + "targets": 3, + "searchable": true, + "title": "Id", + }, + { + "targets": 4, + "searchable": false, + "orderable": false, + "title": "Actions", + "width": "210px" + } + ], + "autoWidth": false, + "lengthChange": false, + "searching": true, + "language": { + "search": "Filter: ", + "emptyTable": "No groups available" + }, + "info": true, + "paging": true, + "pageLength": 100, + "ordering": true, + "responsive": true, + "dom": '<"groups_nav"><"top"fi>rtip', + "stripeClasses": ["", "alt"], + }); + } + return ontTable; +} + +function populateGroupRows(data) { + let groups = data['groups']; + let allRows = groups.map(group => { + let name = group['name']; + let description = group['description'] + let created = group['created']; + let id = group['acronym']; + let actions = [ + 'Edit', + ] + return [name, description, created, id , actions.join('|')]; + }) + + return allRows; +} + +function DeleteGroups() { +} + +DeleteGroups.act = function(groupName) { + let group2delete = jQuery("#adminGroups tr.selected td:nth-child(4)").map(function(index, value) { return value.textContent.trim();}).toArray(); + let confirmMsg = "You are about to delete the following groups:
    " + group2delete.join(",") + "

    Should I proceed?"; + alertify.confirm(confirmMsg, (e) => { + if (e) { + _clearStatusMessages(); + let success = []; + let errors = []; + let notices = []; + let errorState = false; + let deferredObj = jQuery.Deferred(); + let initialDeferredObj = deferredObj; + + for (let group of group2delete) { + fun = () => { return jQuery.ajax("/admin/groups/" + encodeURIComponent(group), { + method: "DELETE", + dataType: "json", + success: function(data, msg) { + var reg = /\s*,\s*/g; + + if (data.errors) { + errorState = true; + errors.push.apply(errors, data.errors); + } + + if (data.success) { + success.push(data.success); + } + + if (data.notices) { + notices.push.apply(notices, data.notices); + } + + _showStatusMessages(success, errors, notices, false); + }, + error: function(request, textStatus, errorThrown) { + errorState = true; + errors.push(request.status + ": " + errorThrown); + _showStatusMessages(success, errors, notices, false); + }, + complete: function(request, textStatus) { + if (errorState) { + jQuery("#tr_" + group).removeClass('selected'); + } + } + }) + }; + deferredObj = deferredObj.then(fun, fun); + } + // hide progress message and deselect rows after ALL operations have completed + deferredObj.always(function () { + jQuery("#adminGroups").DataTable().ajax.reload(); + }); + + initialDeferredObj.resolve(); + } + }); +} +/* categories part */ +function displayCategories(data, category) { + let ontTable = null; + let allRows + if (jQuery.fn.dataTable.isDataTable('#adminCategories')) { + ontTable = jQuery('#adminCategories').DataTable().ajax.reload(); + } else { + ontTable = jQuery("#adminCategories").DataTable({ + "ajax": { + "url": "/admin/categories", + "contentType": "application/json", + "dataSrc": function (json) { + return populateCategoryRows(json); + } + }, + "rowCallback": function(row, data, index) { + var acronym = jQuery('td:nth-child(4)', row).text(); + + jQuery(row).attr("id", "tr_" + acronym); + }, + "initComplete": function(settings, json) { + }, + "columnDefs": [ + { + "targets": 0, + "searchable": true, + "title": "Name", + }, + { + "targets": 1, + "searchable": true, + "title": "Description", + }, + { + "targets": 2, + "searchable": true, + "title": "Created", + }, + { + "targets": 3, + "searchable": true, + "title": "Id", + }, + { + "targets": 4, + "searchable": false, + "orderable": false, + "title": "Parent", + }, + { + "targets": 5, + "searchable": false, + "orderable": false, + "title": "Actions", + "width": "210px" + } + ], + "autoWidth": false, + "lengthChange": false, + "searching": true, + "language": { + "search": "Filter: ", + "emptyTable": "No categories available" + }, + "info": true, + "paging": true, + "pageLength": 100, + "ordering": true, + "responsive": true, + "dom": '<"categories_nav"><"top"fi>rtip', + "stripeClasses": ["", "alt"], + }); + } + return ontTable; +} + +function populateCategoryRows(data) { + let categories = data['categories']; + let allRows = categories.map(category => { + let name = category['name']; + let description = category['description'] + let created = category['created']; + let id = category['acronym']; + let parentCategory = category['parentCategory']; + let actions = [ + 'Edit', + ] + return [name, description, created, id , parentCategory, actions.join('|')]; + }) + + return allRows; +} + +function DeleteCategories() { +} + +DeleteCategories.act = function(groupName) { + let category2delete = jQuery("#adminCategories tr.selected td:nth-child(4)").map(function(index, value) { return value.textContent.trim();}).toArray(); + let confirmMsg = "You are about to delete the following categories:
    " + category2delete.join(",") + "

    Should I proceed?"; + alertify.confirm(confirmMsg, (e) => { + if (e) { + _clearStatusMessages(); + let success = []; + let errors = []; + let notices = []; + let errorState = false; + let deferredObj = jQuery.Deferred(); + let initialDeferredObj = deferredObj; + + for (let category of category2delete) { + fun = () => { return jQuery.ajax("/admin/categories/" + encodeURIComponent(category), { + method: "DELETE", + dataType: "json", + success: function(data, msg) { + var reg = /\s*,\s*/g; + + if (data.errors) { + errorState = true; + errors.push.apply(errors, data.errors); + } + + if (data.success) { + success.push(data.success); + } + + if (data.notices) { + notices.push.apply(notices, data.notices); + } + + _showStatusMessages(success, errors, notices, false); + }, + error: function(request, textStatus, errorThrown) { + errorState = true; + errors.push(request.status + ": " + errorThrown); + _showStatusMessages(success, errors, notices, false); + }, + complete: function(request, textStatus) { + if (errorState) { + jQuery("#tr_" + category).removeClass('selected'); + } + } + }) + }; + deferredObj = deferredObj.then(fun, fun); + } + // hide progress message and deselect rows after ALL operations have completed + deferredObj.always(function () { + jQuery("#adminCategories").DataTable().ajax.reload(); + }); + + initialDeferredObj.resolve(); + } + }); +} + +/***************************** + * COMMON FUNCTIONS + *****************************/ +function _clearStatusMessages() { + jQuery("#progress_message").hide(); + jQuery("#success_message").hide(); + jQuery("#error_message").hide(); + jQuery("#info_message").hide(); + jQuery("#progress_message").html(""); + jQuery("#success_message").html(""); + jQuery("#error_message").html(""); + jQuery("#info_message").html(""); +} \ No newline at end of file diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 181e46e113..ad58c2c449 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -16,6 +16,7 @@ .error { background: #ffecec image-url("error.png") no-repeat 10px 50%; border: 1px solid #f5aca6; + padding-left: 30px; } .success { @@ -139,3 +140,8 @@ table.dataTable tbody tr.selected { margin-right: 8px; } } + +div.groupFormError { + color: red; + padding-top: 3px; +} diff --git a/app/controllers/admin/categories_controller.rb b/app/controllers/admin/categories_controller.rb new file mode 100644 index 0000000000..2af3f9143d --- /dev/null +++ b/app/controllers/admin/categories_controller.rb @@ -0,0 +1,108 @@ +class Admin::CategoriesController < ApplicationController + + layout :determine_layout + before_action :unescape_id, only: [:edit, :show, :update, :destroy] + before_action :authorize_admin + + CATEGORIES_URL = "#{LinkedData::Client.settings.rest_url}/categories" + + def index + response = _categories + render :json => response + end + + def new + @category = LinkedData::Client::Models::Category.new + + respond_to do |format| + format.html { render "new", :layout => false } + end + end + + def edit + @category = LinkedData::Client::Models::Category.find_by_acronym(params[:id]).first + + respond_to do |format| + format.html { render "edit", :layout => false } + end + end + + def create + response = { errors: '', success: '' } + start = Time.now + begin + category = LinkedData::Client::Models::Category.new(values: category_params) + category_saved = category.save + if response_error?(category_saved) + response[:errors] = response_errors(category_saved) + else + response[:success] = "category successfully created in #{Time.now - start}s" + end + rescue Exception => e + response[:errors] = "Problem creating the category - #{e.message}" + end + render json: response, status: (response[:errors] == '' ? :created : :internal_server_error) + + end + + def update + response = { errors: '', success: ''} + start = Time.now + begin + category = LinkedData::Client::Models::Category.find_by_acronym(params[:id]).first + category.update_from_params(category_params) + category_update = category.update + + if response_error?(category_update) + response[:errors] = response_errors(category_update) + else + response[:success] = "category successfully updated in #{Time.now - start}s" + end + rescue Exception => e + response[:errors] = "Problem updating the category - #{e.message}" + end + render json: response, status: (response[:errors] == '' ? :ok : :internal_server_error) + end + + def destroy + response = { errors: '', success: ''} + start = Time.now + begin + category = LinkedData::Client::Models::Category.find_by_acronym(params[:id]).first + error_response = category.delete + + if response_error?(error_response) + response[:errors] = response_errors(error_response) + else + response[:success] = "category successfully deleted in #{Time.now - start}s" + end + rescue Exception => e + response[:errors] = "Problem deleting the category - #{e.message}" + end + render json: response, status: (response[:errors] == '' ? :ok : :internal_server_error) + end + + private + + def unescape_id + params[:id] = CGI.unescape(params[:id]) + end + + def category_params + params.require(:category).permit(:acronym, :name, :description, :parentCategory).to_h + end + + def _categories + response = { categories: Hash.new, errors: '', success: '' } + start = Time.now + begin + response[:categories] = JSON.parse(LinkedData::Client::HTTP.get(CATEGORIES_URL, { include: 'all' }, raw: true)) + + response[:success] = "categories successfully retrieved in #{Time.now - start}s" + LOG.add :debug, "Categories - retrieved #{response[:categories].length} groups in #{Time.now - start}s" + rescue Exception => e + response[:errors] = "Problem retrieving categories - #{e.message}" + end + response + end +end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb new file mode 100644 index 0000000000..842982957e --- /dev/null +++ b/app/controllers/admin/groups_controller.rb @@ -0,0 +1,107 @@ +class Admin::GroupsController < ApplicationController + + layout :determine_layout + before_action :unescape_id, only: [:edit, :show, :update, :destroy] + before_action :authorize_admin + + GROUPS_URL = "#{LinkedData::Client.settings.rest_url}/groups" + + def index + response = _groups + render :json => response + end + + def new + @group = LinkedData::Client::Models::Group.new + + respond_to do |format| + format.html { render "new", :layout => false } + end + end + + def edit + @group = LinkedData::Client::Models::Group.find_by_acronym(params[:id]).first + + respond_to do |format| + format.html { render "edit", :layout => false } + end + end + + def create + response = { errors: '', success: '' } + start = Time.now + begin + group = LinkedData::Client::Models::Group.new(values: group_params) + group_saved = group.save + if response_error?(group_saved) + response[:errors] = response_errors(group_saved) + else + response[:success] = "group successfully created in #{Time.now - start}s" + end + rescue Exception => e + response[:errors] = "Problem creating the group - #{e.message}" + end + render json: response, status: (response[:errors] == '' ? :created : :internal_server_error) + + end + + def update + response = { errors: '', success: ''} + start = Time.now + begin + group = LinkedData::Client::Models::Group.find_by_acronym(params[:id]).first + group.update_from_params(group_params) + group_updated = group.update + if response_error?(group_updated) + response[:errors] = response_errors(group_updated) + else + response[:success] = "group successfully updated in #{Time.now - start}s" + end + rescue Exception => e + response[:errors] = "Problem updating the group - #{e.message}" + end + render json: response, status: (response[:errors] == '' ? :ok : :internal_server_error) + end + + def destroy + response = { errors: '', success: ''} + start = Time.now + begin + group = LinkedData::Client::Models::Group.find_by_acronym(params[:id]).first + error_response = group.delete + + if response_error?(error_response) + response[:errors] = response_errors(error_response) + else + response[:success] = "group successfully deleted in #{Time.now - start}s" + end + rescue Exception => e + response[:errors] = "Problem deleting the group - #{e.message}" + end + render json: response, status: (response[:errors] == '' ? :ok : :internal_server_error) + end + + private + + def unescape_id + params[:id] = CGI.unescape(params[:id]) + end + + def group_params + params.require(:group).permit(:acronym, :name, :description).to_h() + end + + def _groups + response = { groups: Hash.new, errors: '', success: '' } + start = Time.now + begin + response[:groups] = JSON.parse(LinkedData::Client::HTTP.get(GROUPS_URL, { include: 'all' }, raw: true)) + + response[:success] = "groups successfully retrieved in #{Time.now - start}s" + LOG.add :debug, "Groups - retrieved #{response[:groups].length} groups in #{Time.now - start}s" + rescue Exception => e + response[:errors] = "Problem retrieving groups - #{e.message}" + end + response + end +end diff --git a/app/views/admin/categories/_form.html.haml b/app/views/admin/categories/_form.html.haml new file mode 100644 index 0000000000..1d72c63628 --- /dev/null +++ b/app/views/admin/categories/_form.html.haml @@ -0,0 +1,43 @@ +- new_record = @category.acronym.nil? +%div.alert-box.error{style: @errors.nil? ? "display: none" : nil } + %ul + - unless @errors.nil? + - for error in @errors + %li + = error +%div{:style => "width:500px"} + %span.asterik{:style => "float:right;"} * fields are required + %h3 #{title_text} + %table#category_form.form + %colgroup + %col + %col{style: "width: 100%"} + %tr + %th + Acronym: + %span.asterik * + %td.top + - if new_record + = f.text_field :acronym, class: "form-control" + - else + = f.text_field :acronym, class: "form-control", readonly: true + %tr + %th + Name: + %span.asterik * + %td.top + = f.text_field :name, class: "form-control" + %tr + %th + Description: + %td.top + = f.text_area :description, class: "form-control" + - unless new_record + %tr + %th + Created: + %td.top + = f.text_field :created, readonly: true, class: "form-control" + %div.mt-2 + = f.submit button_text, class: "btn btn-primary mr-sm-2 group-form-accept" + = link_to "Cancel", "javascript:;", class: "btn btn-primary dismiss-dialog" \ No newline at end of file diff --git a/app/views/admin/categories/edit.html.haml b/app/views/admin/categories/edit.html.haml new file mode 100644 index 0000000000..b3706314da --- /dev/null +++ b/app/views/admin/categories/edit.html.haml @@ -0,0 +1,6 @@ +- @title = "Edit category" + +%div + = form_for :category, url: admin_categories_path + "/" + escape(@category.acronym), method: "PATCH", remote: true, data: { collection: "categories"}, html: {class: "admin-collection-form" } do |f| + = render partial: "form", locals: {f: f, title_text: "Edit category", + button_text: "Edit category", button_class: "edit-category"} diff --git a/app/views/admin/categories/new.html.haml b/app/views/admin/categories/new.html.haml new file mode 100644 index 0000000000..4d3b3082d0 --- /dev/null +++ b/app/views/admin/categories/new.html.haml @@ -0,0 +1,6 @@ +- @title = "Create new category" + +%div + = form_for :category, url: admin_categories_path, method: "POST", remote: true, data: { collection: "categories"}, html: {class: "admin-collection-form" } do |f| + = render partial: "form", locals: {f: f, title_text: "Create category", + button_text: "Create category" } diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml new file mode 100644 index 0000000000..628f648aef --- /dev/null +++ b/app/views/admin/groups/_form.html.haml @@ -0,0 +1,43 @@ +- new_record = @group.acronym.nil? +%div.alert-box.error{style: @errors.nil? ? "display: none" : nil } + %ul + - unless @errors.nil? + - for error in @errors + %li + = error +%div{:style => "width:500px"} + %span.asterik{:style => "float:right;"} * fields are required + %h3 #{title_text} + %table#group_form.form + %colgroup + %col + %col{style: "width: 100%"} + %tr + %th + Acronym: + %span.asterik * + %td.top + - if new_record + = f.text_field :acronym, class: "form-control" + - else + = f.text_field :acronym, class: "form-control", readonly: true + %tr + %th + Name: + %span.asterik * + %td.top + = f.text_field :name, class: "form-control" + %tr + %th + Description: + %td.top + = f.text_area :description, class: "form-control" + - unless new_record + %tr + %th + Created: + %td.top + = f.text_field :created, readonly: true, class: "form-control" + %div.mt-2 + = f.submit button_text, class: "btn btn-primary mr-sm-2 group-form-accept" + = link_to "Cancel", "javascript:;", class: "btn btn-primary dismiss-dialog" \ No newline at end of file diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml new file mode 100644 index 0000000000..d51cf7123f --- /dev/null +++ b/app/views/admin/groups/edit.html.haml @@ -0,0 +1,6 @@ +- @title = "Edit group" + +%div + = form_for :group, url: admin_groups_path + "/" + escape(@group.acronym), method: "PATCH", remote: true, data: { collection: "groups"}, html: {class: "admin-collection-form" } do |f| + = render partial: "form", locals: {f: f, title_text: "Edit group", + button_text: "Edit group", button_class: "edit-group"} diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml new file mode 100644 index 0000000000..2a8bf5f425 --- /dev/null +++ b/app/views/admin/groups/new.html.haml @@ -0,0 +1,6 @@ +- @title = "Create new group" + +%div + = form_for :group, url: admin_groups_path, method: "POST", remote: true, data: { collection: "groups"}, html: {class: "admin-collection-form" } do |f| + = render partial: "form", locals: {f: f, title_text: "Create group", + button_text: "Create group" } diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index 0b796f2a10..a67c026743 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -1,4 +1,4 @@ -- @title = "BioPortal Administration" +- @title = "Administration" %div.row %div.col @@ -24,6 +24,10 @@ =link_to("Users", "#users", id: "users-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab", href: users_path() }, aria: { controls: "users", selected: "false" }) %li.nav-item =link_to("Metadata Administration", "#ontologies_metadata_curator", id: "ontologies_metadata_curator-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab"}, aria: { controls: "ontologies_metadata_curator", selected: "false" }) + %li.nav-item + =link_to("Groups", "#groups", id: "groups-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab", href: "groups" }, aria: { controls: "groups", selected: "false" }) + %li.nav-item + =link_to("Categories", "#categories", id: "categories-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab", href: "categories" }, aria: { controls: "categories", selected: "false" }) %div#adminTabContent.tab-content -# Site Administration tab @@ -103,6 +107,17 @@ %div.tab-pane.fade{id: "ontologies_metadata_curator", role: "tabpanel", aria: { labelledby: "ontologies_metadata_curator-admin-tab" }} = render partial: 'ontologies_metadata_curator/metadata_tab' + -# Groups tab + %div.tab-pane.fade{id: "groups", role: "tabpanel", aria: { labelledby: "groups-admin-tab" }} + %div.ontologies_list_container.mt-3 + %table#adminGroups.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} + + -# Categories tab + %div.tab-pane.fade{id: "categories", role: "tabpanel", aria: { labelledby: "categories-admin-tab" }} + %div.ontologies_list_container.mt-3 + %table#adminCategories.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} + + \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 057297ac71..81b907f21f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -50,6 +50,8 @@ namespace :admin do resources :licenses, only: [:index, :create, :new] + resources :groups, only: [:index, :create, :new, :edit, :update, :destroy] + resources :categories, only: [:index, :create, :new, :edit, :update, :destroy] end resources :subscriptions From bf9055de2077a89954c8c7fb2c141e4797f207eb Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 5 Sep 2023 04:31:10 +0200 Subject: [PATCH 05/16] Merge pull request #273 from ontoportal-lirmm/fix/bug-ANAEETHES-ontology Fix: Ontology description text display when it is a complex object not a string --- app/views/concepts/_details.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/concepts/_details.html.haml b/app/views/concepts/_details.html.haml index 56cea26439..2b225d586c 100644 --- a/app/views/concepts/_details.html.haml +++ b/app/views/concepts/_details.html.haml @@ -29,7 +29,7 @@ %tr %td{nowrap: ""} Definitions %td - %p= @concept.definition.join(" ") + = dispaly_complex_text(@concept.definition) - if @concept.obsolete? %tr %td{nowrap: ""} Obsolete From 3e711ffd869d231eb66e9259853fc98feeb28532 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Wed, 28 Jun 2023 01:58:44 +0200 Subject: [PATCH 06/16] Merge pull request #277 from ontoportal-lirmm/Fix-alphabetical-sorting Fix: Alphabetical sorting in the the collection members display --- app/views/concepts/_list.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/concepts/_list.html.haml b/app/views/concepts/_list.html.haml index 83a4a27d3d..6b67d002f2 100644 --- a/app/views/concepts/_list.html.haml +++ b/app/views/concepts/_list.html.haml @@ -3,7 +3,7 @@ next_url: concept_list_url(@page.nextPage, request_collection_id, @ontology.acronym), current_page: @page.page, next_page: @page.nextPage, auto_click: @auto_click) do |c| - - concepts = c.collection.sort_by{|concept| [concept.prefLabel || concept.id]} + - concepts = c.collection.sort_by{|concept| [concept.prefLabel.capitalize || concept.id]} - if concepts && !concepts.empty? = raw tree_link_to_concept(child: concepts.shift, ontology_acronym: @ontology.acronym, active_style: c.auto_click? ? 'active' : '') - concepts.each do |concept| From 112278272f4b8dd69d0f837ce76003e76ffa0c29 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 5 Sep 2023 04:33:49 +0200 Subject: [PATCH 07/16] Fix: bug of display list submitted ontologies of account setting (#286) * fix display list of views of ontologies in Submitted ontologies #270 --- app/controllers/home_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 2c7b8065c4..39c6432a4e 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -159,7 +159,7 @@ def account @user_ontologies = @user.customOntology @user_ontologies ||= [] - onts = LinkedData::Client::Models::Ontology.all + onts = LinkedData::Client::Models::Ontology.all(include_views: true); @admin_ontologies = onts.select { |o| o.administeredBy.include? @user.id } projects = LinkedData::Client::Models::Project.all From 0560f0a3aab55fe86ceafbdb1cc74c12e1e390eb Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 5 Sep 2023 04:34:39 +0200 Subject: [PATCH 08/16] fix bug of display text in alert when there is no collections (#285) fix the alert message text that is wrapping and overflowing out #266 --- app/components/alert_message_component.rb | 2 +- .../alert_message_component.html.haml | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/components/alert_message_component.rb b/app/components/alert_message_component.rb index f2175b7e00..c25efdf683 100644 --- a/app/components/alert_message_component.rb +++ b/app/components/alert_message_component.rb @@ -2,7 +2,7 @@ class AlertMessageComponent < ViewComponent::Base include Turbo::FramesHelper - def initialize(id: '', type: 'info') + def initialize(id: '', message: nil, type: 'info', closeable: true) @id = id @type = "alert-#{type}" end diff --git a/app/components/alert_message_component/alert_message_component.html.haml b/app/components/alert_message_component/alert_message_component.html.haml index 19c0e89f01..c32ec7d3c7 100644 --- a/app/components/alert_message_component/alert_message_component.html.haml +++ b/app/components/alert_message_component/alert_message_component.html.haml @@ -1,4 +1,8 @@ -.alert.alert-dismissible.fade.show{:role => "alert", class: "#{@type}"} - = content - %button.close{"aria-label": "Close", "data-dismiss": "alert", type: "button", style: "background: transparent"} - %span{"aria-hidden" => "true"} × \ No newline at end of file +.alert.alert-dismissible.fade.show{:role => "alert", class: "#{@type}", style: "text-align: left;white-space: normal;"} + = @message || content + + - if @closeable + %button.close{"aria-label": "Close", "data-dismiss": "alert", type: "button", style: "background: transparent"} + %span{"aria-hidden" => "true"} × + + \ No newline at end of file From ab762ccb538967c88a90f26c5c9a256c7837d9c3 Mon Sep 17 00:00:00 2001 From: SirineMhedhbi <31127782+SirineMhedhbi@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:49:51 +0200 Subject: [PATCH 09/16] Merge pull request #287 from ontoportal-lirmm/fix-bug-reset-password Fix: bug of reset password --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 319e2dc363..86d9a2e6ae 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -250,7 +250,7 @@ def validate_update(params) if params[:username].nil? || params[:username].length < 1 errors << "Last name field is required" end - if ((!params[:orcidId].match(/^\d{4}+(-\d{4})+$/)) || (params[:orcidId].length != 19)) && !(params[:orcidId].nil? || params[:orcidId].length < 1) + if params[:orcidId].present? && ((!params[:orcidId].match(/^\d{4}-\d{4}-\d{4}-\d{4}$/)) || (params[:orcidId].length != 19)) errors << "Please enter a valide orcide id" end if !params[:password].eql?(params[:password_confirmation]) From b3024d9f1e52e91245c65b1953f1175fdf26f64d Mon Sep 17 00:00:00 2001 From: SirineMhedhbi <31127782+SirineMhedhbi@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:25:20 +0200 Subject: [PATCH 10/16] Fix bug of select list view of ontology (#289) * Add Chosen.js functionality to fix bug of select views of ontolgy * Add Chosen.js functionality for list views ontologies * fix bug of update ontology where there is no view * Refactor code * Delete ponse = @ontology.update --- app/controllers/ontologies_controller.rb | 1 + app/views/shared/_ontology_picker_single.html.erb | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index 07fdfe451b..69a889d6b2 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -420,6 +420,7 @@ def update # Note: find_by_acronym includes ontology views @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology][:acronym] || params[:id]).first @ontology.update_from_params(ontology_params) + @ontology.viewOf = nil if @ontology.isView.eql? "0" error_response = @ontology.update if response_error?(error_response) @categories = LinkedData::Client::Models::Category.all diff --git a/app/views/shared/_ontology_picker_single.html.erb b/app/views/shared/_ontology_picker_single.html.erb index dbcb731252..53e2b4f9df 100644 --- a/app/views/shared/_ontology_picker_single.html.erb +++ b/app/views/shared/_ontology_picker_single.html.erb @@ -10,8 +10,17 @@ <%disabled ||= nil%> From 7c01732b7b4b4c4f65b27aa4a12637bd8c22f18c Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 5 Sep 2023 04:36:11 +0200 Subject: [PATCH 11/16] Merge pull request #295 from ontoportal-lirmm/Fix-display-the-list-of-concepts-in-a-collection-at-one-sight Fix: display the list of concepts in a collection at one sight --- app/controllers/ontologies_controller.rb | 2 +- app/helpers/concepts_helper.rb | 16 ++++++++++++++++ .../_concepts_browser.html.haml | 12 ++++++------ .../concepts_browsers/_concepts_list.html.haml | 2 +- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index 69a889d6b2..cc473e0b3b 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -4,7 +4,7 @@ class OntologiesController < ApplicationController include InstancesHelper include ActionView::Helpers::NumberHelper include OntologiesHelper - include SchemesHelper + include SchemesHelper, ConceptsHelper include CollectionsHelper include MappingStatistics diff --git a/app/helpers/concepts_helper.rb b/app/helpers/concepts_helper.rb index 9d438348cb..b56489dc01 100644 --- a/app/helpers/concepts_helper.rb +++ b/app/helpers/concepts_helper.rb @@ -26,6 +26,22 @@ def get_concept_id(params, concept, root) end end + def sub_menu_active?(section) + params["sub_menu"]&.eql? section + end + + def sub_menu_active_class(section) + "active show" if sub_menu_active?(section) + end + + def default_sub_menu? + !sub_menu_active?('list') && !sub_menu_active?('date') + end + + def default_sub_menu_class + "active show" if default_sub_menu? + end + def concept_label(ont_id, cls_id) @ontology = LinkedData::Client::Models::Ontology.find(ont_id) @ontology ||= LinkedData::Client::Models::Ontology.find_by_acronym(ont_id).first diff --git a/app/views/ontologies/concepts_browsers/_concepts_browser.html.haml b/app/views/ontologies/concepts_browsers/_concepts_browser.html.haml index 967c93e8cd..1c4c5128c2 100644 --- a/app/views/ontologies/concepts_browsers/_concepts_browser.html.haml +++ b/app/views/ontologies/concepts_browsers/_concepts_browser.html.haml @@ -1,18 +1,18 @@ %nav .nav.nav-tabs.text-center{:role => "tablist", style:"background-color: rgba(0, 0, 0, 0.03);"} - %a#concepts-tree-tab.nav-item.nav-link.active.flex-grow-1.border-radius-0{"data-toggle" => "tab", :href => "#concepts-tree-container", title: 'Hierarchy view', 'data-controller': "tooltip"} + %a#concepts-tree-tab.nav-item.nav-link.flex-grow-1.border-radius-0{"data-toggle" => "tab", :href => "#concepts-tree-container", title: 'Hierarchy view', 'data-controller': "tooltip", class: default_sub_menu_class} %img{src: asset_path('list-tree.svg') , style:'width: 25px; height: 25px'} - if skos? - %a#concepts-list-tab.nav-item.nav-link.flex-grow-1.border-radius-0{"data-toggle" => "tab", :href => "#concepts-list-container", title: 'Collection view', 'data-controller': "tooltip"} + %a#concepts-list-tab.nav-item.nav-link.flex-grow-1.border-radius-0{"data-toggle" => "tab", :href => "#concepts-list-container", title: 'Collection view', 'data-controller': "tooltip", class: sub_menu_active_class('list')} %i.fas.fa-list.text-dark{style:'font-size: 25px'} - %a#concepts-date-sort-tab.nav-item.nav-link.flex-grow-1.border-radius-0{"data-toggle" => "tab", :href => "#concepts-date-sort-container", title: 'Date view', 'data-controller': "tooltip"} + %a#concepts-date-sort-tab.nav-item.nav-link.flex-grow-1.border-radius-0{"data-toggle" => "tab", :href => "#concepts-date-sort-container", title: 'Date view', 'data-controller': "tooltip", class: sub_menu_active_class('date')} %i.far.fa-calendar-alt.text-dark{style:'font-size: 25px'} .tab-content.px-1.py-2 - #concepts-tree-container.tab-pane.fade.show.active + #concepts-tree-container.tab-pane.fade{class: default_sub_menu_class} = render partial: 'ontologies/concepts_browsers/concepts_tree' - if skos? - #concepts-list-container.tab-pane.fade + #concepts-list-container.tab-pane.fade{class: sub_menu_active_class('list')} = render partial: 'ontologies/concepts_browsers/concepts_list' - #concepts-date-sort-container.tab-pane.fade + #concepts-date-sort-container.tab-pane.fade{class: sub_menu_active_class('date')} = render partial: 'ontologies/concepts_browsers/concepts_date_sort' \ No newline at end of file diff --git a/app/views/ontologies/concepts_browsers/_concepts_list.html.haml b/app/views/ontologies/concepts_browsers/_concepts_list.html.haml index 07d48f6807..6fb3b288a1 100644 --- a/app/views/ontologies/concepts_browsers/_concepts_list.html.haml +++ b/app/views/ontologies/concepts_browsers/_concepts_list.html.haml @@ -13,5 +13,5 @@ data: {action: 'changed->turbo-frame#updateFrame'}} %div#sd_content.card.p-1{style: 'overflow-y: scroll; height: 60vh;'} - = render TurboFrameComponent.new(id: 'concepts_list_view-page-1', data: {'turbo-frame-target': 'frame'}) do + = render TurboFrameComponent.new(id: 'concepts_list_view-page-1', data: {'turbo-frame-target': 'frame'}, src:params[:concept_collections] ? "/ajax/classes/list?ontology_id=#{@ontology.acronym}&collection_id=#{params[:concept_collections]}" : '') do Please select a collection to display \ No newline at end of file From 8d66b4f35eb04c8333275bdc5703631c7b092553 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Sun, 6 Aug 2023 23:20:03 +0200 Subject: [PATCH 12/16] Merge pull request #291 from ontoportal-lirmm/Fix-remove-views-of-submitted-ontologies-in-account-setting Fix: Remove views ontologies in list of submitted ontologies From 69af592b57a6ca9283ebbc9a0e42b74d803a40d0 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 5 Sep 2023 04:50:09 +0200 Subject: [PATCH 13/16] Merge pull request #280 from ontoportal-lirmm/Fix-Ontology-style-text-display-when-it-is-uri Fix: ontology style text display when it is uri --- app/helpers/ontologies_helper.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/helpers/ontologies_helper.rb b/app/helpers/ontologies_helper.rb index 6e43ba8193..ecb06e34de 100644 --- a/app/helpers/ontologies_helper.rb +++ b/app/helpers/ontologies_helper.rb @@ -416,5 +416,16 @@ def sections_to_show end sections end + def dispaly_complex_text(definitions) + html = "" + definitions.each do |definition| + if definition.is_a?(String) + html += '

    ' + definition + '

    ' + elsif definition.respond_to?(:uri) && definition.uri + html += '

    ' + definition.uri + '

    ' + end + end + return html.html_safe + end end From 0121b164d42fc8ee3914ca79627518f6df86732d Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Wed, 28 Jun 2023 01:52:03 +0200 Subject: [PATCH 14/16] Merge pull request #254 from ontoportal-lirmm/feature/add-ontology-count-to-group-and-category Feature: add column ontologies count to categories and-groups admin page --- app/assets/javascripts/bp_admin.js | 28 +++++++++++++++---- app/components/select_input_component.rb | 3 +- .../select_input_component.html.haml | 2 +- .../admin/categories_controller.rb | 15 ++++++---- app/controllers/admin/groups_controller.rb | 8 ++++-- .../concerns/submission_updater.rb | 26 +++++++++++++++++ app/views/admin/categories/_form.html.haml | 14 +++++++--- app/views/admin/categories/edit.html.haml | 2 +- app/views/admin/groups/_form.html.haml | 13 ++++++--- app/views/admin/groups/edit.html.haml | 2 +- 10 files changed, 88 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/bp_admin.js b/app/assets/javascripts/bp_admin.js index 744a297e9e..7e29b80be9 100644 --- a/app/assets/javascripts/bp_admin.js +++ b/app/assets/javascripts/bp_admin.js @@ -1303,8 +1303,14 @@ function displayGroups(data, group) { "targets": 4, "searchable": false, "orderable": false, - "title": "Actions", - "width": "210px" + "title": "Count", + }, + { + "targets": 5, + "searchable": false, + "orderable": false, + "title": "Actions", + "width": "200px" } ], "autoWidth": false, @@ -1333,10 +1339,13 @@ function populateGroupRows(data) { let description = group['description'] let created = group['created']; let id = group['acronym']; + let nb = [ + ' ' + group['ontologies'].length + ' ', + ]; let actions = [ 'Edit', ] - return [name, description, created, id , actions.join('|')]; + return [name, description, created, id , nb, actions.join('|')]; }) return allRows; @@ -1456,8 +1465,14 @@ function displayCategories(data, category) { "targets": 5, "searchable": false, "orderable": false, + "title": "Count", + }, + { + "targets": 6, + "searchable": false, + "orderable": false, "title": "Actions", - "width": "210px" + "width": "250px" } ], "autoWidth": false, @@ -1487,10 +1502,13 @@ function populateCategoryRows(data) { let created = category['created']; let id = category['acronym']; let parentCategory = category['parentCategory']; + let nb = [ + ' ' + category['ontologies'].length + ' ', + ]; let actions = [ 'Edit', ] - return [name, description, created, id , parentCategory, actions.join('|')]; + return [name, description, created, id , parentCategory, nb , actions.join('|')]; }) return allRows; diff --git a/app/components/select_input_component.rb b/app/components/select_input_component.rb index 872f7f41ee..9bcfe5d1df 100644 --- a/app/components/select_input_component.rb +++ b/app/components/select_input_component.rb @@ -2,13 +2,14 @@ class SelectInputComponent < ViewComponent::Base - def initialize(id:, name:, values:, selected:, multiple: false) + def initialize(id:, name:, values:, selected:, multiple: false, open_to_add_values: false) super @id = id @name = name @values = values @selected = selected @multiple = multiple + @open_to_add_values = open_to_add_values end def options_values diff --git a/app/components/select_input_component/select_input_component.html.haml b/app/components/select_input_component/select_input_component.html.haml index 2a9f7692cd..a46f0ad299 100644 --- a/app/components/select_input_component/select_input_component.html.haml +++ b/app/components/select_input_component/select_input_component.html.haml @@ -1,7 +1,7 @@ %div{:data => { controller: "select-input", 'select-input': {'multiple-value': @multiple.to_s}}} = select_tag(@name, options_values, { multiple: @multiple, class: "form-control", id: "select_#{@id}", data: { action: "select-input#toggleOtherValue", "select-input-target": "selectedValues" } }) - %div.d-flex.mt-1 + %div.d-flex.mt-1{style: "display:#{@open_to_add_values ? 'none !important;' : 'block;'}"} = text_field_tag("add_#{@id}", nil, :style => "margin-right: 1em;width: 16em;display: none;", :placeholder => "Or provide the value", data: {action: "keydown.enter->select-input#addValue", "select-input-target": "inputValueField"}, class: 'metadataInput form-control form-control-sm') diff --git a/app/controllers/admin/categories_controller.rb b/app/controllers/admin/categories_controller.rb index 2af3f9143d..df6fd128c0 100644 --- a/app/controllers/admin/categories_controller.rb +++ b/app/controllers/admin/categories_controller.rb @@ -1,10 +1,12 @@ class Admin::CategoriesController < ApplicationController + include SubmissionUpdater layout :determine_layout before_action :unescape_id, only: [:edit, :show, :update, :destroy] before_action :authorize_admin CATEGORIES_URL = "#{LinkedData::Client.settings.rest_url}/categories" + ATTRIBUTE_TO_INCLUDE = 'name,acronym,created,description,parentCategory,ontologies' def index response = _categories @@ -20,8 +22,8 @@ def new end def edit - @category = LinkedData::Client::Models::Category.find_by_acronym(params[:id]).first - + @category = LinkedData::Client::Models::Category.find_by_acronym(params[:id], include:'name,acronym,created,description,parentCategory,ontologies' ).first + @ontologies_category = LinkedData::Client::Models::Ontology.all(include: 'acronym').map {|o|[o.acronym, o.id] } respond_to do |format| format.html { render "edit", :layout => false } end @@ -49,10 +51,11 @@ def update response = { errors: '', success: ''} start = Time.now begin - category = LinkedData::Client::Models::Category.find_by_acronym(params[:id]).first + category = LinkedData::Client::Models::Category.find_by_acronym(params[:id], include: ATTRIBUTE_TO_INCLUDE ).first + add_ontologies_to_object(category_params[:ontologies],category) if (category_params[:ontologies].size > 0 && category_params[:ontologies].first != '') + delete_ontologies_from_object(category_params[:ontologies],category.ontologies,category) category.update_from_params(category_params) category_update = category.update - if response_error?(category_update) response[:errors] = response_errors(category_update) else @@ -89,14 +92,14 @@ def unescape_id end def category_params - params.require(:category).permit(:acronym, :name, :description, :parentCategory).to_h + params.require(:category).permit(:acronym, :name, :description, :parentCategory, {ontologies:[]}).to_h end def _categories response = { categories: Hash.new, errors: '', success: '' } start = Time.now begin - response[:categories] = JSON.parse(LinkedData::Client::HTTP.get(CATEGORIES_URL, { include: 'all' }, raw: true)) + response[:categories] = JSON.parse(LinkedData::Client::HTTP.get(CATEGORIES_URL, { include: ATTRIBUTE_TO_INCLUDE }, raw: true)) response[:success] = "categories successfully retrieved in #{Time.now - start}s" LOG.add :debug, "Categories - retrieved #{response[:categories].length} groups in #{Time.now - start}s" diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 842982957e..17bc125111 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -1,4 +1,5 @@ class Admin::GroupsController < ApplicationController + include SubmissionUpdater layout :determine_layout before_action :unescape_id, only: [:edit, :show, :update, :destroy] @@ -21,7 +22,8 @@ def new def edit @group = LinkedData::Client::Models::Group.find_by_acronym(params[:id]).first - + @acronyms = @group.ontologies.map { |url| url.match(/\/([^\/]+)$/)[1] } + @ontologies_group = LinkedData::Client::Models::Ontology.all(include: 'acronym').map {|o|[o.acronym, o.id] } respond_to do |format| format.html { render "edit", :layout => false } end @@ -50,6 +52,8 @@ def update start = Time.now begin group = LinkedData::Client::Models::Group.find_by_acronym(params[:id]).first + add_ontologies_to_object(group_params[:ontologies],group) if (group_params[:ontologies].size > 0 && group_params[:ontologies].first != '') + delete_ontologies_from_object(group_params[:ontologies],group.ontologies,group) group.update_from_params(group_params) group_updated = group.update if response_error?(group_updated) @@ -88,7 +92,7 @@ def unescape_id end def group_params - params.require(:group).permit(:acronym, :name, :description).to_h() + params.require(:group).permit(:acronym, :name, :description, {ontologies:[]}).to_h() end def _groups diff --git a/app/controllers/concerns/submission_updater.rb b/app/controllers/concerns/submission_updater.rb index 3b1218fc33..5967e23d2a 100644 --- a/app/controllers/concerns/submission_updater.rb +++ b/app/controllers/concerns/submission_updater.rb @@ -24,6 +24,32 @@ def update_submission(new_submission_hash) @submission.update(cache_refresh_all: false) end + def add_ontologies_to_object(ontologies,object) + ontologies.each do |ont| + next if object.ontologies.include?(ont) + ontology = LinkedData::Client::Models::Ontology.find(ont) + if object.type.match(/\/([^\/]+)$/)[1] == 'Group' + ontology.group.push(object.id) + else + ontology.hasDomain.push(object.id) + end + ontology.update + end + end + + def delete_ontologies_from_object(new_ontologies,old_ontologies,object) + ontologies = old_ontologies - new_ontologies + ontologies.each do |ont| + ontology = LinkedData::Client::Models::Ontology.find(ont) + if object.type.match(/\/([^\/]+)$/)[1] == 'Group' + ontology.group.delete(object.id) + else + ontology.hasDomain.delete(object.id) + end + ontology.update + end + end + private def update_ontology_summary_only diff --git a/app/views/admin/categories/_form.html.haml b/app/views/admin/categories/_form.html.haml index 1d72c63628..34bd8e82ae 100644 --- a/app/views/admin/categories/_form.html.haml +++ b/app/views/admin/categories/_form.html.haml @@ -14,7 +14,7 @@ %col{style: "width: 100%"} %tr %th - Acronym: + Acronym %span.asterik * %td.top - if new_record @@ -23,21 +23,27 @@ = f.text_field :acronym, class: "form-control", readonly: true %tr %th - Name: + Name %span.asterik * %td.top = f.text_field :name, class: "form-control" %tr %th - Description: + Description %td.top = f.text_area :description, class: "form-control" - unless new_record %tr %th - Created: + Created %td.top = f.text_field :created, readonly: true, class: "form-control" + %tr + %th + Ontologies + %td.top + = render SelectInputComponent.new(id: "category_ontologies", name: "category[ontologies]", values: @ontologies_category, selected: @category.ontologies, multiple: true, open_to_add_values: true) + %div.d-flex.mt-1{:style => "display: none;"} %div.mt-2 = f.submit button_text, class: "btn btn-primary mr-sm-2 group-form-accept" = link_to "Cancel", "javascript:;", class: "btn btn-primary dismiss-dialog" \ No newline at end of file diff --git a/app/views/admin/categories/edit.html.haml b/app/views/admin/categories/edit.html.haml index b3706314da..b203c02ce3 100644 --- a/app/views/admin/categories/edit.html.haml +++ b/app/views/admin/categories/edit.html.haml @@ -3,4 +3,4 @@ %div = form_for :category, url: admin_categories_path + "/" + escape(@category.acronym), method: "PATCH", remote: true, data: { collection: "categories"}, html: {class: "admin-collection-form" } do |f| = render partial: "form", locals: {f: f, title_text: "Edit category", - button_text: "Edit category", button_class: "edit-category"} + button_text: "Save", button_class: "edit-category"} diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 628f648aef..ce5c958277 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -14,7 +14,7 @@ %col{style: "width: 100%"} %tr %th - Acronym: + Acronym %span.asterik * %td.top - if new_record @@ -23,21 +23,26 @@ = f.text_field :acronym, class: "form-control", readonly: true %tr %th - Name: + Name %span.asterik * %td.top = f.text_field :name, class: "form-control" %tr %th - Description: + Description %td.top = f.text_area :description, class: "form-control" - unless new_record %tr %th - Created: + Created %td.top = f.text_field :created, readonly: true, class: "form-control" + %tr + %th + Ontologies + %td.top + = render SelectInputComponent.new(id: "group_ontologies", name: "group[ontologies]", values: @ontologies_group , selected: @group.ontologies , multiple: true, open_to_add_values: true) %div.mt-2 = f.submit button_text, class: "btn btn-primary mr-sm-2 group-form-accept" = link_to "Cancel", "javascript:;", class: "btn btn-primary dismiss-dialog" \ No newline at end of file diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml index d51cf7123f..7269a374d0 100644 --- a/app/views/admin/groups/edit.html.haml +++ b/app/views/admin/groups/edit.html.haml @@ -3,4 +3,4 @@ %div = form_for :group, url: admin_groups_path + "/" + escape(@group.acronym), method: "PATCH", remote: true, data: { collection: "groups"}, html: {class: "admin-collection-form" } do |f| = render partial: "form", locals: {f: f, title_text: "Edit group", - button_text: "Edit group", button_class: "edit-group"} + button_text: "Save", button_class: "edit-group"} From f0f0ecc8ea441e5eafe70af367c276c4df73b1b4 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 5 Sep 2023 05:09:29 +0200 Subject: [PATCH 15/16] Merge pull request #302 from ontoportal-lirmm/feature/migrate-fair-score-js-code Feature: Migrate fair score JS code to Stimulus controllers --- Gemfile.lock | 3 + app/assets/javascripts/application.js | 1 - .../controllers/fair_score_home_controller.js | 21 +++++ .../fair_score_landscape_controller.js | 20 +++++ .../fair_score_summary_controller.js | 12 +++ app/javascript/controllers/index.js | 9 ++ .../mixins/useFairScore.js} | 86 +++---------------- app/views/home/_fair_score_home.html.haml | 2 +- .../landscape/_fair_score_landscape.html.haml | 2 +- app/views/ontologies/_fairs_score.html.haml | 2 +- 10 files changed, 82 insertions(+), 76 deletions(-) create mode 100644 app/javascript/controllers/fair_score_home_controller.js create mode 100644 app/javascript/controllers/fair_score_landscape_controller.js create mode 100644 app/javascript/controllers/fair_score_summary_controller.js rename app/{assets/javascripts/fair_score.js => javascript/mixins/useFairScore.js} (90%) diff --git a/Gemfile.lock b/Gemfile.lock index 77efa06d0d..f3dfc2d9a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -235,6 +235,8 @@ GEM netrc (0.11.0) newrelic_rpm (9.2.2) nio4r (2.5.9) + nokogiri (1.14.3-x86_64-darwin) + racc (~> 1.4) nokogiri (1.14.3-x86_64-linux) racc (~> 1.4) oj (3.14.3) @@ -418,6 +420,7 @@ GEM zeitwerk (2.6.8) PLATFORMS + x86_64-darwin-21 x86_64-linux DEPENDENCIES diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index f73139ead6..0d0fe33cb7 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -26,7 +26,6 @@ //= require bp_property_tree //= require concepts //= require home -//= require fair_score //= require_tree ./helpers //= require_tree ./components //= require ontologies diff --git a/app/javascript/controllers/fair_score_home_controller.js b/app/javascript/controllers/fair_score_home_controller.js new file mode 100644 index 0000000000..8b4883ad9e --- /dev/null +++ b/app/javascript/controllers/fair_score_home_controller.js @@ -0,0 +1,21 @@ +import { Controller } from "@hotwired/stimulus" +import {FairScorePrincipleBar, FairScoreCriteriaRadar, FairScoreChartContainer} from "../mixins/useFairScore"; +// Connects to data-controller="fair-score-home" +export default class extends Controller { + connect() { + let fairScoreBar = new FairScorePrincipleBar( 'ont-fair-scores-canvas') + let fairScoreRadar = new FairScoreCriteriaRadar( 'ont-fair-criteria-scores-canvas') + let fairContainer = new FairScoreChartContainer('fair-score-charts-container' , [ fairScoreRadar , fairScoreBar]) + let ontologies = jQuery("#ontology_ontologyId"); + + fairContainer.getFairScoreData("all") + ontologies.change( (e) => { + if(ontologies.val() !== null){ + fairContainer.getFairScoreData(ontologies.val().join(',')) + } else if(ontologies.val() === null){ + fairContainer.getFairScoreData("all") + } + e.preventDefault() + }) + } +} diff --git a/app/javascript/controllers/fair_score_landscape_controller.js b/app/javascript/controllers/fair_score_landscape_controller.js new file mode 100644 index 0000000000..e093f68ea1 --- /dev/null +++ b/app/javascript/controllers/fair_score_landscape_controller.js @@ -0,0 +1,20 @@ +import { Controller } from "@hotwired/stimulus" +import { FairScoreChartContainer, FairScoreCriteriaBar } from "../mixins/useFairScore"; +// Connects to data-controller="fair-score-landscape" +export default class extends Controller { + connect() { + let fairCriteriaBars = new FairScoreCriteriaBar('ont-fair-scores-criteria-bars-canvas') + let fairContainer = new FairScoreChartContainer('fair-score-charts-container' , [fairCriteriaBars]) + let ontologies = jQuery("#ontology_ontologyId"); + + fairContainer.getFairScoreData("all") + ontologies.change( (e) => { + if(ontologies.val() !== null){ + fairContainer.getFairScoreData(ontologies.val().join(',')) + } else if(ontologies.val() === null){ + fairContainer.getFairScoreData("all") + } + e.preventDefault() + }) + } +} diff --git a/app/javascript/controllers/fair_score_summary_controller.js b/app/javascript/controllers/fair_score_summary_controller.js new file mode 100644 index 0000000000..883e2cde62 --- /dev/null +++ b/app/javascript/controllers/fair_score_summary_controller.js @@ -0,0 +1,12 @@ +import { Controller } from "@hotwired/stimulus" +import {FairScorePrincipleBar, FairScoreCriteriaRadar, FairScoreChartContainer} from "../mixins/useFairScore"; +// Connects to data-controller="fair-score-summary" +export default class extends Controller { + connect() { + let fairScoreBar = new FairScorePrincipleBar( 'ont-fair-scores-canvas') + let fairScoreRadar = new FairScoreCriteriaRadar( 'ont-fair-criteria-scores-canvas') + let fairContainer = new FairScoreChartContainer('fair-score-charts-container' , [ fairScoreRadar , fairScoreBar]) + + fairContainer.getFairScoreData(window.location.pathname.split('/')[2]) + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 553504b0ac..83bcc541f1 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -13,6 +13,15 @@ application.register("class-search-auto-complete", ClassSearchAutoCompleteContro import ContainerSplitterController from "./container_splitter_controller" application.register("container-splitter", ContainerSplitterController) +import FairScoreHomeController from "./fair_score_home_controller" +application.register("fair-score-home", FairScoreHomeController) + +import FairScoreLandscapeController from "./fair_score_landscape_controller" +application.register("fair-score-landscape", FairScoreLandscapeController) + +import FairScoreSummaryController from "./fair_score_summary_controller" +application.register("fair-score-summary", FairScoreSummaryController) + import FormAutoCompleteController from "./form_auto_complete_controller" application.register("form-auto-complete", FormAutoCompleteController) diff --git a/app/assets/javascripts/fair_score.js b/app/javascript/mixins/useFairScore.js similarity index 90% rename from app/assets/javascripts/fair_score.js rename to app/javascript/mixins/useFairScore.js index c9186984d3..82aa516648 100644 --- a/app/assets/javascripts/fair_score.js +++ b/app/javascript/mixins/useFairScore.js @@ -1,7 +1,7 @@ - function round(val , base = 1){ return Math.floor( val * 100 * base) / 100 } + function getObtainedNotObtainedNA(scoresIn, portalMax , max , normalize = true){ const delimiter = (val) => (normalize ? val : 1) const notObtained = portalMax.map((x,i) => { @@ -391,17 +391,17 @@ class FairScoreCriteriaRadar extends FairScoreChart{ getFairScoreDataSet() { const scores = this.fairScoreChartCanvas.data('normalizedScores') - return [ - { - label: 'Fair score', - data: scores, - fill: true, - backgroundColor: 'rgba(151, 187, 205, 0.2)', - borderColor: 'rgba(151, 187, 205, 1)', - pointBorderColor: 'rgba(151, 187, 205, 1)', - pointBackgroundColor: 'rgba(151, 187, 205, 1)' - } - ] + return [ + { + label: 'Fair score', + data: scores, + fill: true, + backgroundColor: 'rgba(151, 187, 205, 0.2)', + borderColor: 'rgba(151, 187, 205, 1)', + pointBorderColor: 'rgba(151, 187, 205, 1)', + pointBackgroundColor: 'rgba(151, 187, 205, 1)' + } + ] } @@ -492,7 +492,7 @@ class FairScoreCriteriaBar extends FairScoreChart{ if (topOffset <= 0) topOffset = 0 - else if( (topOffset + tooltipEl.clientHeight) >= position.height) + else if( (topOffset + tooltipEl.clientHeight) >= position.height) topOffset = position.height - tooltipEl.clientHeight // Display, position, and set styles for font @@ -627,63 +627,5 @@ class FairScoreCriteriaBar extends FairScoreChart{ } } - -/* - For landscape - */ -jQuery('#fairness_assessment').ready(()=> { - let fairCriteriaBars = new FairScoreCriteriaBar('ont-fair-scores-criteria-bars-canvas') - let fairContainer = new FairScoreChartContainer('fair-score-charts-container' , [fairCriteriaBars]) - let ontologies = jQuery("#ontology_ontologyId"); - - fairContainer.getFairScoreData("all") - ontologies.change( (e) => { - if(ontologies.val() !== null){ - fairContainer.getFairScoreData(ontologies.val().join(',')) - } else if(ontologies.val() === null){ - fairContainer.getFairScoreData("all") - } - e.preventDefault() - }) - return false -}) - - - -/* - For the home - */ -jQuery('#fair-home').ready( function (e) { - - let fairScoreBar = new FairScorePrincipleBar( 'ont-fair-scores-canvas') - let fairScoreRadar = new FairScoreCriteriaRadar( 'ont-fair-criteria-scores-canvas') - let fairContainer = new FairScoreChartContainer('fair-score-charts-container' , [ fairScoreRadar , fairScoreBar]) - let ontologies = jQuery("#ontology_ontologyId"); - - fairContainer.getFairScoreData("all") - ontologies.change( (e) => { - if(ontologies.val() !== null){ - fairContainer.getFairScoreData(ontologies.val().join(',')) - } else if(ontologies.val() === null){ - fairContainer.getFairScoreData("all") - } - e.preventDefault() - }) - return false -}) - -/* - For the summary -*/ -jQuery('#fair-summary').ready( function (e) { - - let fairScoreBar = new FairScorePrincipleBar( 'ont-fair-scores-canvas') - let fairScoreRadar = new FairScoreCriteriaRadar( 'ont-fair-criteria-scores-canvas') - let fairContainer = new FairScoreChartContainer('fair-score-charts-container' , [ fairScoreRadar , fairScoreBar]) - - fairContainer.getFairScoreData(window.location.pathname.split('/')[2]) - - return false -}) - +export {round, getObtainedNotObtainedNA, FairScoreChartContainer, FairScoreChart, FairScorePrincipleBar, FairScoreCriteriaRadar, FairScoreCriteriaBar} diff --git a/app/views/home/_fair_score_home.html.haml b/app/views/home/_fair_score_home.html.haml index 9e83a221ba..988ef5cf02 100644 --- a/app/views/home/_fair_score_home.html.haml +++ b/app/views/home/_fair_score_home.html.haml @@ -1,4 +1,4 @@ -%div.col +%div.col{data:{controller:"fair-score-home"}} %div.card.mb-3 %div.card-header.d-flex.justify-content-between %span diff --git a/app/views/landscape/_fair_score_landscape.html.haml b/app/views/landscape/_fair_score_landscape.html.haml index 7abc353f87..adac535099 100644 --- a/app/views/landscape/_fair_score_landscape.html.haml +++ b/app/views/landscape/_fair_score_landscape.html.haml @@ -31,7 +31,7 @@ This interface shows how an ontology or a group responded successfully to O’FAIRe FAIRness assessment questions %div See details for each ontologies on the specific ontology summary pages - %div.card-body + %div.card-body{data:{controller:"fair-score-landscape"}} %div#fair-score-charts-container.row %div.col %span.text-secondary.mt-4 diff --git a/app/views/ontologies/_fairs_score.html.haml b/app/views/ontologies/_fairs_score.html.haml index e3c29dd32f..26a98885c3 100644 --- a/app/views/ontologies/_fairs_score.html.haml +++ b/app/views/ontologies/_fairs_score.html.haml @@ -12,7 +12,7 @@ class: "btn btn-primary w-100", data: { show_modal_title_value: "FAIRness assessment questions", show_modal_size_value: 'modal-xl' }, ) - %div.statistics_container + %div.statistics_container{data:{controller:"fair-score-summary"}} %div = render partial: "shared/fair_score_radar" , locals: {data: @fair_scores_data} %div From af7333fde62f6460a349c3b6140d4b1cd012458a Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Wed, 6 Sep 2023 19:27:39 +0200 Subject: [PATCH 16/16] fix generated bug after edit group and category (#334) --- app/controllers/admin/categories_controller.rb | 2 +- app/controllers/admin/groups_controller.rb | 2 +- app/controllers/concerns/submission_updater.rb | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/admin/categories_controller.rb b/app/controllers/admin/categories_controller.rb index df6fd128c0..8d52212bb1 100644 --- a/app/controllers/admin/categories_controller.rb +++ b/app/controllers/admin/categories_controller.rb @@ -52,7 +52,7 @@ def update start = Time.now begin category = LinkedData::Client::Models::Category.find_by_acronym(params[:id], include: ATTRIBUTE_TO_INCLUDE ).first - add_ontologies_to_object(category_params[:ontologies],category) if (category_params[:ontologies].size > 0 && category_params[:ontologies].first != '') + add_ontologies_to_object(category_params[:ontologies],category) if (category_params[:ontologies].present? && category_params[:ontologies].size > 0 && category_params[:ontologies].first != '') delete_ontologies_from_object(category_params[:ontologies],category.ontologies,category) category.update_from_params(category_params) category_update = category.update diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 17bc125111..3df27d2e02 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -52,7 +52,7 @@ def update start = Time.now begin group = LinkedData::Client::Models::Group.find_by_acronym(params[:id]).first - add_ontologies_to_object(group_params[:ontologies],group) if (group_params[:ontologies].size > 0 && group_params[:ontologies].first != '') + add_ontologies_to_object(group_params[:ontologies],group) if (group_params[:ontologies].present? && group_params[:ontologies].size > 0 && group_params[:ontologies].first != '') delete_ontologies_from_object(group_params[:ontologies],group.ontologies,group) group.update_from_params(group_params) group_updated = group.update diff --git a/app/controllers/concerns/submission_updater.rb b/app/controllers/concerns/submission_updater.rb index 5967e23d2a..2ab43c70a4 100644 --- a/app/controllers/concerns/submission_updater.rb +++ b/app/controllers/concerns/submission_updater.rb @@ -38,7 +38,8 @@ def add_ontologies_to_object(ontologies,object) end def delete_ontologies_from_object(new_ontologies,old_ontologies,object) - ontologies = old_ontologies - new_ontologies + new_ontologies = [] if new_ontologies.nil? + ontologies = old_ontologies - new_ontologies ontologies.each do |ont| ontology = LinkedData::Client::Models::Ontology.find(ont) if object.type.match(/\/([^\/]+)$/)[1] == 'Group'