From 9a9984ef2e2e92885843c4cc7dc67535de7196ce Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 23 May 2024 01:43:31 +0200 Subject: [PATCH 01/10] Merge to master: Release 2.8.0 - Search everywhere anything, Visualize mappings cloud and OntoPortal URIs (#564) * Fix: Annotator fast context special chars issues & statistics page syntax error (#561) * fix statistics page syntax error * update ontoportal instances home section title * fix fast context option in annotator page * remove special chars from annotator's input on submit to avoid errors * Feature: Add json button to search page (#562) * add json button ui to search page * extract json_link method from annotator controller to application controller * build the search page json link using json_link method * move the search json button down besides the advanced options button * remove this usage of api_params instead of direct params in search controller --------- Co-authored-by: Syphax Bouazzouni * Feature: Adding Content Finder Page (#511) * Adding Content Finder Page * use ontologies selector instead of the url input * Change the controller to add use the accept headers - use the Net::HTTP instead of Linked::Client::HTTP to use the accept header * use ontologies selector in the content finder page * use the rest_url helper instead of the global variable REST_URI * remove content finder link from the topnav * close the array items in navitems method that causes an error * use Faraday instead of Net::HTTP * Remove result in content_finder_controller and use this.element.textContent * use this.contentTarget.textContent instead element in the content finder * Add html view using the concept details component * use tool layout for the content finder page --------- Co-authored-by: Syphax Bouazzouni * Feature: Add ontology concepts exporters to diffrent formats (xml, ntriples, turtle) (#547) * add feature to display resouce content in different formats * Add download and copy buttons to the raw data section * Add css to highlight the content of turtle and ntriples * Add the copy functionality to the resource raw data for formts: json, xml, ntriples and turtle * Remove the clipboard component and change the HTML title to Raw Data * Add clipboard component to the resource-content * Use Net::HTTP instead of Linked::Client::HTTP to include accept header * Add the content formats for instances and Properties * Remove copy_concept_content method in clipboard component * Use the highlight-js in the metadata controller and remove content_finder_controller * Remove code duplication for content formats - add content_formats method in application helper and use it for concepts, properties and instances * Remove content finder controller from index.js * Small fix: remove a condition in metadata_downloader * Add content_formats method to ontologies_helper and remove it from application_helper * Use Faraday instead of Net::HTTP * Add helpers for content finder controller * change content finder route to /ontologies/:acronym/:id/serialize/:output_format * Add concept details component to schemes and collections and fix code * rename the metadata downloader controller and remove the result value * update the concept formats tab UI to use icons and open in modal * update the content finder page code to re-use concept format code * update the content formats icons to use the primary color of the portal * extract commun code in content finder and concept formats in a concern * extract rdf highlighter component from concept format and content finder * rename the concept formats file and action to content_serlizer * in content finder don't call the backend serializer if format is html * fix the content finder container width * clean unused css code for the content finder * extract link_to_format_modal function in the concept details component * abstract the usage of a highlighter from the dependency highlight.js * update instance details page to use the concept details component --------- Co-authored-by: Syphax bouazzouni * Feature: update admin page UI to use vertical tabs (#566) * create vertical_tabs component * use the vertical tabs component in the admin page * enable sort in the users admin table * fix the padding and marging of the metadata administration page * fix analytics admin page locals * fix an error in table component after rebasing the branch * remove an unused file * remove the paths escaping in the actions of user admin table * add again the link to API for the users admin page * implement for the vertical tabs the option to save the selected tab * Feature: Add indexation admin page (#567) * create vertical_tabs component * use the vertical tabs component in the admin page * add search admin page * translate the admin search tab to french * create number_input helper for Input::NumberComponent * make the regular button hide the loading state after request processed * remove an unused file * translate the search admin page to french and english * Feature: Update search input in homepage and topnav to use the indexed content (#505) * fix styling warning of flex "end" deperection * fix search input text overflowing out of the container * add search content by rdfs label in additoon of skos prefLabel * implement search redirection to the good page * use ontologies_content_autocomplete helper in homepage and topnav * add the search ontology content route * clean unused agent search code from this branch * move the code in search helper to a concern * re-integrate search input loader code to show a loader while searching * Feature: Improve Browse page performance (#572) * optimize fetched attributes in the browse page in API mode * update ontologies browse to use API or Index * add cache for the fair_score and submission endpoint * internationalize a sentence in the browse and ontologies selector views * fix browse hasFormality and isOfType filters * prfioritize API filtring over the index in the browse page * fix a bug with edit submission returning nil error on submission * Feature: Add OntoPortal tools age (#559) * add tools page * extract search input in the search page to re-use in url checker tool * internationlize the tools page * add tools link to the footer * fix the tools page block widths * add top padding to tools cards * Feature: Add concepts mappings button in search page results (#569) * add mappings to concepts search results * fix mapping count in concept page * add new more from this ontology icon * move concept_mappings_loader function to mappings helper * remove indesired comment from _concept_mappings.html.haml file * put url escape functions in a separate helper * Feature: Add search bars for the instances, properties, collections and schemes pages (#550) * remove forgotten binding.pry's from the code * add the option to the tree link component to open in modals * add type value to properties show view * create paginated search list helpers * update instances UI and add search bar * add a search bar to the properties show page * add a search bar for the collections show page * implement a search bar for the schemes show page * add search icon to the concepts search box * fix a typo in the search content concerns * move _instace_details file to _details * remove top padding from the search bar in properties and instances tree * implement selected in the paginated_list_component * use link_last_part in the tree link component to get the label from id * prioritize the label from the id in the ontology search content * remvoe top padding from search bar in the schemes and collection tree * translate missing word in the properties details view * clean the ontologies routes by defining a scope a removing duplicates * remove agent code not relevant in this branch * fix left padding of collections, schemes and properties views * fix tree autoclicking for collections, instances and schemes * move the html rendering from the search concern to helper * fix tests after changing the routes * translate missing words in the new ontologies section search bars * remove no more used instances js code * Feature: Update concept mappings table style (#574) * fix search page search button style * fix search page json button style * update concepts mappings table style * add back chip button overflow wraping in concepts mappings table * Fix: submission date and summary not ready (#576) * fix the copy of creation date on new submission * fix ontology viewer to hidde the data sections if any submission ready * prevent infinite redirection in the ontology controller show action * Feature: Improve browse page search ranking and performance (#575) * enhance the ontology search input to include ontology names * fix new instances of page rendering * add browse page search ranking if API mode * include views by default to be cached and filter them by request after * remove no more used instances of controller and helpers code * Feature: Add terms and conditions to signup and footer links (#579) * put terms and conditions check box in register page in french and english * add terms and conditions link to the config file * make the acceptation of the terms and conditions required for user registration * update the style of the register form errors * add terms and conditions links to the footer * add terms_and_conditions to login_flows_test * fix login flow integration test after adding terms and conditions * clean the config sample file --------- Co-authored-by: Syphax bouazzouni * Fix: new instance search feature to filter strictly only NamedIndividual (#581) * fix the paginated list selected value ti not select on each new page * fix instance search type filter to not include everything * Feature: Dynamic routes for ontologies content dereferencement (#568) * Add uri redirection for /ontologies/:acronym/:id to the appropriate page * small fixes: remove binding.pry and return resource_id in find_type_by_search * Add copy agroportal link functionality - this functionality is using the ClipboardComponent because it works the same but differ only in the content and icon - the clipboard component has been changed to accept title and icon * redirect to content finder page when no type is valid * Updated route to use redirect action instead of show_redirection * Use default icon and title arguments in the Clipboard Component * rename and internationalize clipboard component titles * small fix: remove % from svg icon * Add the copy title to the components section in en.yml and fr.yml * Show the generated uri when user hover over the copy link icon * Make the dynamic route content negotiable - based on the accept header we will - if "text/html" we will redirect to the agroportal page - else we will call the api and get the content serialized in the format specified and return it * update serialize content to return accept_header * translate copy_original_uri and copy_portal_uri to french * reuse search content concern in uri redirection concern * remove duplicate link_last_part method * update the ontology redirection action to use ontology URI if resource_id nil * update the content serializer to translate ntriples to an html table if asked for html format * in the ontology redirection by default redirect to content finder with in html format * remove the ":" from the copy the portal URI tooltip text * change the order of the icons in the link_with_action component --------- Co-authored-by: Syphax bouazzouni * Feature: Update mappings page (#538) * install D3 js and display a sample of ontology mappings bubbles * add zoom feature for mappings bubble view * fix bubble view style in mappings page * fix a scss selector in the annotator css that affects all a tags * mappings page layout * add ontologies select in mappings page * add upload new mappings in mappings page * add mappings table turbo frame * display the table view for ontology mappings * fix turbo frame issue in mappings page * Make the modal work in mappings table * improve mappings table view style * make mappings bubbles grow in a log way instead of a linear way * Highlighting mappings for the selected bubble * enable bubble deselection in mappings page * disable non highlighted bubbles in mappings page * show mappings page bubbles mappings in modal * make mappings page bubbles modal show correct values * customize mappings page bubbles tooltip * Link the table and the bubble view in mappings page * show highlighted bubbles with a gradient color related to the number of mappings * upload mappings in a separate tab * add concepts mappings to search page results * fix upload mappings tab style * display ontologies in ontologies selector * finalize ontologies selector design * put ontologies selector in the modal component * fix ontologies result display in ontologies selector * Make ontologies selector search bar work * show ontologies results count in ontologies selector * make groups filter work in ontologies selector * make filter by categories work in ontologies selector * add ontologies selector loading animation * make submission related filters work for ontologies selector * make select all work in ontologies selector * make retired ontologies and views filters work in ontologies selector * show tab selected checks count in ontologies selector * Add loading animation to ontologies selector * make ontology selection work in ontologies selector * make ontologies selector component * remove duplicated cross icon in ontologies selector * replace use cases of the old ontologies selector * convert ontologies selector ui component into a helper * replace 'selector' by 'ontologies_selector' in ontologies controller * enhance mapping page ontologies filtring style * add search page mappings count * add mappings bubble view to the ontology details page * make upload mappings work in mapping page * change seach results more ontologies button icon * fix concepts table mappings count * add a loading animation to the mappings bubble view * extract mappings page tab sections into partials * add internationalization to mappings page * clean mappings bubble view code * give more significant name for mappings ontologies table route * Clean mappings controller code * remove mappings search page part from main mappings PR * update new mapping form style * fix mappings section style and add search to its table in summary page * fix create new mapping for concept issue * fix upload mappings example style issue * update mappings ontologies table style * update the style the search filter of mappings table in mappings tab section * fix mappings bubble view modal link value * replace var by let in mappings visu stimulus controller * make hard coded mappings-bubble-view id by a dynamic * remove undesired #getLastPartOfUrl function from mappings vizu stimulus controller * extract #init_mappings_section_bubble_view() to a separate function in mappings visu stimulus controller * Add a default value for zoomRatio stimulus value in mappings visu controller * Add a default value for type stimulus value in mappings visu controller * refactor submit function in mappings visu stimulus controller * refactor select bubble method in mappings visu stimulus controller * put center_scroll method inside draw bubble method to not repeat code in mappings visu stimulus controller * refactor normalization ration function in mappings visu stimulus controller * use a better comprehensive helper for the empty modal in mappings page * add a legend for mappings bubble view * add back external mappings to mappings table view, and clean visu stimulus controller * move rest_url function to application_helper to be used by views * set table container box-sizing to not overflow if padding set * remove the new created to mappings actions and use the count action * remove the no more used loader action * move some mappings helpers and remove unused ones * use component helpers instead calling render & use reveal component * remove no more used partials in mappings * refactor and clean the mappings stimulus controller * extract from mappings stimulus the drawBubble logic in a file * fix mappings bubble view legend (bubble size) * fix search page results broken links (#583) * Fix: admin delete submission route (#585) * add back admin routes to fix delete submission * remove admin duplicated routes * Fix: Restore the new sample text of the annotator and recommender (#593) * restore the new sample text of the annotator and recommender * add french translation for annotator and recommender sample text * Fix: search results selected language (#595) * add language param to search results links * use helpers.request_lang instead of passing lang variable in search aggregator * make the selection of the closest preflabel to the query case unsensitive * Fix: new instance details page, properties selected property display and browse page ontology type filter (#596) * fix the auto selection of isOfType filter on browse page * fix search properties redirection to use propertyid not instanceid * fix admin ontologies visits sorting * fix ontologies browse page to show ontologies with no submissions * fix the sort by dates order in browse page * add apikey to the fairness cache key * show in the search box result "ontology view" for views * Feature: add a script to import or export CSV of the i18n locals (#597) * Fix: Remove duplicated ontology scheme rows (#584) * remove duplicated ontology scheme rows * move the concept details row duplication logic to filter_properties function * clean concept details component code * reject bottom set keys from leftover in concepts details component * fix skos-xl not been displayed in the raw data section * fix: summary page for ontologies with no submissions * Fix: Note thread show action (#606) * Fix undefined method error Resolves #308 * escape username when creating an ontology subscribtion * fix missing roots message for nil classes --------- Co-authored-by: Jennifer Vendetti * Fix: user reset password internationalisation issue (#605) * fix user reset password internationalisation issue * get user info in reset password * fix card message component image * fix: trim the copied text in the clipboard stimulus controller (#611) * Feature: Add recommender system tests (#599) * install Webmock gem * setup recommender page test * mock recommender api results in recommender system test * add test edit text area and submit in recommender test * disable webmock after the test ends in recommender test * make add comment/proposal buttons smaller * remove non related code to recommender tests PR * put the a correct recommendations fixture * check the number of results in recommender test * check the number of the highlighted annotations in recommender test * Disable webmock before each test to insure the non mocking of the different requests * use dynamic host and apikey in recommender webmocking * Feature: add htaccess URIs redirection modal help (#580) * Add uri redirection for /ontologies/:acronym/:id to the appropriate page * small fixes: remove binding.pry and return resource_id in find_type_by_search * Add copy agroportal link functionality - this functionality is using the ClipboardComponent because it works the same but differ only in the content and icon - the clipboard component has been changed to accept title and icon * redirect to content finder page when no type is valid * Updated route to use redirect action instead of show_redirection * Use default icon and title arguments in the Clipboard Component * rename and internationalize clipboard component titles * small fix: remove % from svg icon * Add the copy title to the components section in en.yml and fr.yml * Show the generated uri when user hover over the copy link icon * Make the dynamic route content negotiable - based on the accept header we will - if "text/html" we will redirect to the agroportal page - else we will call the api and get the content serialized in the format specified and return it * update serialize content to return accept_header * translate copy_original_uri and copy_portal_uri to french * reuse search content concern in uri redirection concern * remove duplicate link_last_part method * Add htaccess functionality * Add apache and nginx instruction for htaccess redirection * Clean te code in the view file * Add OntologiesRedirectionController - remove redirect and generate_htaccess from ontologies_controller - clean code the generate_htaccess method - add "ontology_portal_uri" in @identifiers * Regroupe routes * Make ontologies_redirection controller and refactor generate_htaccess code * Internationalization of redirection rewrite rules modal * Add note for url that has # * link contact support button to the feedback page * Change regex to redirect only url of type: /path/resource_id - for urls that has the # it will redirect to the ontology page * Add ontology redirection based on the accept header format * move the ontology redirection route to the bottom to have less priority * Change rewrite rules note using alert component * Add /ontologies/ACRONYM/download?format=FORMAT route * ontology redirection based on the accept header using /ontologies/acronym/download route * Add redirect assertion in ontologies controller test * Fix content serialization when calling /ontologies/:acronym/:id * create private function for accept header and remove ontology redirection * remove generate_rewrite_rules function * remove redirect assertion in ontologies controller test * change htaccess route to /ontologies/:acronym/htaccess * fix copy internal links in LinkFieldComponent * add raw to copy internal links in identifiers card * return all response and add text/n3 format in content serializer * escape id when redirecting to content_finder * choose the right result from the results of search content * add algorithm to choose the right accept header, not_acceptable if no format valid * add an additional security to the uri redirection to have an exact match * make the check resolvability icon clickable & redirect to the tool page * make the check resolvability extend to accept equivalent formats * move the redirection function from ontologies_controller to uri concern * fix check_resolvability_tool to redirect to the full url not only the path - the problem was because everytime it is redirecting to the uri.path and uri.path does not include the ?format parameter - add the octet-stream accept header in the accepted format for xml * add reference to the url of the original file when format not acceptable * put again the resolvability timeout to 5 secondes --------- Co-authored-by: Syphax bouazzouni * Fix: Release 2.8.0 grouped issues - Summary, Concept, Notes, Mappings pages (#613) * fix hidden multiple synonyms in concept details * remove unecessary search icon from concepts, instances and schemes trees search inputs * fix the style of add comment and add proposal buttons * include page url auto in the errors page feedback form * add the selected bubble to the mappings legend tooltip * update the fix of hidden synonyms to prevent possible breaks * fix concept details raw data values alignement to use divs breaking line * fix recommender result ontology link to redirect to UI not API * fix summary ofaire tooltip breaking to a new line the card header * fix opening the concept tree view link in a new tab showing only html * fix french dates internationalization * fix edit submission up front help message in english version * handle the case of ontologies returning an error when roots are called * add no properties alert message to the ontology viewer * prevent ontology show redirection if summaryOnly show summary directly * localize the mappings vizualization legend text --------- Co-authored-by: Syphax Bouazzouni * fix data tables warning issue (#624) --------- Co-authored-by: Bilel Kihal <61744974+Bilelkihal@users.noreply.github.com> Co-authored-by: Imad Bourouche Co-authored-by: Jennifer Vendetti --- Gemfile | 3 + Gemfile.lock | 27 +- app/assets/images/icons/copy_link.svg | 7 + app/assets/images/icons/json-ld-file.svg | 19 + app/assets/images/icons/ntriples-file.svg | 19 + app/assets/images/icons/rdf-xml-file.svg | 19 + app/assets/images/icons/three-dots.svg | 3 + app/assets/images/icons/turtle-file.svg | 19 + app/assets/javascripts/application.js | 4 - app/assets/javascripts/bp_admin.js | 20 +- .../components/data_table/data_table.js | 57 -- .../data_table/data_table_loader.js | 14 - .../components/instances/instances_table.js | 126 ---- app/assets/stylesheets/admin.scss | 2 - app/assets/stylesheets/annotator.scss | 2 +- .../stylesheets/application.css.scss.erb | 6 +- app/assets/stylesheets/components/index.scss | 3 +- .../components/progress_pages.scss | 2 +- .../stylesheets/components/search_input.scss | 45 +- app/assets/stylesheets/components/table.scss | 2 +- .../components/tabs_container.scss | 2 +- .../stylesheets/components/vertical_tabs.scss | 83 +++ app/assets/stylesheets/concepts.scss | 4 + app/assets/stylesheets/content_finder.scss | 42 ++ app/assets/stylesheets/mappings.scss | 282 +++++++ app/assets/stylesheets/ontologies.scss | 28 +- app/assets/stylesheets/recommender.scss | 4 +- app/assets/stylesheets/register.scss | 2 +- app/assets/stylesheets/search.scss | 6 +- app/assets/stylesheets/tools.scss | 13 + .../agent_search_input_component.html.haml | 2 +- .../regular_button_component.html.haml | 8 +- .../card_message_component.html.haml | 2 +- app/components/clipboard_component.rb | 4 +- .../clipboard_component.html.haml | 6 +- .../clipboard_component_controller.js | 4 +- app/components/concept_details_component.rb | 21 +- .../concept_details_component.html.haml | 19 +- app/components/display/header_component.rb | 4 +- .../display/info_tooltip_component.rb | 5 +- .../display/rdf_highlighter_component.rb | 9 + .../rdf_highlighter_component.html.haml | 6 + .../rdf_highlighter_component_controller.js} | 62 +- .../display/search_result_component.rb | 17 +- .../search_result_component.html.haml | 6 +- .../layout/vertical_tabs_component.rb | 14 + .../vertical_tabs_component.html.haml | 20 + app/components/link_field_component.rb | 13 +- .../search_input_component.html.haml | 11 +- .../search_input_component_controller.js | 25 +- .../table_component_controller.js | 13 +- app/components/tree_link_component.rb | 10 +- .../tree_link_component.html.haml | 9 +- app/controllers/admin/search_controller.rb | 89 +++ app/controllers/admin_controller.rb | 6 +- app/controllers/application_controller.rb | 30 +- app/controllers/collections_controller.rb | 40 +- app/controllers/concepts_controller.rb | 13 +- .../concerns/ontology_content_serializer.rb | 46 ++ app/controllers/concerns/ontology_updater.rb | 2 +- app/controllers/concerns/search_aggregator.rb | 8 +- app/controllers/concerns/search_content.rb | 181 +++++ app/controllers/concerns/submission_filter.rb | 209 ++++-- app/controllers/concerns/uri_redirection.rb | 96 +++ app/controllers/content_finder_controller.rb | 12 + app/controllers/errors_controller.rb | 2 + app/controllers/home_controller.rb | 27 +- app/controllers/instances_controller.rb | 78 +- app/controllers/login_controller.rb | 3 +- app/controllers/mappings_controller.rb | 54 +- app/controllers/notes_controller.rb | 3 +- app/controllers/ontologies_controller.rb | 129 ++-- .../ontologies_redirection_controller.rb | 93 +++ app/controllers/properties_controller.rb | 76 +- app/controllers/schemes_controller.rb | 36 +- app/controllers/search_controller.rb | 19 +- app/controllers/submissions_controller.rb | 2 +- app/controllers/subscriptions_controller.rb | 2 +- app/controllers/users_controller.rb | 6 +- app/helpers/admin_helper.rb | 2 +- app/helpers/application_helper.rb | 82 +- app/helpers/auto_complete_helper.rb | 26 + app/helpers/check_resolvability_helper.rb | 27 +- app/helpers/collections_helper.rb | 2 +- app/helpers/components_helper.rb | 110 ++- app/helpers/concepts_helper.rb | 6 +- app/helpers/fair_score_helper.rb | 28 +- app/helpers/inputs_helper.rb | 6 + app/helpers/instances_helper.rb | 25 +- app/helpers/mappings_helper.rb | 97 ++- app/helpers/metadata_helper.rb | 4 +- app/helpers/modal_helper.rb | 8 +- app/helpers/multi_languages_helper.rb | 50 +- app/helpers/ontologies_helper.rb | 86 ++- app/helpers/properties_helper.rb | 10 + app/helpers/schemes_helper.rb | 4 +- app/helpers/search_helper.rb | 51 ++ app/helpers/submission_inputs_helper.rb | 2 +- app/helpers/submissions_helper.rb | 3 +- app/helpers/urls_helper.rb | 9 + app/javascript/component_controllers/index.js | 3 + .../controllers/browse_filters_controller.js | 12 +- .../controllers/content_finder_controller.js | 105 +++ app/javascript/controllers/index.js | 7 +- .../mappings_visualization_controller.js | 259 +++++++ app/javascript/mixins/useHighLight.js | 88 +++ app/javascript/mixins/useMappingsBubbles.js | 110 +++ app/views/admin/_analytics.html.haml | 76 +- app/views/admin/_main.html.haml | 8 +- app/views/admin/index.html.haml | 38 +- app/views/admin/search/index.html.haml | 33 + app/views/admin/search/search.html.haml | 26 + app/views/admin/search/show.html.haml | 16 + app/views/check_resolvability/index.html.haml | 7 +- app/views/collections/_collection.html.haml | 4 +- app/views/collections/_list_view.html.haml | 31 +- app/views/concepts/_details.html.haml | 6 +- app/views/concepts/_show.html.haml | 12 +- app/views/content_finder/index.html.haml | 24 + .../errors/internal_server_error.html.haml | 2 +- app/views/errors/not_found.html.haml | 2 +- .../fair_score/_fair_service_header.html.haml | 2 +- app/views/home/index.html.haml | 2 +- app/views/home/tools.html.haml | 12 + app/views/instances/_details.html.haml | 27 + .../instances/_instance_details.html.haml | 19 - app/views/instances/_instances.html.haml | 30 +- app/views/layouts/_topnav.html.haml | 2 +- .../layouts/ontology_viewer/_header.html.haml | 5 +- app/views/layouts/tool.html.haml | 4 +- app/views/login/lost_password.html.haml | 2 +- .../mappings/_concept_mappings.html.haml | 10 +- .../_concept_mappings_selector.html.haml | 21 - app/views/mappings/_count.html.haml | 49 +- app/views/mappings/_form.html.haml | 2 +- app/views/mappings/_mapping_table.html.haml | 13 +- .../mappings/_ontology_mappings.html.haml | 20 - app/views/mappings/_show.html.haml | 10 +- .../mappings/bulk_loader/_loader.html.haml | 19 - app/views/mappings/index.html.haml | 33 +- .../tab_sections/_bubble_view.html.haml | 26 + .../tab_sections/_table_view.html.haml | 8 + .../tab_sections/_upload_mappings.html.haml | 16 + app/views/metadata_export/index.html.haml | 20 +- .../ontologies/browser/_ontologies.html.haml | 6 +- app/views/ontologies/browser/browse.html.haml | 15 +- .../_collections_picker.html.haml | 2 +- .../_concepts_browser.html.haml | 2 +- .../_concepts_list.html.haml | 2 +- .../ontologies/content_serializer.html.haml | 2 + app/views/ontologies/htaccess.html.haml | 68 ++ .../ontologies_selector_results.html.haml | 2 +- .../sections/_collections.html.haml | 10 +- .../ontologies/sections/_metadata.html.haml | 3 +- .../ontologies/sections/_schemes.html.haml | 10 +- .../ontologies/sections/properties.html.haml | 19 +- .../_metadata_tab.html.haml | 5 +- .../properties/_properties_tree.html.haml | 4 - app/views/properties/_show.html.haml | 7 +- app/views/recommender/index.html.haml | 4 +- app/views/schemes/_scheme.html.haml | 4 +- app/views/schemes/_tree_view.html.haml | 21 +- app/views/search/index.html.haml | 12 +- app/views/submissions/_form_content.html.haml | 2 +- app/views/users/_form.html.haml | 9 +- app/views/users/index.html.haml | 22 +- app/views/users/new.html.haml | 11 +- config/bioportal_config_env.rb.sample | 61 +- config/bioportal_config_test.rb | 2 +- config/i18n-tasks.yml | 8 + config/locales/en.yml | 141 +++- config/locales/fr.yml | 207 ++++-- config/routes.rb | 52 +- package.json | 1 + .../controllers/ontologies_controller_test.rb | 2 +- test/fixtures/recommender.yml | 698 ++++++++++++++++++ test/integration/login_flows_test.rb | 3 +- test/system/agent_flows_test.rb | 1 + test/system/login_flows_test.rb | 2 + test/system/recommender_page_test.rb | 118 +++ test/system/submission_flows_test.rb | 1 + test/test_helper.rb | 6 +- yarn.lock | 283 +++++++ 183 files changed, 4928 insertions(+), 1176 deletions(-) create mode 100644 app/assets/images/icons/copy_link.svg create mode 100644 app/assets/images/icons/json-ld-file.svg create mode 100644 app/assets/images/icons/ntriples-file.svg create mode 100644 app/assets/images/icons/rdf-xml-file.svg create mode 100644 app/assets/images/icons/three-dots.svg create mode 100644 app/assets/images/icons/turtle-file.svg delete mode 100644 app/assets/javascripts/components/data_table/data_table.js delete mode 100644 app/assets/javascripts/components/data_table/data_table_loader.js delete mode 100644 app/assets/javascripts/components/instances/instances_table.js create mode 100644 app/assets/stylesheets/components/vertical_tabs.scss create mode 100644 app/assets/stylesheets/content_finder.scss create mode 100644 app/assets/stylesheets/tools.scss create mode 100644 app/components/display/rdf_highlighter_component.rb create mode 100644 app/components/display/rdf_highlighter_component/rdf_highlighter_component.html.haml rename app/{javascript/controllers/metadata_downloader_controller.js => components/display/rdf_highlighter_component/rdf_highlighter_component_controller.js} (76%) create mode 100644 app/components/layout/vertical_tabs_component.rb create mode 100644 app/components/layout/vertical_tabs_component/vertical_tabs_component.html.haml create mode 100644 app/controllers/admin/search_controller.rb create mode 100644 app/controllers/concerns/ontology_content_serializer.rb create mode 100644 app/controllers/concerns/search_content.rb create mode 100644 app/controllers/concerns/uri_redirection.rb create mode 100644 app/controllers/content_finder_controller.rb create mode 100644 app/controllers/ontologies_redirection_controller.rb create mode 100644 app/helpers/auto_complete_helper.rb create mode 100644 app/helpers/search_helper.rb create mode 100644 app/helpers/urls_helper.rb create mode 100644 app/javascript/controllers/content_finder_controller.js create mode 100644 app/javascript/controllers/mappings_visualization_controller.js create mode 100644 app/javascript/mixins/useHighLight.js create mode 100644 app/javascript/mixins/useMappingsBubbles.js create mode 100644 app/views/admin/search/index.html.haml create mode 100644 app/views/admin/search/search.html.haml create mode 100644 app/views/admin/search/show.html.haml create mode 100644 app/views/content_finder/index.html.haml create mode 100644 app/views/home/tools.html.haml create mode 100644 app/views/instances/_details.html.haml delete mode 100644 app/views/instances/_instance_details.html.haml delete mode 100644 app/views/mappings/_concept_mappings_selector.html.haml delete mode 100644 app/views/mappings/_ontology_mappings.html.haml delete mode 100644 app/views/mappings/bulk_loader/_loader.html.haml create mode 100644 app/views/mappings/tab_sections/_bubble_view.html.haml create mode 100644 app/views/mappings/tab_sections/_table_view.html.haml create mode 100644 app/views/mappings/tab_sections/_upload_mappings.html.haml create mode 100644 app/views/ontologies/content_serializer.html.haml create mode 100644 app/views/ontologies/htaccess.html.haml delete mode 100644 app/views/properties/_properties_tree.html.haml create mode 100644 config/i18n-tasks.yml create mode 100644 test/fixtures/recommender.yml create mode 100644 test/system/recommender_page_test.rb diff --git a/Gemfile b/Gemfile index 00fb43225..810a8c853 100644 --- a/Gemfile +++ b/Gemfile @@ -115,6 +115,8 @@ group :development do # Use console on exceptions pages [https://github.com/rails/web-console] gem 'web-console' gem 'i18n-tasks' + gem 'i18n-tasks-csv', '~> 1.1' + gem 'deepl-rb' gem 'letter_opener_web', '~> 2.0' gem 'haml-rails' @@ -131,6 +133,7 @@ group :test do gem 'simplecov', require: false gem 'simplecov-cobertura' # for codecov.io #gem 'webdrivers' + gem 'webmock' end diff --git a/Gemfile.lock b/Gemfile.lock index 2aef58e74..ea693d64b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,14 +92,7 @@ GEM execjs (~> 2) base64 (0.2.0) bcrypt_pbkdf (1.1.0) - better_html (2.0.2) - actionview (>= 6.0) - activesupport (>= 6.0) - ast (~> 2.0) - erubi (~> 1.4) - parser (>= 2.4) - smart_properties - bigdecimal (3.1.7) + bigdecimal (3.1.6) bindata (2.5.0) bindex (0.8.1) bootsnap (1.18.3) @@ -142,6 +135,9 @@ GEM childprocess (5.0.0) coderay (1.1.3) concurrent-ruby (1.2.3) + crack (1.0.0) + bigdecimal + rexml crass (1.0.6) css_parser (1.16.0) addressable @@ -192,6 +188,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) + hashdiff (1.1.0) hashie (5.0.0) highline (2.1.0) html2haml (2.3.0) @@ -206,17 +203,18 @@ GEM domain_name (~> 0.5) i18n (1.14.4) concurrent-ruby (~> 1.0) - i18n-tasks (1.0.13) + i18n-tasks (0.9.37) activesupport (>= 4.0.2) ast (>= 2.1.0) - better_html (>= 1.0, < 3.0) erubi highline (>= 2.0.0) i18n - parser (>= 3.2.2.1) + parser (>= 2.2.3.0) rails-i18n rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) + i18n-tasks-csv (1.1) + i18n-tasks (~> 0.9) iconv (1.0.8) importmap-rails (2.0.1) actionpack (>= 6.0.0) @@ -496,7 +494,6 @@ GEM simplecov (~> 0.19) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - smart_properties (1.17.0) snaky_hash (2.0.1) hashie version_gem (~> 1.1, >= 1.1.1) @@ -554,6 +551,10 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) + webmock (3.23.0) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) websocket (1.2.10) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -594,6 +595,7 @@ DEPENDENCIES html2haml i18n i18n-tasks + i18n-tasks-csv (~> 1.1) iconv importmap-rails inline_svg @@ -644,6 +646,7 @@ DEPENDENCIES tzinfo-data view_component (~> 2.72) web-console + webmock will_paginate (~> 3.0) BUNDLED WITH diff --git a/app/assets/images/icons/copy_link.svg b/app/assets/images/icons/copy_link.svg new file mode 100644 index 000000000..88cb33d33 --- /dev/null +++ b/app/assets/images/icons/copy_link.svg @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/assets/images/icons/json-ld-file.svg b/app/assets/images/icons/json-ld-file.svg new file mode 100644 index 000000000..870b32514 --- /dev/null +++ b/app/assets/images/icons/json-ld-file.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + JSON-LD + + diff --git a/app/assets/images/icons/ntriples-file.svg b/app/assets/images/icons/ntriples-file.svg new file mode 100644 index 000000000..99628c2a1 --- /dev/null +++ b/app/assets/images/icons/ntriples-file.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + N-TRIPLE + + diff --git a/app/assets/images/icons/rdf-xml-file.svg b/app/assets/images/icons/rdf-xml-file.svg new file mode 100644 index 000000000..570097b55 --- /dev/null +++ b/app/assets/images/icons/rdf-xml-file.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + RDF/XML + + diff --git a/app/assets/images/icons/three-dots.svg b/app/assets/images/icons/three-dots.svg new file mode 100644 index 000000000..72c8241dd --- /dev/null +++ b/app/assets/images/icons/three-dots.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/icons/turtle-file.svg b/app/assets/images/icons/turtle-file.svg new file mode 100644 index 000000000..11eaaa257 --- /dev/null +++ b/app/assets/images/icons/turtle-file.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + TURTLE + + diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 493c35ad3..3fe97607a 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -18,12 +18,8 @@ //= require bp_mappings //= require bp_admin //= require concepts -//= require_tree ./components //= require projects //= require Chart.min //= require application_esbuild -customElements.define('data-table-loader', DataTableLoader) -customElements.define('data-table', DataTable) -customElements.define('instances-table', InstancesTable) diff --git a/app/assets/javascripts/bp_admin.js b/app/assets/javascripts/bp_admin.js index e6468f9f8..d96daf85d 100644 --- a/app/assets/javascripts/bp_admin.js +++ b/app/assets/javascripts/bp_admin.js @@ -406,11 +406,11 @@ function populateOntologyRows(data) { var ontologyDateCreated = parseReportDate(ontology["date_created"]); if (ontology["logFilePath"] != '') { - bpLinks += "Log  |  "; + bpLinks += ""; } - bpLinks += "REST  |  "; + bpLinks += ""; let title = `Ontology Submissions for ${acronym}` - let link = `Submissions` + let link = `` bpLinks += link var errStatus = ontology["errErrorStatus"] ? ontology["errErrorStatus"].join(", ") : ''; @@ -525,53 +525,45 @@ function displayOntologies(data, ontology) { { "targets": 0, "searchable": true, - "title": "Ontology", - "width": "160px" + "title": "Ontology" }, { "targets": 1, "searchable": true, "title": "Admin", - "width": "160px" }, { "targets": 2, "searchable": true, "title": "Format", - "width": "55px" }, { "targets": 3, "searchable": true, "title": "Date Created", "type": "date", - "width": "170px" }, { "targets": 4, "searchable": true, "title": "Report Date", "type": "date", - "width": "170px" }, { "targets": 5, "searchable": false, "orderable": false, "title": "URL", - "width": "140px" }, { "targets": 6, "searchable": true, "title": "Error Status", - "width": "130px" }, { "targets": 7, "searchable": true, "title": "Missing Status", - "width": "130px" }, { "targets": 8, @@ -584,8 +576,8 @@ function displayOntologies(data, ontology) { "visible": false } ], - "autoWidth": false, - "lengthChange": false, + "autoWidth": true, + "lengthChange": true, "searching": true, "language": { "search": "Filter: ", diff --git a/app/assets/javascripts/components/data_table/data_table.js b/app/assets/javascripts/components/data_table/data_table.js deleted file mode 100644 index cca5c0a08..000000000 --- a/app/assets/javascripts/components/data_table/data_table.js +++ /dev/null @@ -1,57 +0,0 @@ -class DataTable extends HTMLElement{ - - - get config() { - return this.getAttribute("config") || {} - } - - set config(v) { - this.setAttribute("config" ,v) - } - constructor(){ - super() - this.tableElem = document.createElement("table") - this.tableElem.style.width = "100%" - this.tableElem.classList.add('table-content') - this.tableElem.classList.add('table-content-stripped') - - this.container = document.createElement("div") - this.container.appendChild(this.tableElem) - - this.dataTable = null - } - - connectedCallback(){ - this.initDataTable() - this.dispatchTableUpdateEvent() - this.dispatchClickedRow() - } - - initDataTable(){ - if ( $.fn.dataTable.isDataTable( this.tableElem ) ) { - $(this.tableElem).DataTable().destroy(); - } - this.dataTable = $(this.tableElem).DataTable(this.config) - this.appendChild(this.container) - } - dispatchTableUpdateEvent(){ - this.dataTable.on("xhr" , () => { - this.dispatchEvent(new CustomEvent("update" , { - detail :{data:this.dataTable.ajax.json()} - })) - }) - } - - dispatchClickedRow(){ - this.dataTable.on('click', 'tbody tr', (e) => { - const row = e.currentTarget - const rowIndex = row.rowIndex - const data = this.dataTable.row(row).data() - this.dispatchEvent(new CustomEvent("row-click" ,{ - detail: {row, data, rowIndex} - })) - }) - } - - -} \ No newline at end of file diff --git a/app/assets/javascripts/components/data_table/data_table_loader.js b/app/assets/javascripts/components/data_table/data_table_loader.js deleted file mode 100644 index b660a8229..000000000 --- a/app/assets/javascripts/components/data_table/data_table_loader.js +++ /dev/null @@ -1,14 +0,0 @@ -class DataTableLoader extends HTMLElement { - - constructor() { - super() - } - - connectedCallback() { - this.innerHTML = ` -
- Loading... -
- ` - } -} \ No newline at end of file diff --git a/app/assets/javascripts/components/instances/instances_table.js b/app/assets/javascripts/components/instances/instances_table.js deleted file mode 100644 index 8fb4371cb..000000000 --- a/app/assets/javascripts/components/instances/instances_table.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * A web components that render a jquery data table of an ontology (and class) instances inside a element - */ -class InstancesTable extends DataTable { - - - get ontologyAcronym() { - return this.getAttribute("ontology-acronym") || "" - } - - get classUri() { - return this.getAttribute("class-uri") || "" - } - - get config() { - return { - "paging": true, - "pagingType": "full", - "info": true, - "searching": true, - "ordering": false, - "serverSide": true, - "processing": true, - "ajax": { - "url": this.getAjaxUrl(), - "contentType": "application/json", - "dataSrc": (json) => { - json.recordsTotal = json["table"]["totalCount"] - json.recordsFiltered = json.recordsTotal - return json["table"]["collection"].map(x => [ - { - id: x["table"]["@id"], - label: x["table"]["label"], - prefLabel: x["table"]["prefLabel"], - labelToPrint: x["table"]["labelToPrint"], - ontology: x["table"]["ontology"] - }, - x["table"]["types"], - x["table"]["properties"] - ]) - }, - error: (e) => { - console.log("instances ajax call error: " + e.statusText) - }, - "data": (d) => { - //return parameters to send for the server - let columns = d.columns - let sortby = (d.order[0] ? columns[d.order[0].column].name : "") - let order = (d.order[0] ? d.order[0].dir : "") - return { - page: (d.start / d.length) + 1, - pagesize: d.length, - search: d.search.value, - sortby, order - } - } - }, - "columnDefs": this.render(), - "language": { - 'loadingRecords': ' ', - 'processing': new DataTableLoader(), - "search": "Search by labels:" - } - - } - } - - constructor() { - super() - } - - connectedCallback() { - super.connectedCallback() - } - - update(ontologyAcronym, classUri) { - this.setAttribute("ontology-acronym", ontologyAcronym) - this.setAttribute("class-uri", classUri) - super.initDataTable() - } - - render() { - - let columns = [{ - "targets": 0, - "name": "label", - "title": 'Instance', - "render": (data) => { - const {id, labelToPrint, ontology} = data - return `${labelToPrint}` - } - }] - - if (!this.isClassURISet()) - columns.push({ - "targets": 1, - "name": "types", - "title": 'Types', - "render": (data) => data.map(x => { - const id = x.type - const label = x.labelToPrint - const acronym = x.ontology - const href = (id === label ? id : `?p=classes&conceptid=${encodeURIComponent(id)}`) - return `${label}` - }) - }) - - return columns - } - - - getAjaxUrl() { - let url = "/ajax/" + this.ontologyAcronym - if (this.isClassURISet() > 0) { - url += "/classes/" + encodeURIComponent(this.classUri) - } - url += "/instances" - return url - } - - isClassURISet() { - return this.classUri.length > 0 - } - - -} \ No newline at end of file diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index b7e8e48d1..7d8e8ac0e 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -152,9 +152,7 @@ table.dataTable tbody tr.selected { } .site-admin-page-section { - display: flex; margin-top: 15px; - .admin-action-item { margin-right: 8px; } diff --git a/app/assets/stylesheets/annotator.scss b/app/assets/stylesheets/annotator.scss index fb051b5f3..8cb42cc98 100644 --- a/app/assets/stylesheets/annotator.scss +++ b/app/assets/stylesheets/annotator.scss @@ -45,7 +45,7 @@ } .annotator-page-text-area .insert-sample-text-button{ display: flex; - justify-content: end; + justify-content: flex-end; padding: 10px 20px 20px 20px; } .annotator-page-text-area .insert-sample-text-button .button{ diff --git a/app/assets/stylesheets/application.css.scss.erb b/app/assets/stylesheets/application.css.scss.erb index d1c9118a4..7f4793fcf 100755 --- a/app/assets/stylesheets/application.css.scss.erb +++ b/app/assets/stylesheets/application.css.scss.erb @@ -56,10 +56,14 @@ @import "ontology_details_header"; @import "ontology_viewer"; @import "browse"; +@import "agent_tooltip"; +@import "content_finder"; +@import "tools"; + /* Bootstrap and Font Awesome */ @import "bootstrap"; @import "bootstrap_overrides"; -@import "agent_tooltip"; + <% if (ui_theme = $UI_THEME.to_s.parameterize).present? && File.exists?(Rails.root.join('app', 'assets', 'stylesheets', 'themes', ui_theme)) %> @import "themes/<%= ui_theme %>/main"; diff --git a/app/assets/stylesheets/components/index.scss b/app/assets/stylesheets/components/index.scss index a93e389b1..c3d57ff51 100644 --- a/app/assets/stylesheets/components/index.scss +++ b/app/assets/stylesheets/components/index.scss @@ -32,4 +32,5 @@ @import "search_result"; @import "range_slider"; @import "ontologies_selector"; -@import "loader"; \ No newline at end of file +@import "loader"; +@import "vertical_tabs"; \ No newline at end of file diff --git a/app/assets/stylesheets/components/progress_pages.scss b/app/assets/stylesheets/components/progress_pages.scss index 6e3f6cff5..42b055fa4 100644 --- a/app/assets/stylesheets/components/progress_pages.scss +++ b/app/assets/stylesheets/components/progress_pages.scss @@ -37,7 +37,7 @@ .progress-pages-container .progress-item:last-of-type { & > div { - align-items: end; + align-items: flex-end; left: -23px; } } diff --git a/app/assets/stylesheets/components/search_input.scss b/app/assets/stylesheets/components/search_input.scss index 8d01cbb9a..dd721562a 100644 --- a/app/assets/stylesheets/components/search_input.scss +++ b/app/assets/stylesheets/components/search_input.scss @@ -11,6 +11,7 @@ .search-content{ display: flex; + flex-wrap: wrap; color: #777777 !important; justify-content: space-between; padding: 20px 20px; @@ -26,10 +27,14 @@ .search-content div img{ width: 12px; } + .search-content div p{ font-weight: 300; - margin-left: 10px; margin-bottom: 0; + word-break: break-word; +} +.search-content div small { + word-break: break-word; } .search-dropdown-active{ @@ -38,35 +43,59 @@ .search-element{ margin-bottom: 0; + max-width: 280px; } .searched-elements{ margin-bottom: 0; } + .search-inputs{ position: relative; } +.concepts-search-button{ + top: 15px !important; + right: 10px !important; +} + + .home-search-button{ position: absolute; - top: 42px; - right: 20px; + top: 38px; + right: 12px; +} + +.home-search-button svg{ width: 15px; height: 15px; cursor: pointer; } -.home-search-button path{ +.home-search-button svg path{ fill: var(--primary-color); } .home-search-button.search-input-nav-icon{ position: absolute; - top: 11px; - right: 8px; + top: 4px; + right: 6px; + z-index: 99; +} +.home-search-button.search-input-nav-icon svg{ width: 11px; height: 11px; cursor: pointer; - z-index: 99; } -.home-search-button.search-input-nav-icon path{ + +.home-search-button.search-input-nav-icon svg path{ fill: white; } +.search-component-loader{ + padding-top: 4px; + padding-right: 2px; + color: var(--primary-color); +} +.home-search-button.search-input-nav-icon .search-component-loader{ + margin-top: 0; + padding-right: 0; + color: white; +} diff --git a/app/assets/stylesheets/components/table.scss b/app/assets/stylesheets/components/table.scss index f3fb8711a..6c03be874 100644 --- a/app/assets/stylesheets/components/table.scss +++ b/app/assets/stylesheets/components/table.scss @@ -2,7 +2,7 @@ border-collapse: collapse; width: 100%; border-spacing: 0; - + box-sizing: border-box !important; } .table-auto-layout{ table-layout: auto !important; diff --git a/app/assets/stylesheets/components/tabs_container.scss b/app/assets/stylesheets/components/tabs_container.scss index a9589e678..bb670a95c 100644 --- a/app/assets/stylesheets/components/tabs_container.scss +++ b/app/assets/stylesheets/components/tabs_container.scss @@ -21,7 +21,7 @@ position: relative; } -.tabs-container .tab-items div { +.tabs-container .tab-items > div { font-size: 16px; font-weight: 400; color: #5e5e5e; diff --git a/app/assets/stylesheets/components/vertical_tabs.scss b/app/assets/stylesheets/components/vertical_tabs.scss new file mode 100644 index 000000000..aa47a1923 --- /dev/null +++ b/app/assets/stylesheets/components/vertical_tabs.scss @@ -0,0 +1,83 @@ +.center { + display: flex; + justify-content: center; + margin-top: 50px; +} + +.edit-ontology-container { + padding: 0 50px; + width: 1248px +} + +.edit-ontology-title { + font-size: 18px; + font-weight: bold; +} + +.edit-ontology-title hr { + width: 93px; + border: 1px solid var(--primary-color); + margin: 0; + opacity: 100%; +} + + +.tabs-left-column { + border-radius: 5px; + min-width: 250px; + .nav-pills.disabled a { + color: #888888 !important; + cursor: not-allowed; + } + + .nav-pills:not(.disabled){ + .vertical-tab-item.active { + color: var(--primary-color); + background-color: var(--light-color) !important; + border-left: 3px solid var(--primary-color); + font-weight: 700; + } + + + + .vertical-tab-item:hover { + background-color: #F6F6F6; + } + + } +} + +.vertical-tab-item { + font-size: 14px; + width: 100%; + padding: 10px 15px; + font-weight: 500; + margin: 3px 0; + cursor: pointer; + border-radius: 5px; + transition: background-color ease 0.3s; +} + +.tabs-right-column { + border-radius: 5px; + width: 100%; +} + + + +.tabs-left-column input { + border: 1px solid #BDBDBD; + outline: none; + font-size: 14px; + border-radius: 5px; + padding: 10px 15px; + width: 100%; + margin-top: 20px; +} + + + + + + + diff --git a/app/assets/stylesheets/concepts.scss b/app/assets/stylesheets/concepts.scss index c9f04c7d0..e466177ed 100644 --- a/app/assets/stylesheets/concepts.scss +++ b/app/assets/stylesheets/concepts.scss @@ -156,4 +156,8 @@ div.synonym-change-request button { justify-content: center; color: var(--gray-color); margin-top: 10px; +} + +.concepts-mapping-count .d-flex{ + display: inline !important; } \ No newline at end of file diff --git a/app/assets/stylesheets/content_finder.scss b/app/assets/stylesheets/content_finder.scss new file mode 100644 index 000000000..4259b5720 --- /dev/null +++ b/app/assets/stylesheets/content_finder.scss @@ -0,0 +1,42 @@ +.content-finder-container{ + display: flex; + justify-content: center; + margin-bottom: 6px; +} + +.content-finder-container .inputs > div{ + margin:10px 0; + text-align: center; +} + +.content-finder-result{ + text-align: left; + width: 95%; + margin-left: auto; + margin-right: auto; + background-color: #fff; +} + +.content-finder-html-result{ + max-width: 95%; + margin-left: auto; + margin-right: auto; +} +.hljs-custom-prefixes{ + color: grey; +} +.hljs-custom-symbol{ + color: red; +} + +.hljs-custom-concepts{ + color: #770088; +} + +.hljs-subject{ + color: green; +} + +.hljs-predicate{ + color: #770088; +} \ No newline at end of file diff --git a/app/assets/stylesheets/mappings.scss b/app/assets/stylesheets/mappings.scss index 070fafea7..c0a494fb0 100644 --- a/app/assets/stylesheets/mappings.scss +++ b/app/assets/stylesheets/mappings.scss @@ -1,4 +1,266 @@ +.mappigs-page-container{ + display: flex; + justify-content: center; + .file_uploader{ + width: 700px; + } +} +.mappings-page-subcontainer{ + width: 1248px; + padding: 20px 50px; +} + +.mappings-page-title{ + font-size: 25px; + font-weight: 700; +} +.mappings-page-title .line{ + height: 2px; + width: 57px; + background-color: var(--primary-color); + border-radius: 10px; +} +.mappings-page-decription{ + color: #888888; + margin: 20px 0; +} + +.mappings-bubble{ + cursor: pointer; +} +.mappings-bubble-view-frame{ + width: 600px; + height: 600px; + overflow: auto; +} + +.mappings-zoom-buttons{ + display: flex; + margin-top: 20px; + justify-content: center; +} +.mappings-zoom-buttons svg{ + width: 40px; + cursor: pointer; + path{ + fill: var(--primary-color) + } + &:hover path{ + fill: var(--hover-color) + } +} +.mappings-zoom-buttons div + div{ + margin-left: 40px; +} +.mappings-bubble-view-container{ + display: flex; + flex-direction: column; + align-items: center; +} +.mappings-table-view-container{ + display: flex; + flex-direction: column; + align-items: center; + margin: 20px 0; +} +.mappings-ontologies-select{ + width: 700px; +} + +.mappings-table-container{ + width: 678px; + margin: 20px 0; +} +.mappings-table-ontology-name{ + font-weight: 600; +} + +.bubble-tooltip { + position: absolute; + text-align: center; + padding: 6px; + font-size: 12px; + background: rgba(0, 0, 0, 0.7); + color: white; + border-radius: 4px; + pointer-events: none; + z-index: 9999; +} + +.upload-mappings{ + display: flex; + flex-direction: column; + align-items: center; + margin-top: 20px; +} + +.upload-mappings-example{ + width: 700px; + margin-bottom: 20px; +} +.upload-mappings-example.ontologies{ + width: 600px; +} + +.mappings-page-ontologies-selector{ + width: 100%; + padding: 0 30px 30px 30px; +} + +.mappings-page-ontologies-selector .selector-button{ + margin-top: 20px; +} + +.upload-mappings-example .title-bar{ + display: flex; + justify-content: center; + width: 100%; + cursor: pointer; + padding: 15px; + color: var(--primary-color); + align-items: center; +} +.upload-mappings-example .title-bar svg{ + margin-right: 10px; +} + +.summary-mappigs-page-container{ + margin-left: 90px; +} +.mapping-bubbles-loader{ + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.summary-mappings-tab { + margin-top: 10px; +} + +#ontology_viewermappings_content { + .summary-mappings-tab { + display: flex; + } + + .summary-mappings-tab-table { + width: 400px; + } +} + +.mappings-table-pagination a, .mappings-table-pagination em{ + margin-left: 10px; +} + +.mappings-bubble-view-legend{ + width: 600px; + display: flex; + flex-direction: column; + align-items: center; + font-weight: 500; + padding: 20px; +} +.mappings-bubble-view-legend > .title { + display: flex; + flex-direction: column; + align-items: center; +} + +.mappings-bubble-view-legend > .title .text{ + font-size: 18px; + color: var(--primary-color); +} +.mappings-bubble-view-legend > .title .line{ + background-color: var(--primary-color); + border-radius: 100%; + width: 40px; + height: 2px; +} + +.content-container{ + width: 100%; + margin-top: -35px; +} +.bubble-view-legend-item{ + margin-top: 50px; +} +.bubble-view-legend-item .title{ + font-weight: 500; +} +.bubble-view-legend-item .title span{ + font-weight: 400; +} + +.mappings-bubble-size-legend, .mappings-bubble-color-legend{ + display: flex; + justify-content: space-between; + align-items: center; + margin: 20px 0; +} +.mappings-bubble-size-legend .bubble{ + border-radius: 100%; + border: 2px solid var(--primary-color); +} +.mappings-bubble-size-legend .bubble.bubble1{ + width: 10px; + height: 10px; +} +.mappings-bubble-size-legend .bubble.bubble2{ + width: 20px; + height: 20px; +} +.mappings-bubble-size-legend .bubble.bubble3{ + width: 30px; + height: 30px; +} +.mappings-bubble-size-legend .bubble.bubble4{ + width: 40px; + height: 40px; +} +.mappings-bubble-size-legend .bubble.bubble5{ + width: 50px; + height: 50px; +} +.mappings-bubble-size-legend .bubble.bubble6{ + width: 60px; + height: 60px; +} +.mappings-bubble-color-legend .bubble{ + border-radius: 100%; + background-color: var(--primary-color); + width: 35px; + height: 35px; +} +.mappings-bubble-color-legend .bubble.bubble1{ + opacity: 30%; +} +.mappings-bubble-color-legend .bubble.bubble2{ + opacity: 50%; +} +.mappings-bubble-color-legend .bubble.bubble3{ + opacity: 70%; +} +.mappings-bubble-color-legend .bubble.bubble4{ + opacity: 80%; +} +.mappings-bubble-color-legend .bubble.bubble5{ + opacity: 90%; +} +.mappings-bubble-color-legend .bubble.bubble6{ + opacity: 100%; +} +.mappings-bubble-size-legend .bubble.yellow{ + width: 35px; + height: 35px; + background-color: var(--secondary-color); + border: none; +} +.mappings-legend-text{ + color: var(--primary-color); + font-size: 15px; + font-weight: 500; +} @media (min-width: 1000px) { #mappings_container{ @@ -76,3 +338,23 @@ div#map_from_concept_details_table, div#map_to_concept_details_table { width: 100%; } +.summary-mappings-tab-table { + .dataTables_wrapper .dataTables_filter { + float: none; + text-align: unset; + } +} + +.summary-mappings-tab-table label { + display: block; + font-size: 0; +} + +.summary-mappings-tab-table label input[type="search"] { + font-size: 16px; + width: 100%; + border-radius: 8px; + padding: 10px; + outline: none; + margin-left: 0 !important; +} \ No newline at end of file diff --git a/app/assets/stylesheets/ontologies.scss b/app/assets/stylesheets/ontologies.scss index b93f83277..afa87ae7c 100644 --- a/app/assets/stylesheets/ontologies.scss +++ b/app/assets/stylesheets/ontologies.scss @@ -179,6 +179,13 @@ $widget-table-border-color: #EFEFEF; /************************************ /* Classes pane ************************************/ +.tree-container { + overflow-x: auto; + min-width: 35%; + min-height: 70vh; + max-height: 90vh; + font: small/1.231 arial,helvetica,clean,sans-serif; +} #bd_content { display: flex; @@ -213,21 +220,11 @@ $widget-table-border-color: #EFEFEF; /* Properties pane ************************************/ -#propTree a { - font-family: "Myriad Pro","Myriad Web Pro","Myriad Web","Myriad","Trebuchet MS","Tahoma","Helvetica","Arial",sans-serif; - font: small/1.231 arial,helvetica,clean,sans-serif; -} - .ont-properties { display: flex; flex-direction: row; } -#propTree { - overflow-x: auto; - white-space: nowrap; -} - /************************************ /* Widgets pane ************************************/ @@ -304,4 +301,15 @@ $widget-table-border-color: #EFEFEF; opacity: 1; } } +} + +.htacess-code-container{ + margin-top: 20px; + margin-bottom: 30px; + text-align: left; + padding: 20px; + background-color: #1d1d1d; + color: white; + border-radius: 10px; + position: relative; } \ No newline at end of file diff --git a/app/assets/stylesheets/recommender.scss b/app/assets/stylesheets/recommender.scss index b89ec8349..07ba6952f 100644 --- a/app/assets/stylesheets/recommender.scss +++ b/app/assets/stylesheets/recommender.scss @@ -52,7 +52,7 @@ } .recommender-page-text-area .insert-sample-text-button{ display: flex; - justify-content: end; + justify-content: flex-end; padding: 20px; } .recommender-page-text-area .insert-sample-text-button .button{ @@ -176,7 +176,7 @@ } .recommender-result-highlighted{ display: flex; - justify-content: end; + justify-content: flex-end; } .recommender-page-text-area-results a{ font-weight: 600; diff --git a/app/assets/stylesheets/register.scss b/app/assets/stylesheets/register.scss index 2f9f1b305..9dda813db 100644 --- a/app/assets/stylesheets/register.scss +++ b/app/assets/stylesheets/register.scss @@ -87,7 +87,7 @@ margin-top: 20px; } -#user_register_mail_list{ +.user_register_checkbox{ margin-top: 14px; accent-color: #8E8E8E; } diff --git a/app/assets/stylesheets/search.scss b/app/assets/stylesheets/search.scss index e19e08ebf..e66312f7e 100644 --- a/app/assets/stylesheets/search.scss +++ b/app/assets/stylesheets/search.scss @@ -11,8 +11,12 @@ } .search-page-input{ + position: relative; + padding-bottom: 30px; display: flex; - margin-bottom: 25px; + .search-container{ + top:65px; + } } .search-page-input input{ border-radius: 8px; diff --git a/app/assets/stylesheets/tools.scss b/app/assets/stylesheets/tools.scss new file mode 100644 index 000000000..3ed34770a --- /dev/null +++ b/app/assets/stylesheets/tools.scss @@ -0,0 +1,13 @@ +.tools-container{ + svg path { + fill: var(--primary-color); + } + .summary-card{ + height: 100%; + background-color: white; + } + + .tool-description{ + color: var(--gray-color) !important; + } +} \ No newline at end of file diff --git a/app/components/agent_search_input_component/agent_search_input_component.html.haml b/app/components/agent_search_input_component/agent_search_input_component.html.haml index 13a1101ed..89f0fdd30 100644 --- a/app/components/agent_search_input_component/agent_search_input_component.html.haml +++ b/app/components/agent_search_input_component/agent_search_input_component.html.haml @@ -6,6 +6,6 @@ - s.template do %a{href: "LINK", class: "search-content", 'data-turbo-frame': '_self'} %p.search-element.home-searched-ontology - NAME(ACRONYM) + NAME (IDENTIFIERS) %p.home-result-type TYPE \ No newline at end of file diff --git a/app/components/buttons/regular_button_component/regular_button_component.html.haml b/app/components/buttons/regular_button_component/regular_button_component.html.haml index 8b6567203..f2ce56cbd 100644 --- a/app/components/buttons/regular_button_component/regular_button_component.html.haml +++ b/app/components/buttons/regular_button_component/regular_button_component.html.haml @@ -8,4 +8,10 @@ const loading = document.getElementById(loadingId) element.style.display = 'none'; loading.style.display = 'flex'; - } \ No newline at end of file + } + document.addEventListener('turbo:submit-end', function(event) { + const loading = document.getElementById('#{"#{@id}-loading-animation"}') + const button = document.getElementById('#{"#{@id}"}') + button.style.display = 'flex'; + loading.style.display = 'none'; + }); \ No newline at end of file diff --git a/app/components/card_message_component/card_message_component.html.haml b/app/components/card_message_component/card_message_component.html.haml index bc7641ee1..06043a539 100644 --- a/app/components/card_message_component/card_message_component.html.haml +++ b/app/components/card_message_component/card_message_component.html.haml @@ -1,7 +1,7 @@ .d-flex.justify-content-center.m-4 .card-message-card .d-flex.justify-content-center.mb-4 - %img{:src => "#{asset_path(icon)}"}/ + = inline_svg_tag icon - if no_title? %p.card-message-text = @message diff --git a/app/components/clipboard_component.rb b/app/components/clipboard_component.rb index 1dfad61e5..feab36c1a 100644 --- a/app/components/clipboard_component.rb +++ b/app/components/clipboard_component.rb @@ -2,7 +2,9 @@ class ClipboardComponent < ViewComponent::Base - def initialize(message: nil, show_content: true) + def initialize(icon: "icons/copy.svg", title: "Copy", message: nil, show_content: true) + @icon = icon + @title = title @message = message @show_content = show_content end diff --git a/app/components/clipboard_component/clipboard_component.html.haml b/app/components/clipboard_component/clipboard_component.html.haml index 9022a2dbd..1567cb679 100644 --- a/app/components/clipboard_component/clipboard_component.html.haml +++ b/app/components/clipboard_component/clipboard_component.html.haml @@ -1,8 +1,8 @@ %div.clipboard.d-flex.align-items-center{data:{controller: 'clipboard'}, style: 'cursor:pointer;'} %div{data: {'clipboard-target': 'content'}, class: @show_content ? '' : 'd-none'} = @message || content - %div.ml-2 - %div.copy{data: {controller:'tooltip', action: 'click->clipboard#copy', 'clipboard-target': 'copy'}, title: 'Copy'} - = inline_svg_tag('icons/copy.svg', width: '20', height: '21px', fill: 'none') + %div + %div.copy{data: {controller:'tooltip', action: 'click->clipboard#copy', 'clipboard-target': 'copy'}, title: @title} + = inline_svg_tag(@icon, width: '20', height: '21px', fill: 'none') %div.d-none.check{data: {controller:'tooltip', 'clipboard-target': 'check'}, title: 'Copied!'} = inline_svg_tag('check.svg', width: '18px', height: '18px') diff --git a/app/components/clipboard_component/clipboard_component_controller.js b/app/components/clipboard_component/clipboard_component_controller.js index e28b5bdb0..358a9cafb 100644 --- a/app/components/clipboard_component/clipboard_component_controller.js +++ b/app/components/clipboard_component/clipboard_component_controller.js @@ -10,11 +10,11 @@ export default class extends Controller { copy () { const text = this.contentTarget.innerHTML || this.contentTarget.value - navigator.clipboard.writeText(text).then(() => { + navigator.clipboard.writeText(text.trim()).then(() => { this.#copied() }) } - + #copied () { if (this.timeout) { clearTimeout(this.timeout) diff --git a/app/components/concept_details_component.rb b/app/components/concept_details_component.rb index 16635e419..663e7e188 100644 --- a/app/components/concept_details_component.rb +++ b/app/components/concept_details_component.rb @@ -2,6 +2,7 @@ class ConceptDetailsComponent < ViewComponent::Base include ApplicationHelper + include OntologiesHelper include MultiLanguagesHelper renders_one :header, TableComponent @@ -9,13 +10,14 @@ class ConceptDetailsComponent < ViewComponent::Base attr_reader :concept_properties - def initialize(id:, acronym:, properties:, top_keys:, bottom_keys:, exclude_keys:) + def initialize(id:, acronym:, concept_id: nil , properties: nil, top_keys: [], bottom_keys: [], exclude_keys: []) @acronym = acronym @properties = properties @top_keys = top_keys @bottom_keys = bottom_keys @exclude_keys = exclude_keys @id = id + @concept_id=concept_id @concept_properties = concept_properties2hash(@properties) if @properties end @@ -40,7 +42,7 @@ def row_hash_properties(properties_set, ontology_acronym, &block) values = data[:values] url = data[:key] - ajax_links = values.map do |v| + ajax_links = Array(values).map do |v| if block_given? block.call(v) else @@ -50,11 +52,11 @@ def row_hash_properties(properties_set, ontology_acronym, &block) display_in_multiple_languages([v].to_h) end end - end if values.is_a?(Array) + end out << [ - { th: "#{remove_owl_notation(key)}".html_safe }, - { td: "
#{"

#{ajax_links&.join('

')}"}

".html_safe } + { th: content_tag(:span, remove_owl_notation(key), title: url, 'data-controller': 'tooltip') }, + { td: content_tag(:span, ajax_links.join.html_safe) } ] end out @@ -71,12 +73,19 @@ def filter_properties(top_keys, bottom_keys, exclude_keys, concept_properties) all_keys = concept_properties&.keys || [] top_set = properties_set_by_keys(top_keys, concept_properties, exclude_keys) bottom_set = properties_set_by_keys(bottom_keys, concept_properties, exclude_keys) - leftover = properties_set_by_keys(all_keys - top_keys - bottom_keys, concept_properties, exclude_keys) + leftover = properties_set_by_keys(all_keys, concept_properties, exclude_keys) + leftover = leftover.reject { |key, _| top_set.key?(key) || bottom_set.key?(key) } [top_set, leftover, bottom_set] end private + def link_to_format_modal(format, icon) + link_to_modal(nil, "/ontologies/#{@acronym}/#{escape(@concept_id)}/serialize/#{format}", data: {show_modal_title_value: @concept_id, show_modal_size_value: 'modal-xl'}) do + inline_svg("icons/#{icon}.svg", width: '50px', height: '50px') + end + end + def concept_properties2hash(properties) # NOTE: example properties # diff --git a/app/components/concept_details_component/concept_details_component.html.haml b/app/components/concept_details_component/concept_details_component.html.haml index 4b1d6015c..e42cfa818 100644 --- a/app/components/concept_details_component/concept_details_component.html.haml +++ b/app/components/concept_details_component/concept_details_component.html.haml @@ -3,12 +3,11 @@ = header %div.my-3 %div.raw-table - - if @bottom_keys.present? - = render DropdownContainerComponent.new(title: 'Raw data', id: "accordion-#{@id}") do - - top_set, leftover_set, bottom_set = filter_properties(@top_keys, @bottom_keys, @exclude_keys, prefix_properties(@concept_properties)) - - leftover_set = convert_dates(leftover_set) - = render TableComponent.new(stripped: true) do |t| - + = render DropdownContainerComponent.new(title: 'Raw data', id: "accordion-#{@id}") do + = render TableComponent.new(stripped: true) do |t| + - if @bottom_keys.present? + - top_set, leftover_set, bottom_set = filter_properties(@top_keys, @bottom_keys, @exclude_keys, prefix_properties(@concept_properties)) + - leftover_set = convert_dates(leftover_set) - row_hash_properties(top_set, @acronym).each do |row| - t.add_row(*row) @@ -23,3 +22,11 @@ - row_hash_properties(bottom_set, @acronym).each do |row| - t.add_row(*row) + + - if @concept_id + - t.row do |r| + - r.td(colspan: 2) do + %div.d-flex.justify-content-center.p-2 + - [["json","json-ld-file"], ["xml","rdf-xml-file"] , ["ntriples","ntriples-file"], ["turtle","turtle-file"]].each do |format, icon| + %div.mx-4{data: {controller: "tooltip"}, title: "Export in #{format.upcase}"} + = link_to_format_modal(format, icon) \ No newline at end of file diff --git a/app/components/display/header_component.rb b/app/components/display/header_component.rb index b814f55fc..76784068e 100644 --- a/app/components/display/header_component.rb +++ b/app/components/display/header_component.rb @@ -2,6 +2,8 @@ class Display::HeaderComponent < ViewComponent::Base + include ComponentsHelper + renders_one :text def initialize(text: nil, tooltip: nil) @@ -14,7 +16,7 @@ def call content_tag(:div, class: 'header-component') do out = content_tag(:p, text || @text) if @info && !@info.empty? - out = out + render(Display::InfoTooltipComponent.new(text: @info)) + out = out + info_tooltip(content_tag(:div, @info, style: 'max-width: 300px')) end out end diff --git a/app/components/display/info_tooltip_component.rb b/app/components/display/info_tooltip_component.rb index a9e10fd89..a3c36f5da 100644 --- a/app/components/display/info_tooltip_component.rb +++ b/app/components/display/info_tooltip_component.rb @@ -2,13 +2,14 @@ class Display::InfoTooltipComponent < ViewComponent::Base - def initialize(text: nil , icon: "info.svg") + def initialize(text: nil , icon: "info.svg", interactive: true) super @text = text @icon = icon + @interactive = interactive end def call - content_tag(:div, data:{controller:'tooltip', 'tooltip-interactive-value': 'true'}, title: @text, style: 'display: inline-block;') do + content_tag(:div, data:{controller:'tooltip', 'tooltip-interactive-value': @interactive}, title: @text, style: 'display: inline-block;') do if content content else diff --git a/app/components/display/rdf_highlighter_component.rb b/app/components/display/rdf_highlighter_component.rb new file mode 100644 index 000000000..9e96dafaf --- /dev/null +++ b/app/components/display/rdf_highlighter_component.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Display::RdfHighlighterComponent < ViewComponent::Base + + def initialize(format: , text: ) + @format = format + @content = text + end +end diff --git a/app/components/display/rdf_highlighter_component/rdf_highlighter_component.html.haml b/app/components/display/rdf_highlighter_component/rdf_highlighter_component.html.haml new file mode 100644 index 000000000..ee81966e9 --- /dev/null +++ b/app/components/display/rdf_highlighter_component/rdf_highlighter_component.html.haml @@ -0,0 +1,6 @@ +.clipboard-component-resource-content{style: 'position: absolute; right: 2%; top: 5px;'} + = render ClipboardComponent.new(message: @content, show_content: false) +.content-finder-result{data: {controller: 'rdf-highlighter', 'rdf-highlighter-format-value': @format }} + %pre.mb-0 + %code.d-block{style: 'text-wrap: pretty; word-break: break-all', data: {'rdf-highlighter-target': 'content'}} + #{@content} \ No newline at end of file diff --git a/app/javascript/controllers/metadata_downloader_controller.js b/app/components/display/rdf_highlighter_component/rdf_highlighter_component_controller.js similarity index 76% rename from app/javascript/controllers/metadata_downloader_controller.js rename to app/components/display/rdf_highlighter_component/rdf_highlighter_component_controller.js index c1f139e51..073736970 100644 --- a/app/javascript/controllers/metadata_downloader_controller.js +++ b/app/components/display/rdf_highlighter_component/rdf_highlighter_component_controller.js @@ -1,10 +1,8 @@ import { Controller } from '@hotwired/stimulus' import * as jsonld from 'jsonld' -import hljs from 'highlight.js/lib/core' -import xml from 'highlight.js/lib/languages/xml' -import json from 'highlight.js/lib/languages/json' +import { useHighLighter } from '../../../javascript/mixins/useHighLight' -// Connects to data-controller="metadata-downloader" +// Connects to data-controller="rdf-highlighter" export default class extends Controller { static targets = ['content', 'loader'] @@ -17,19 +15,24 @@ export default class extends Controller { connect () { this.formatedData = this.#formatData() + this.highlighter = useHighLighter(this.formatValue) + switch (this.formatValue) { case 'xml': - hljs.registerLanguage('xml', xml) this.showXML() break case 'json': - hljs.registerLanguage('json', json) this.showJSONLD() break case 'triples': - hljs.registerLanguage('xml', xml) this.showNTriples() break + case 'ntriples': + this.showNTriples() + break + case 'turtle': + this.showTURTLE() + break } } @@ -50,28 +53,42 @@ export default class extends Controller { } showNTriples () { - this.#toggleLoader() - this.#toNTriples(this.formatedData).then((nquads) => { - this.contentTarget.innerHTML = hljs.highlight(nquads, { language: 'xml' }).value + if (!this.hasMetadataValue) { + this.contentTarget.innerHTML = this.highlighter.highlight(this.contentTarget.textContent, 'ntriples') + } else { this.#toggleLoader() - }) + this.#toNTriples(this.formatedData).then((nquads) => { + this.contentTarget.innerHTML = this.highlighter.highlight(nquads, 'ntriples') + this.#toggleLoader() + }) + } } showXML () { - this.#toggleLoader() - this.contentTarget.innerHTML = hljs.highlight( - this.#toXML(this.formatedData, this.contextValue), - { language: 'xml' } - ).value - this.#toggleLoader() + if (!this.hasMetadataValue) { + this.contentTarget.innerHTML = this.highlighter.highlight(this.contentTarget.textContent, 'xml') + } else { + this.#toggleLoader() + const xml = this.#toXML(this.formatedData, this.contextValue) + this.contentTarget.innerHTML = this.highlighter.highlight(xml, 'xml') + this.#toggleLoader() + } } showJSONLD () { - this.#toggleLoader() - this.#toJSONLD().then((jsonld) => { - this.contentTarget.innerHTML = hljs.highlight(JSON.stringify(jsonld, null, ' '), { language: 'json' }).value + if (!this.hasMetadataValue) { + this.contentTarget.innerHTML = this.highlighter.highlight(JSON.stringify(JSON.parse(this.contentTarget.textContent), null, ' '), 'json') + } else { this.#toggleLoader() - }) + this.#toJSONLD().then((jsonld) => { + this.contentTarget.innerHTML = this.highlighter.highlight(JSON.stringify(jsonld, null, ' '), 'json') + this.#toggleLoader() + }) + } + } + + showTURTLE () { + this.contentTarget.innerHTML = this.highlighter.highlight(this.contentTarget.textContent, 'turtle') } #toggleLoader () { @@ -153,7 +170,7 @@ export default class extends Controller { const data = this.formatedData const resolveNamespace = this.namespacesValue let namespaces = {} - let xmlString = "" + let xmlString = '' delete data['@id'] delete data['@type'] @@ -161,7 +178,6 @@ export default class extends Controller { for (let prop in data) { const attr_uri = prop - // Replace the full URI by namespace:attr for (const ns in resolveNamespace) { if (prop.startsWith(resolveNamespace[ns])) { diff --git a/app/components/display/search_result_component.rb b/app/components/display/search_result_component.rb index 4249b9427..3a2505e87 100644 --- a/app/components/display/search_result_component.rb +++ b/app/components/display/search_result_component.rb @@ -1,6 +1,6 @@ class Display::SearchResultComponent < ViewComponent::Base + include UrlsHelper include ModalHelper - renders_many :subresults, Display::SearchResultComponent renders_many :reuses, Display::SearchResultComponent def initialize(number: 0,title: nil, ontology_acronym: nil ,uri: nil, definition: nil, link: nil, is_sub_component: false) @@ -34,6 +34,21 @@ def details_button end end + def mappings_button + link_to_modal(nil, "/ajax/mappings/get_concept_table?ontologyid=#{@ontology_acronym}&conceptid=#{escape(@uri)}&type=modal", data: { show_modal_title_value: @title, show_modal_size_value: 'modal-xl' }) do + content_tag(:div, class: 'button') do + inline_svg_tag('icons/ontology.svg') + + content_tag(:div, class: 'text d-flex') do + render(TurboFrameComponent.new(id: 'mapping_count', src: "/ajax/mappings/get_concept_table?ontologyid=#{@ontology_acronym}&conceptid=#{escape(@uri)}", loading: "lazy")) do |t| + t.loader do + render LoaderComponent.new(small: true) + end + end + content_tag(:div, 'mappings', class: 'ml-1') + end + end + end + end + def visualize_button link_to_modal(nil, "/ajax/biomixer/?ontology=#{@ontology_acronym}&conceptid=#{@uri}", data: { show_modal_title_value: @title, show_modal_size_value: 'modal-xl' }) do content_tag(:div, class: 'button') do diff --git a/app/components/display/search_result_component/search_result_component.html.haml b/app/components/display/search_result_component/search_result_component.html.haml index ba26bc004..376fbf473 100644 --- a/app/components/display/search_result_component/search_result_component.html.haml +++ b/app/components/display/search_result_component/search_result_component.html.haml @@ -11,8 +11,9 @@ .actions = details_button = visualize_button + = mappings_button - if subresults? - = reveal_ontologies_button("#{subresults.size} #{t('search.result_component.more_from_ontology')}", sub_ontologies_id, 'icons/ontology.svg') + = reveal_ontologies_button("#{subresults.size} #{t('search.result_component.more_from_ontology')}", sub_ontologies_id, 'icons/three-dots.svg') - if reuses? = reveal_ontologies_button("#{t('search.result_component.reuses_in')} #{reuses.size} ontologies", reuses_id, 'icons/reuses.svg') - if subresults? @@ -28,5 +29,4 @@ .search-result-sub-components - reuses.each do |reuse| .search-result-sub-component - = reuse - \ No newline at end of file + = reuse \ No newline at end of file diff --git a/app/components/layout/vertical_tabs_component.rb b/app/components/layout/vertical_tabs_component.rb new file mode 100644 index 000000000..c4709df27 --- /dev/null +++ b/app/components/layout/vertical_tabs_component.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Layout::VerticalTabsComponent < ViewComponent::Base + + renders_many :item_contents + + def initialize(titles: [], header: nil , selected: nil, url_parameter: nil) + @titles = titles + @selected = selected + @header = header + @url_parameter = url_parameter + end + +end diff --git a/app/components/layout/vertical_tabs_component/vertical_tabs_component.html.haml b/app/components/layout/vertical_tabs_component/vertical_tabs_component.html.haml new file mode 100644 index 000000000..ebc37bb14 --- /dev/null +++ b/app/components/layout/vertical_tabs_component/vertical_tabs_component.html.haml @@ -0,0 +1,20 @@ +- if @header + %div.mb-2 + .edit-ontology-title.mx-2{style: 'font-size: 30px'} + %div=@header + %hr + +.d-flex{data: {controller:'tabs-container'}} + .tabs-left-column + %div.nav.nav-pills.flex-column + - @titles.each_with_index do |key| + %a.vertical-tab-item.d-block{href: "##{key.parameterize}-tab", "data-toggle" => "pill", class: @selected.eql?(key.parameterize) ? 'active show' : '', data: {'tab-id': key.parameterize, 'tab-title': key.titleize, 'url-parameter': @url_parameter, action: 'click->tabs-container#selectTab'}} + = key.humanize + + .tabs-right-column.w-100 + %div.tab-content + - item_contents.each_with_index do |tab, index| + .tab-pane.fade.mx-4{id: @titles[index].parameterize+'-tab', class: @selected.eql?(@titles[index].parameterize) ? 'active show' : ''} + = tab + + diff --git a/app/components/link_field_component.rb b/app/components/link_field_component.rb index 573f69a64..3e5af446e 100644 --- a/app/components/link_field_component.rb +++ b/app/components/link_field_component.rb @@ -4,12 +4,15 @@ class LinkFieldComponent < ViewComponent::Base include ApplicationHelper, Turbo::FramesHelper, ComponentsHelper - def initialize(value:, raw: false, check_resolvability: false, enable_copy: true) + def initialize(value:, acronym: nil, raw: false, check_resolvability: false, enable_copy: true, generate_link: false, generate_htaccess: false) super @value = value @raw = raw @check_resolvability = check_resolvability @enable_copy = enable_copy + @acronym = acronym + @generate_link = generate_link + @generate_htaccess = generate_htaccess end def internal_link? @@ -17,9 +20,9 @@ def internal_link? end def link_tag - if internal_link? - url = @raw ? @value : @value.to_s.split("/").last - text = @raw ? @value : @value.to_s.sub("data.", "") + if !@raw && internal_link? + url = @value.to_s.split("/").last + text = @value.to_s.sub("data.", "") target = "" else url = @value.to_s @@ -28,7 +31,7 @@ def link_tag end tag = link_to(text, url, target: target, class: 'summary-link-truncate', 'data-controller': 'tooltip', title: text) - link_to_with_actions(tag, url: url, copy: @enable_copy, check_resolvability: @check_resolvability) + link_to_with_actions(tag, acronym: @acronym, url: url, copy: @enable_copy, check_resolvability: @check_resolvability, generate_link: @generate_link, generate_htaccess: @generate_htaccess) end end diff --git a/app/components/search_input_component/search_input_component.html.haml b/app/components/search_input_component/search_input_component.html.haml index d77759858..fb0d79f28 100644 --- a/app/components/search_input_component/search_input_component.html.haml +++ b/app/components/search_input_component/search_input_component.html.haml @@ -6,11 +6,18 @@ 'data-search-input-scroll-down-value': @scroll_down.to_s, 'data-search-input-selected-item-value': 0 } + - if @search_icon_type - %a{href: '/search', 'data-search-input-target': 'button'} - = inline_svg_tag 'arrow-right.svg', class: "home-search-button #{nav_icon_class}" + %div{class: "home-search-button #{nav_icon_class}"} + %a{href: '/search', 'data-search-input-target': 'button'} + .search-component-arrow + = inline_svg_tag 'arrow-right.svg' + .search-component-loader.d-none{'data-search-input-target': 'loader'} + = render LoaderComponent.new(small: true) - else + %a.d-none{'data-search-input-target': 'loader'} %a.d-none{'data-search-input-target': 'button'} + = render Input::InputFieldComponent.new(name: @name, placeholder: @placeholder, data: {'action': 'input->search-input#search blur->search-input#blur keydown.down->search-input#arrow_down keydown.up->search-input#arrow_up keydown.enter->search-input#enter_key', 'search-input-target': 'input'}) diff --git a/app/components/search_input_component/search_input_component_controller.js b/app/components/search_input_component/search_input_component_controller.js index f3322977c..5c8d07cad 100644 --- a/app/components/search_input_component/search_input_component_controller.js +++ b/app/components/search_input_component/search_input_component_controller.js @@ -1,9 +1,10 @@ import {Controller} from "@hotwired/stimulus" import useAjax from "../../javascript/mixins/useAjax"; +import debounce from 'debounce' // Connects to data-controller="search-input" export default class extends Controller { - static targets = ["input", "dropDown", "actionLink", "template", "button"] + static targets = ["input", "dropDown", "actionLink", "template", "button", "loader"] static values = { items: Array, ajaxUrl: String, @@ -19,11 +20,18 @@ export default class extends Controller { this.dropDown = this.dropDownTarget this.actionLinks = this.actionLinkTargets this.items = this.itemsValue + this.search = debounce(this.search.bind(this), 100); } search() { this.selectedItemValue = 0 - this.#searchInput() + this.loaderTarget.classList.remove("d-none") + this.buttonTarget.classList.add("d-none") + this.searchInput() + } + + searchInput() { + this.#fetchItems() } prevent(event){ @@ -74,7 +82,7 @@ export default class extends Controller { } else { useAjax({ type: "GET", - url: this.ajaxUrlValue + this.#inputValue(), + url: this.ajaxUrlValue + encodeURIComponent(this.#inputValue()), dataType: "json", success: (data) => { this.items = data.map(x => { return {...x, link: (this.itemLinkBaseValue + x[this.idKeyValue])}} ) @@ -89,6 +97,8 @@ export default class extends Controller { } #renderLines() { + this.loaderTarget.classList.add("d-none") + this.buttonTarget.classList.remove("d-none") const inputValue = this.#inputValue(); let results_list = [] if (inputValue.length > 0) { @@ -112,9 +122,8 @@ export default class extends Controller { let text = Object.values(item).reduce((acc, value) => acc + value, "") - // Check if the item contains the substring - if (text.toLowerCase().includes(inputValue.toLowerCase())) { + if (!this.cacheValue || text.toLowerCase().includes(inputValue.toLowerCase())) { results_list.push(item); breaker = breaker + 1 } @@ -149,13 +158,9 @@ export default class extends Controller { value = value.toString().split('/').slice(-1) } const regex = new RegExp('\\b' + key + '\\b', 'gi'); - string = string.replace(regex, value.toString()) + string = string.replace(regex, value ? value.toString() : "") }) return new DOMParser().parseFromString(string, "text/html").body.firstElementChild } - - #searchInput() { - this.#fetchItems() - } } diff --git a/app/components/table_component/table_component_controller.js b/app/components/table_component/table_component_controller.js index 007602825..1e9f4e0d6 100644 --- a/app/components/table_component/table_component_controller.js +++ b/app/components/table_component/table_component_controller.js @@ -10,13 +10,12 @@ export default class extends Controller { searching: Boolean, noinitsort: Boolean } - connect(){ - let table_component - table_component = this.element.childNodes[1] - let default_sort_column - default_sort_column = parseInt(this.sortcolumnValue, 10) - if (this.sortcolumnValue || this.searchingValue){ - let table = new DataTable('#'+table_component.id, { + connect(){ + const table_component = this.element.querySelector('table') + const default_sort_column = parseInt(this.sortcolumnValue, 10) + + if (this.sortcolumnValue || this.searchingValue || this.pagingValue){ + this.table = new DataTable('#'+table_component.id, { paging: this.pagingValue, info: false, searching: this.searchingValue, diff --git a/app/components/tree_link_component.rb b/app/components/tree_link_component.rb index 81f510fb5..91eb8b054 100644 --- a/app/components/tree_link_component.rb +++ b/app/components/tree_link_component.rb @@ -1,17 +1,17 @@ # frozen_string_literal: true class TreeLinkComponent < ViewComponent::Base - include MultiLanguagesHelper - def initialize(child:, href:, children_href: , selected: false , data: {}, muted: false, target_frame: nil) + include MultiLanguagesHelper, ModalHelper, ApplicationHelper + def initialize(child:, href:, children_href: , selected: false , data: {}, muted: false, target_frame: nil, open_in_modal: false) @child = child @active_style = selected ? 'active' : '' #@icons = child.relation_icon(node) @muted_style = muted ? 'text-muted' : '' @href = href @children_link = children_href - label = @child.prefLabel rescue @child.id + label = (@child.prefLabel || @child.label) rescue @child.id if label.nil? - @pref_label_html = child.id.split('/').last + @pref_label_html = link_last_part(child.id) else pref_label_lang, @pref_label_html = select_language_label(label) pref_label_lang = pref_label_lang.to_s.upcase @@ -26,6 +26,8 @@ def initialize(child:, href:, children_href: , selected: false , data: {}, muted @data.merge!(data) do |_, old, new| "#{old} #{new}" end + + @open_in_modal = open_in_modal end diff --git a/app/components/tree_link_component/tree_link_component.html.haml b/app/components/tree_link_component/tree_link_component.html.haml index 2bf3815b0..c66ef7131 100644 --- a/app/components/tree_link_component/tree_link_component.html.haml +++ b/app/components/tree_link_component/tree_link_component.html.haml @@ -1,8 +1,11 @@ %li{id:li_id , class: open?} = open_children_link - %a{id: @child.id, data: @data, title: @tooltip, - href: @href, class: "tree-link #{@muted_style} #{@active_style} #{border_left} #{open?}"} - = @pref_label_html + - if @open_in_modal + = link_to_modal(@pref_label_html, @href, data: { show_modal_size_value: 'modal-xl' } ) + - else + %a{id: @child.id, data: @data, title: @tooltip, + href: @href, class: "tree-link #{@muted_style} #{@active_style} #{border_left} #{open?}"} + = @pref_label_html - if @child.hasChildren && !@child.expanded? = render TurboFrameComponent.new(id: "#{child_id}_childs") diff --git a/app/controllers/admin/search_controller.rb b/app/controllers/admin/search_controller.rb new file mode 100644 index 000000000..7f87d9263 --- /dev/null +++ b/app/controllers/admin/search_controller.rb @@ -0,0 +1,89 @@ +class Admin::SearchController < ApplicationController + include TurboHelper + layout :determine_layout + before_action :authorize_admin + + SEARCH_URL = "#{LinkedData::Client.settings.rest_url}/admin/search" + + def index + json = LinkedData::Client::HTTP.get("#{SEARCH_URL}/collections") + @collections = json.collections + end + + def index_batch + response = {} + model = params[:model_name] + + if model.blank? + render_turbo_stream(alert(type: 'danger') { 'No model selected' }) + return + end + + begin + response[:success] = LinkedData::Client::HTTP.post("#{SEARCH_URL}/index_batch/#{model}", {}) + rescue StandardError => e + response[:errors] = e + end + + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render_turbo_stream alert(type: 'success') { response[:success] } + end + end + end + end + + def init_schema + response = {} + collection = params[:collection] + + if collection.blank? + render_turbo_stream(alert(type: 'danger') { 'No collection selected' }) + return + end + + begin + response[:success] = LinkedData::Client::HTTP.post("#{SEARCH_URL}/collections/#{collection}/schema/init", {}) + rescue StandardError => e + response[:errors] = e + end + + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render_turbo_stream alert(type: 'success') { "Collection #{collection} schema initialized" } + end + end + end + end + + def show + json = LinkedData::Client::HTTP.get("#{SEARCH_URL}/collections/#{params[:collection]}/schema", {}, raw: true) + @collection = JSON.parse(json.to_s) + + @fields = @collection["fields"] + @collection["dynamicFields"] + @collection["copyFields"] + render 'show', layout: false + end + + def search + query = params[:query] || '*' + page = (params[:page] || 1).to_i + page_size = (params[:page_size] || 10).to_i + start = (page - 1) * page_size + + json = LinkedData::Client::HTTP.post("#{SEARCH_URL}/collections/#{params[:collection]}/search", + { q: query, start: start, rows: page_size }, + raw: true) + response = JSON.parse(json.to_s) + @count = response["response"]["numFound"] + @docs = response["response"]["docs"] + + render 'search', layout: false + end + +end diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 7aaa1a394..2c62d0db1 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -379,7 +379,7 @@ def user_visits_data return visits_data if analytics.empty? - analytics.each do |year, year_data| + analytics.sort.each do |year, year_data| year_data.each do |month, value| visits_data[:visits] << value visits_data[:labels] << DateTime.parse("#{year}/#{month}").strftime("%b %Y") @@ -424,8 +424,7 @@ def ontology_visits_data end - - aggregated_data.each do |year, year_data| + aggregated_data.sort.each do |year, year_data| year_data.each do |month, value| visits_data[:visits] << value visits_data[:labels] << DateTime.parse("#{year}/#{month}").strftime("%b %Y") @@ -443,6 +442,7 @@ def page_visits_data visits_data = { visits: [], labels: [] } return visits_data if analytics.empty? + analytics.each do |path, count| visits_data[:labels] << path visits_data[:visits] << count diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 725208924..04215413c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -193,19 +193,9 @@ def bp_config_json def rest_url - # Split the URL into protocol and path parts - protocol, path = REST_URI.split("://", 2) - - # Remove duplicate "//" - cleaned_url = REST_URI.gsub(/\/\//, '/') - - # Remove the last '/' in the path part - cleaned_path = path.chomp('/') - # Reconstruct the cleaned URL - "#{protocol}://#{cleaned_path}" + helpers.rest_url end - - + def check_http_file(url) session = Net::HTTP.new(url.host, url.port) session.use_ssl = true if url.port == 443 @@ -422,20 +412,21 @@ def get_class(params) if ignore_concept_param # get the top level nodes for the root # TODO_REV: Support views? Replace old view call: @ontology.top_level_classes(view) - @roots = @ontology.explore.roots(concept_schemes: params[:concept_schemes]) - if @roots.nil? || @roots.empty? + @roots = @ontology.explore.roots(concept_schemes: params[:concept_schemes]) rescue nil + + if @roots.nil? || response_error?(@roots) || @roots.compact&.empty? LOG.add :debug, t('application.missing_roots_for_ontology', acronym: @ontology.acronym) classes = @ontology.explore.classes.collection - @concept = classes.first.explore.self(full: true) if classes.first + @concept = classes.first.explore.self(full: true) if classes&.first return end @root = LinkedData::Client::Models::Class.new(read_only: true) - @root.children = @roots.sort{|x,y| (x.prefLabel || "").downcase <=> (y.prefLabel || "").downcase} + @root.children = @roots.sort{|x,y| (x&.prefLabel || "").downcase <=> (y&.prefLabel || "").downcase} # get the initial concept to display root_child = @root.children&.first - not_found(t('application.missing_roots', id: @roots.id)) if root_child.nil? + not_found(t('application.missing_roots')) if root_child.nil? @concept = root_child.explore.self(full: true, lang: lang) # Some ontologies have "too many children" at their root. These will not process and are handled here. @@ -455,7 +446,7 @@ def get_class(params) rootNode = @concept.explore.tree(include: include, concept_schemes: params[:concept_schemes], lang: lang) if rootNode.nil? || rootNode.empty? @roots = @ontology.explore.roots(concept_schemes: params[:concept_schemes]) - if @roots.nil? || @roots.empty? + if @roots.nil? || response_error?(@roots) || @roots.compact&.empty? LOG.add :debug, t('application.missing_roots_for_ontology', acronym: @ontology.acronym) @concept = @ontology.explore.classes.collection.first.explore.self(full: true) return @@ -710,9 +701,8 @@ def init_trial_license # Get the submission metadata from the REST API. def submission_metadata - @metadata ||= JSON.parse(LinkedData::Client::HTTP.get("#{REST_URI}/submission_metadata", {}, raw: true)) + @metadata ||= helpers.submission_metadata end - helper_method :submission_metadata def request_lang helpers.request_lang diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb index 981c3e301..14f697902 100644 --- a/app/controllers/collections_controller.rb +++ b/app/controllers/collections_controller.rb @@ -1,6 +1,44 @@ class CollectionsController < ApplicationController - include CollectionsHelper + include CollectionsHelper,SearchContent + + + def index + acronym = params[:ontology] + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(acronym).first + ontology_not_found(acronym) if @ontology.nil? + + + collection_id = params[:collectionid] + @collection = get_collection(@ontology, collection_id) if collection_id + + + if params[:search].blank? + @collections = get_collections(@ontology) + + render partial: 'collections/list_view' + else + + query, page, page_size = helpers.search_content_params + + results, _, next_page, total_count = search_ontologies_content(query: query, + page: page, + page_size: page_size, + filter_by_ontologies: [acronym], + filter_by_types: ['Collection']) + + + render inline: helpers.render_search_paginated_list(container_id: 'collections_sorted_list', + next_page_url: "/ontologies/#{@ontology.acronym}/collections", + child_url: "/ontologies/#{@ontology.acronym}/collections/show", child_turbo_frame: 'collection', + child_param: :collectionid, + results: results, next_page: next_page, total_count: total_count + ) + end + end + def show + redirect_to(ontology_path(id: params[:ontology_id], p: 'collections', collectionid: params[:id], lang: request_lang)) and return unless turbo_frame_request? + @collection = get_request_collection end diff --git a/app/controllers/concepts_controller.rb b/app/controllers/concepts_controller.rb index 127621e9f..96a5fd0b7 100644 --- a/app/controllers/concepts_controller.rb +++ b/app/controllers/concepts_controller.rb @@ -7,7 +7,7 @@ class ConceptsController < ApplicationController layout 'ontology' - def show_concept + def show params[:id] = params[:id] ? params[:id] : params[:conceptid] if params[:id].nil? || params[:id].empty? @@ -16,8 +16,10 @@ def show_concept end # Note that find_by_acronym includes views by default - @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology_id]).first - ontology_not_found(params[:ontology_id]) if @ontology.nil? + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology]).first + ontology_not_found(params[:ontology]) if @ontology.nil? + + redirect_to(ontology_path(id: params[:ontology], p: 'classes', conceptid: params[:id], lang: request_lang)) and return unless turbo_frame_request? @submission = get_ontology_submission_ready(@ontology) @ob_instructions = helpers.ontolobridge_instructions_template(@ontology) @@ -26,11 +28,10 @@ def show_concept concept_not_found(params[:id]) if @concept.nil? @notes = @concept.explore.notes - - render :partial => 'show' + render partial: 'show' end - def show + def index # Handle multiple methods of passing concept ids params[:id] = params[:id] ? params[:id] : params[:conceptid] diff --git a/app/controllers/concerns/ontology_content_serializer.rb b/app/controllers/concerns/ontology_content_serializer.rb new file mode 100644 index 000000000..2d7dc1904 --- /dev/null +++ b/app/controllers/concerns/ontology_content_serializer.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module OntologyContentSerializer + extend ActiveSupport::Concern + + def serialize_content(ontology_acronym:, concept_id:, format:) + if ontology_acronym && concept_id + @format = format + @result = "" + @acronym = ontology_acronym + + @format = 'ntriples' if format.eql?('html') + + url = content_finder_url(ontology_acronym, concept_id) + accept_header = content_finder_accept_header(@format) + conn = Faraday.new(url: url) do |faraday| + faraday.headers['Accept'] = accept_header + faraday.adapter Faraday.default_adapter + faraday.headers['Authorization'] = "apikey token=#{get_apikey}" + end + response = conn.get + @result = response.body.force_encoding(Encoding::UTF_8) + end + [@result, accept_header] + end + + def content_finder_url(acronym, uri) + URI.parse("#{rest_url}/ontologies/#{acronym.strip}/resolve/#{helpers.escape(uri.strip)}") + end + + def content_finder_accept_header(output_format) + case output_format + when 'json', 'application/json', 'application/ld+json', 'application/*' + 'application/ld+json' + when 'xml', 'text/xml', 'text/rdf+xml', 'application/rdf+xml', 'application/xml' + 'application/rdf+xml' + when 'ntriples', 'application/n-triples', '*/*', 'text/*', 'text/n3' + 'application/n-triples' + when 'turtle', 'text/turtle' + 'text/turtle' + else + output_format + end + end + +end diff --git a/app/controllers/concerns/ontology_updater.rb b/app/controllers/concerns/ontology_updater.rb index 5f3d4ad6d..71672178c 100644 --- a/app/controllers/concerns/ontology_updater.rb +++ b/app/controllers/concerns/ontology_updater.rb @@ -98,7 +98,7 @@ def copyable_submission_params?(key, value) return false if value.nil? || (value.respond_to?(:empty?) && value.empty?) attr_to_not_copy = [:versionIRI, :version, :deprecated, :valid, :curatedOn, - :pullLocation, :metadataVoc, :hasPriorVersion, + :pullLocation, :metadataVoc, :hasPriorVersion, :creationDate, :submissionStatus] !attr_to_not_copy.include?(key.to_sym) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 9ba970f7b..c18e70ce4 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -1,4 +1,5 @@ module SearchAggregator + include UrlsHelper extend ActiveSupport::Concern BLACKLIST_FIX_STR = [ "https://", @@ -55,7 +56,7 @@ def search_result_elem(class_object, ontology_acronym, title) uri: class_object.id.to_s, title: title.empty? ? label : "#{label} - #{title}", ontology_acronym: ontology_acronym, - link: "/ontologies/#{ontology_acronym}?p=classes&conceptid=#{class_object.id}", + link: "/ontologies/#{ontology_acronym}?p=classes&conceptid=#{escape(class_object.id)}#{helpers.request_lang&.eql?("ALL") ? '' : "&language="+helpers.request_lang.to_s}", definition: Array(class_object.definition) } end @@ -63,7 +64,7 @@ def search_result_elem(class_object, ontology_acronym, title) def concept_label(pref_labels_list, obsolete = false, max_length = 60) # select closest to query selected = pref_labels_list.select do |pref_lab| - pref_lab.include?(@search_query) || @search_query.include?(pref_lab) + pref_lab.downcase.include?(@search_query.downcase) || @search_query.downcase.include?(pref_lab.downcase) end.first selected ||= (pref_labels_list&.first || '') @@ -75,7 +76,6 @@ def concept_label(pref_labels_list, obsolete = false, max_length = 60) def ontology_name_acronym(ontologies, selected_acronym) ontology = ontologies.select { |x| x.acronym.eql?(selected_acronym.split('/').last) }.first - binding.pry if ontology.nil? "#{ontology.name} (#{ontology.acronym})" end @@ -202,7 +202,7 @@ def ontology_owner_of_class(cls_id, ontologies, query) def ontology_own_class?(cls_id, acronym, blacklist_words) cls_id = blacklist_cls_id_components(cls_id.dup, blacklist_words) - cls_id.upcase.include?(acronym) rescue binding.pry + cls_id.upcase.include?(acronym) end def blacklist_cls_id_components(cls_id, blacklist_words) diff --git a/app/controllers/concerns/search_content.rb b/app/controllers/concerns/search_content.rb new file mode 100644 index 000000000..e8fd48a59 --- /dev/null +++ b/app/controllers/concerns/search_content.rb @@ -0,0 +1,181 @@ +module SearchContent + extend ActiveSupport::Concern + + def search_ontologies(query: '*', groups: [], categories: [], languages: [], private_only: false, formats: [], + is_of_type: [], formality_level: [], + show_views: false, status: 'alpha,beta,production', + page: 1, page_size: 10) + + visibility = private_only ? "private" : 'public' + qf = [ + "ontology_acronymSuggestEdge^25 ontology_nameSuggestEdge^15 descriptionSuggestEdge^10 ", # start of the word first + "ontology_acronym_text^15 ontology_name_text^10 description_text^5 " + ] + submissions = LinkedData::Client::HTTP.get('search/ontologies', + { query: query.blank? ? "*" : query, + groups: groups, + hasDomain: categories, + hasOntologyLanguage: formats, + status: status, + page: page, pagesize: page_size, + visibility: visibility, + isOfType: is_of_type, + hasFormat: formality_level, + show_views: show_views, + qf: qf, # custom ranking + languages: languages } + .reject { |k, v| v.blank? }) + + submissions = submissions.collection + + submissions.map do |os| + transformed_os = OpenStruct.new + ontology = OpenStruct.new + metrics = OpenStruct.new + os.each_pair do |key, value| + if key.to_s.start_with?("ontology_") + ontology[key.to_s.sub("ontology_", "").gsub(/_.*\z/, "")] = value + elsif key.to_s.start_with?("metrics_") + metrics[key.to_s.sub("metrics_", "").gsub(/_.*\z/, "")] = value + elsif key != :links && key != :context + new_key = key.to_s.gsub(/_.*\z/, "") + transformed_os[new_key] = value + end + end + transformed_os[:ontology] = ontology unless ontology.to_h.empty? + transformed_os[:metrics] = metrics unless metrics.to_h.empty? + transformed_os + end + end + + def search_ontologies_content(query:, page: 1, page_size: 10, filter_by_ontologies: [], filter_by_types: []) + acronyms = filter_by_ontologies + original_query = query + types = filter_by_types + query = query.gsub(':', '\:').gsub('/', '\/') if page.eql?(1) + + qf = [ + "ontology_t^100 resource_id^10", + "http___www.w3.org_2004_02_skos_core_prefLabel_txt^30", + "http___www.w3.org_2004_02_skos_core_prefLabel_t^30", + "http___www.w3.org_2000_01_rdf-schema_label_txt^30", + "http___www.w3.org_2000_01_rdf-schema_label_t^30", + ] + ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym,name,viewOf', also_include_views: true) + selected_onto = [] + + q = query.split(' ').first || '' + selected_onto += ontologies.select { |x| (acronyms.empty? && x.acronym.downcase.eql?(q.downcase)) || acronyms.include?(x.acronym) } + + selected_onto.uniq! + [selected_onto.first].compact.each do |o| + acr = o.acronym + acronyms << acr + query.gsub!(/\b#{acr}\b/, "") + query.gsub!(/\b#{acr.downcase}\b/, "") + query.gsub!('-', " ") + end + + query = query + if query.blank? + query = "*" + elsif query.split(' ').size > 1 + query = "^#{query}*" + else + query = "*#{query}*" + end + + results = search_content( q: query, qf: qf.join(' '), page: page, pagesize: page_size, ontologies: acronyms.first, types: types.join(',')) + [search_content_result_to_json(original_query, query, results, ontologies, selected_onto), results.page,results.nextPage, results.totalCount] + end + + + + def search_content(params) + LinkedData::Client::HTTP.get('search/ontologies/content', params) + end + + private + + def search_content_result_to_json(query, changed_query, results, ontologies, selected_onto = []) + json = [] + selected_onto = selected_onto.empty? ? ontologies.select { |x| x.name.downcase.include?(query.downcase) || x.acronym.downcase.include?(query.downcase) } : selected_onto + + json += selected_onto.map do |x| + { + id: ontology_path(id: x.acronym, p: 'summary'), + name: x.name, + acronym: x.acronym, + type: x.viewOf.blank? ? 'Ontology' : 'Ontology View', + label: nil + } + end + + changed_query.gsub!('*', '') + + json += results.collection.map do |x| + acronym = x.ontology_t || x.submission_id_t.split('/')[-3] + next nil unless acronym + + label = nil + [ + "http___www.w3.org_2000_01_rdf-schema_label_t", + "http___www.w3.org_2000_01_rdf-schema_label_txt", + "http___www.w3.org_2004_02_skos_core_prefLabel_t", + "http___www.w3.org_2004_02_skos_core_prefLabel_txt", + ].each do |v| + v = Array(x[v]) + selected_label = v&.select { |p| p.downcase[changed_query.strip.downcase] || changed_query.downcase[p.strip.downcase] }&.first + label = selected_label if selected_label + label ||= v&.first + end + + + type = id_type(x.type_t, x.type_txt) + { + id: link_by_type(x.resource_id, acronym, type), + name: x.resource_id, + acronym: acronym, + type: type || '', + label: label + } + end.compact + + json + end + + def supported_types + %w[Concept Class Ontology ConceptScheme Collection NamedIndividual AnnotationProperty ObjectProperty DatatypeProperty] + end + + def id_type(type_t, type_txt) + + type = (Array(type_t) + Array(type_txt)).map { |x| helpers.link_last_part(x) } + .select{|x| supported_types.include?(x)} + + type = Array(type).reject { |x| x.eql?("NamedIndividual") } if (Array(type).size > 1) + + type.first + end + + def link_by_type(id, ontology, type) + case type + when 'Concept', 'Class' + ontology_path(id: ontology, p: 'classes', conceptid: id) + when 'Ontology' + ontology_path(id: ontology, p: 'summary') + when 'ConceptScheme' + ontology_path(id: ontology, p: 'schemes', schemeid: id) + when 'Collection' + ontology_path(id: ontology, p: 'collections', collectionid: id) + when 'NamedIndividual' + ontology_path(id: ontology, p: 'instances', instanceid: id) + when 'AnnotationProperty', 'ObjectProperty', 'DatatypeProperty' + ontology_path(id: ontology, p: 'properties', propertyid: id) + else + "/content_finder?acronym=#{ontology}&uri=#{helpers.escape(id)}&output_format=html" + end + end + +end + diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index 6cda41406..c8677e601 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -1,6 +1,8 @@ module SubmissionFilter extend ActiveSupport::Concern + include SearchContent + BROWSE_ATTRIBUTES = ['ontology', 'submissionStatus', 'description', 'pullLocation', 'creationDate', 'contact', 'released', 'naturalLanguage', 'hasOntologyLanguage', 'hasFormalityLevel', 'isOfType', 'deprecated', 'status', 'metrics'] @@ -10,34 +12,53 @@ def init_filters(params) @show_private_only = params[:private_only]&.eql?('true') @show_retired = params[:show_retired]&.eql?('true') @selected_format = params[:format] - @selected_sort_by = params[:sort_by].blank? ? 'visits' : params[:sort_by] + @sort_by = params[:sort_by].blank? ? 'visits' : params[:sort_by] @search = params[:search] end def submissions_paginate_filter(params) - request_params = filters_params(params, page: nil) + request_params = filters_params(params, page: params[:page], pagesize: 10) + filter_params = params.permit(@filters.keys).to_h init_filters(params) - # pagination disabled because is not supported by 4store, - # see https://github.com/ontoportal-lirmm/ontologies_api/issues/25 - # @page = LinkedData::Client::Models::OntologySubmission.all(request_params) - @page = OpenStruct.new(page: 1, next_page: nil) - submissions = LinkedData::Client::Models::OntologySubmission.all(request_params) - @analytics = helpers.ontologies_analytics + + @analytics = Rails.cache.fetch("ontologies_analytics-#{Time.now.year}-#{Time.now.month}") do + helpers.ontologies_analytics + end + + @ontologies = LinkedData::Client::Models::Ontology.all(include: 'all', also_include_views: true, display_links: false, display_context: false) # get fair scores of all ontologies @fair_scores = fairness_service_enabled? ? get_fair_score('all') : nil - submissions = submissions.reject { |sub| sub.ontology.nil? }.map { |sub| ontology_hash(sub) } - if @selected_sort_by.eql?('visits') - submissions = submissions.sort_by { |x| -x[:popularity] } - elsif @selected_sort_by.eql?('fair') - submissions = submissions.sort_by { |x| -x[:fairScore] } - elsif @selected_sort_by.eql?('notes') - submissions = submissions.sort_by { |x| -x[:note_count] } - elsif @selected_sort_by.eql?('projects') - submissions = submissions.sort_by { |x| -x[:project_count] } + @total_ontologies = @ontologies.size + search_backend = params[:search_backend] + params = { query: @search, + status: request_params[:status], + show_views: @show_views, + private_only: @show_private_only, + languages: request_params[:naturalLanguage], + page_size: @total_ontologies, + formality_level: request_params[:hasFormalityLevel], + is_of_type: request_params[:isOfType], + groups: request_params[:group], categories: request_params[:hasDomain], + formats: request_params[:hasOntologyLanguage] } + + + if search_backend.eql?('index') + submissions = filter_using_index(**params) + submissions = @ontologies.map{ |ont| ontology_hash(ont, submissions) } + else + submissions = filter_using_data(@ontologies, **params) end - submissions + + submissions = sort_submission_by(submissions, @sort_by, @search) + + + @page = paginate_submissions(submissions, request_params[:page].to_i, request_params[:pagesize].to_i) + + count = @page.page.eql?(1) ? count_objects(submissions) : {} + + [@page.collection, @page.totalCount, count, filter_params] end def ontologies_filter_url(filters, page: 1, count: false) @@ -46,10 +67,98 @@ def ontologies_filter_url(filters, page: 1, count: false) private + def filter_using_index(query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) + search_ontologies( + query: query, + status: status, + show_views: show_views, + private_only: private_only, + languages: languages, + page_size: page_size, + formality_level: formality_level, + is_of_type: is_of_type, + groups: groups, categories: categories, + formats: formats + ) + + end + + def filter_using_data(ontologies, query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) + submissions = LinkedData::Client::Models::OntologySubmission.all(include: BROWSE_ATTRIBUTES.join(','), also_include_views: true, display_links: false, display_context: false) + submissions = ontologies.map { |ont| ontology_hash(ont, submissions) } + + submissions.map do |s| + out = ((s.ontology.viewingRestriction.eql?('public') && !private_only) || private_only && s.ontology.viewingRestriction.eql?('private')) + out = out && (groups.blank? || (s.ontology.group.map { |x| helpers.link_last_part(x) } & groups.split(',')).any?) + out = out && (categories.blank? || (s.ontology.hasDomain.map { |x| helpers.link_last_part(x) } & categories.split(',')).any?) + out = out && (status.blank? || status.eql?('alpha,beta,production,retired') || status.split(',').include?(s.status)) + out = out && (formats.blank? || formats.split(',').any? { |f| s.hasOntologyLanguage.eql?(f) }) + out = out && (is_of_type.blank? || is_of_type.split(',').any? { |f| helpers.link_last_part(s.isOfType).eql?(f) }) + out = out && (formality_level.blank? || formality_level.split(',').any? { |f| helpers.link_last_part(s.hasFormalityLevel).eql?(f) }) + out = out && (languages.blank? || languages.split(',').any? { |f| s.naturalLanguage.any? { |n| helpers.link_last_part(n).eql?(f) } }) + out = out && (s.ontology.viewOf.blank? || (show_views && !s.ontology.viewOf.blank?)) + + out = out && (query.blank? || [s.description, s.ontology.name, s.ontology.acronym].any? { |x| (x|| '').downcase.include?(query.downcase) }) + + if out + s[:rank] = 0 + + next s if query.blank? + + s[:rank] += 3 if s.ontology.acronym && s.ontology.acronym.downcase.include?(query.downcase) + + s[:rank] += 2 if s.ontology.name && s.ontology.name.downcase.include?(query.downcase) + + s[:rank] += 1 if s.description && s.description.downcase.include?(query.downcase) + + s + else + nil + end + + end.compact + end + + def paginate_submissions(all_submissions, page, size) + current_page = page + page_size = size + + start_index = (current_page - 1) * page_size + end_index = start_index + page_size - 1 + next_page = current_page * page_size < all_submissions.size ? current_page + 1 : nil + OpenStruct.new(page: current_page, nextPage: next_page, totalCount: all_submissions.size, + collection: all_submissions[start_index..end_index]) + end + + def sort_submission_by(submissions, sort_by, query = nil) + return submissions.sort_by { |x| x[:rank] ? -x[:rank] : 0} unless query.blank? + + if sort_by.eql?('visits') + submissions = submissions.sort_by { |x| -(x[:popularity] || 0) } + elsif sort_by.eql?('fair') + submissions = submissions.sort_by { |x| -x[:fairScore] } + elsif sort_by.eql?('notes') + submissions = submissions.sort_by { |x| -x[:note_count] } + elsif sort_by.eql?('projects') + submissions = submissions.sort_by { |x| -x[:project_count] } + elsif sort_by.eql?('metrics_classes') + submissions = submissions.sort_by { |x| -x[:class_count] } + elsif sort_by.eql?('metrics_individuals') + submissions = submissions.sort_by { |x| -x[:individual_count] } + elsif sort_by.eql?('creationDate') + submissions = submissions.sort_by { |x| x[:creationDate] || '' }.reverse + elsif sort_by.eql?('released') + submissions = submissions.sort_by { |x| x[:released] || '' }.reverse + elsif sort_by.eql?('ontology_name') + submissions = submissions.sort_by { |x| -x[:name] } + end + submissions + end + def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pagesize: 5) request_params = { display_links: false, display_context: false, include: includes, include_status: 'RDF' } - request_params.merge!(page: page, pagesize: pagesize) if page + request_params.merge!(page: page.to_i, pagesize: pagesize.to_i) if page filters_values_map = { categories: :hasDomain, groups: :group, @@ -83,7 +192,6 @@ def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pages @filters[:show_retired] = 'true' end - filters_values_map.each do |filter, api_key| next if params[filter].nil? || params[filter].empty? @@ -93,19 +201,31 @@ def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pages end end + unless params[:sort_by].blank? + @filters[:sort_by] = params[:sort_by] + end + + unless params[:search].blank? + @filters[:search] = params[:search] + end + request_params.delete(:order_by) if %w[visits fair].include?(request_params[:sort_by].to_s) request_params end - def ontology_hash(sub) + def ontology_hash(ont, submissions) o = {} - ont = sub.ontology + sub = submissions.select{|x| x.ontology&.id.eql?(ont.id)}.first + + o[:ontology] = ont add_ontology_attributes(o, ont) add_submission_attributes(o, sub) add_fair_score_metrics(o, ont) - if sub.metrics + o[:hasOntologyLanguage] = sub&.hasOntologyLanguage + + if sub&.metrics o[:class_count] = sub.metrics.classes o[:individual_count] = sub.metrics.individuals else @@ -115,23 +235,17 @@ def ontology_hash(sub) o[:class_count_formatted] = number_with_delimiter(o[:class_count], delimiter: ',') o[:individual_count_formatted] = number_with_delimiter(o[:individual_count], delimiter: ',') - o[:note_count] = ont.notes.length - o[:project_count] = ont.projects.length + o[:note_count] = ont.notes&.length || 0 + o[:project_count] = ont.projects&.length || o[:popularity] = @analytics[ont.acronym] || 0 + o[:rank] = sub&[:rank] || 0 - # if o[:type].eql?('ontology_view') - # unless ontologies_hash[ont.viewOf].blank? - # o[:viewOfOnt] = { - # name: ontologies_hash[ont.viewOf].name, - # acronym: ontologies_hash[ont.viewOf].acronym - # } - # end - # end - - o + OpenStruct.new(o) end def add_submission_attributes(ont_hash, sub) + return if sub.nil? + ont_hash[:submissionStatus] = sub.submissionStatus ont_hash[:deprecated] = sub.deprecated ont_hash[:status] = sub.status @@ -144,12 +258,12 @@ def add_submission_attributes(ont_hash, sub) ont_hash[:hasFormalityLevel] = sub.hasFormalityLevel ont_hash[:isOfType] = sub.isOfType ont_hash[:submissionStatusFormatted] = submission_status2string(sub).gsub(/\(|\)/, '') - ont_hash[:format] = sub.hasOntologyLanguage - ont_hash[:contact] = sub.contact.map(&:name).first unless sub.contact.nil? + ont_hash[:format] = sub.hasOntologyLanguage&.split('/').last + ont_hash[:contact] = sub.contact.map { |c| c.is_a?(String) ? c.split('|').first : c.name }.first unless sub.contact.nil? end def add_ontology_attributes(ont_hash, ont) - return if ont.nil? + return if ont.nil? ont_hash[:id] = ont.id ont_hash[:type] = ont.viewOf.nil? ? 'ontology' : 'ontology_view' @@ -182,11 +296,11 @@ def ontology_filters_init(categories, groups) end @formalityLevel = submission_metadata.select { |x| x['@id']['hasFormalityLevel'] }.first['enforcedValues'].map do |id, name| - { 'id' => id, 'name' => name, 'acronym' => name.camelize(:lower), 'value' => name.delete(' ')} + { 'id' => id, 'name' => helpers.link_last_part(id), 'acronym' => name, 'value' => helpers.link_last_part(id) } end @isOfType = submission_metadata.select { |x| x['@id']['isOfType'] }.first['enforcedValues'].map do |id, name| - { 'id' => id, 'name' => name, 'acronym' => name.camelize(:lower), 'value' => name.delete(' ') } + { 'id' => id, 'name' => helpers.link_last_part(id), 'acronym' => name, 'value' => helpers.link_last_part(id) } end @formats = [[t("submissions.filter.all_formats"), ''], 'OBO', 'OWL', 'SKOS', 'UMLS'] @@ -217,10 +331,10 @@ def ontology_filters_init(categories, groups) { categories: object_filter(categories, :categories), groups: object_filter(groups, :groups), - naturalLanguage: object_filter(@languages, :naturalLanguage), + naturalLanguage: object_filter(@languages, :naturalLanguage, "value"), hasFormalityLevel: object_filter(@formalityLevel, :hasFormalityLevel), - isOfType: object_filter(@isOfType, :isOfType), - #missingStatus: object_filter(@missingStatus, :missingStatus) + isOfType: object_filter(@isOfType, :isOfType, "value"), + # missingStatus: object_filter(@missingStatus, :missingStatus) } end @@ -229,12 +343,9 @@ def check_id(name_value, objects, name_key) selected_category.first && selected_category.first['id'] end - def object_checks(key) - params[key]&.split(',') - end def object_filter(objects, object_name, name_key = 'acronym') - checks = object_checks(object_name) || [] + checks = params[object_name]&.split(',') || [] checks = checks.map { |x| check_id(x, objects, name_key) }.compact ids = objects.map { |x| x['id'] } @@ -246,10 +357,10 @@ def count_objects(ontologies) objects_count = {} @categories = LinkedData::Client::Models::Category.all(display_links: false, display_context: false) @groups = LinkedData::Client::Models::Group.all(display_links: false, display_context: false) + @filters = ontology_filters_init(@categories, @groups) object_names = @filters.keys - @filters.each do |filter, values| objects = values.first objects_count[filter] = objects.map { |v| [v['id'], 0] }.to_h @@ -259,6 +370,8 @@ def count_objects(ontologies) object_names.each do |name| values = Array(ontology[name]) values.each do |v| + v.gsub!('http://data.bioontology.org', rest_url) + objects_count[name] = {} unless objects_count[name] objects_count[name][v] = (objects_count[name][v] || 0) + 1 end diff --git a/app/controllers/concerns/uri_redirection.rb b/app/controllers/concerns/uri_redirection.rb new file mode 100644 index 000000000..9ef86fcd4 --- /dev/null +++ b/app/controllers/concerns/uri_redirection.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module UriRedirection + extend ActiveSupport::Concern + + include SearchContent + + def find_type_by_id(id, acronym) + type, resource_id = find_type_by_search(id, acronym) + return type, resource_id if type + + type, resource_id = find_type_by_metadata(id, acronym) + return type, resource_id if type + + return nil, nil + end + + + def redirect_to_file? + accept_header.nil? || (accept_header != "text/html" && params[:p].nil?) + end + + def redirect_to_file + # when dont have the specified format in the accept header + return not_acceptable("Invalid requested format, valid format are: JSON, XML, HTML and CSV\nto download the original file you can get it from: #{rest_url}/ontologies/#{params[:id]}/download\n") if accept_header.nil? + + # when the format is different than text/html + redirect_to_download_file if (accept_header != "text/html" && params[:p].nil?) + end + + private + + def find_type_by_search(id, acronym) + # search for URIs that ends with "/id" or "#id" + result = search_content(q: "*##{id} || *\/#{id}", qf: "resource_id", page: 1, pagesize: 10, ontologies: acronym) + + find_exact_resource = result[:collection].select { |x| helpers.link_last_part(x[:resource_id]).eql?(id) }.first + + if !find_exact_resource + type = nil + resource_id = nil + else + type = id_type(find_exact_resource[:type_t], find_exact_resource[:type_txt]) + resource_id = find_exact_resource[:resource_id] + end + + [type, resource_id] + end + + def find_type_by_metadata(id, acronym) + return nil, nil # TODO maybe implemented if needed + end + + + def not_acceptable(message = nil) + render plain: message, status: 406 + end + + def redirect_to_download_file + redirect_to("/ontologies/#{params[:id]}/download?format=#{helpers.escape(accept_header)}", allow_other_host: true) + end + + + def accept_header + header = request.env["HTTP_ACCEPT"] + entries = header.to_s.split(',') + parsed_entries = entries.map { |e| accept_entry(e) } + sorted_entries = parsed_entries.sort_by(&:last) + content_types = sorted_entries.map(&:first) + filtered_content_types = content_types.map { |e| find_content_type_for_media_range(e) } + filtered_content_types.flatten.compact.first + end + + def accept_entry(entry) + type, *options = entry.split(';').map(&:strip) + quality = 0 + options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' } + [options.unshift(type).join(';'), [quality, type.count('*'), 1 - options.size]] + end + + def find_content_type_for_media_range(media_range) + case media_range.to_s + when '*/*', 'text/html', 'text/*' + 'text/html' + when 'application/json', 'application/ld+json', 'application/*' + 'application/ld+json' + when 'text/xml', 'text/rdf+xml', 'application/rdf+xml', 'application/xml' + 'application/rdf+xml' + when 'text/csv' + 'text/csv' + else + nil + end + end + +end \ No newline at end of file diff --git a/app/controllers/content_finder_controller.rb b/app/controllers/content_finder_controller.rb new file mode 100644 index 000000000..c387efcf7 --- /dev/null +++ b/app/controllers/content_finder_controller.rb @@ -0,0 +1,12 @@ +require 'faraday' + +class ContentFinderController < ApplicationController + include OntologyContentSerializer + + def index + @result, _ = serialize_content(ontology_acronym: params[:acronym], + concept_id: params[:uri], + format: params[:output_format]) + render 'content_finder/index', layout: 'tool' + end +end \ No newline at end of file diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index b5ffb1eec..8b55c7903 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -3,10 +3,12 @@ class ErrorsController < ApplicationController layout :determine_layout def not_found + @referer_url = request.referer render status: 404 end def internal_server_error + @referer_url = request.referer render status: 500 end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 1f4f1ee2b..87f9e6e2c 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -25,7 +25,7 @@ def index @upload_benefits = [ t('home.benefit1'), t('home.benefit2'), - t('home.benefit3'), + t('home.benefit3'), t('home.benefit4'), t('home.benefit5') ] @@ -39,6 +39,31 @@ def index end + def tools + @tools = { + search: { + link: "search/ontologies/content", + icon: "icons/search.svg", + title: t('tools.search.title'), + description: t('tools.search.description'), + }, + converter: { + link: "/content_finder", + icon: "icons/settings.svg", + title: t('tools.converter.title'), + description: t('tools.converter.description'), + }, + url_checker: { + link: check_resolvability_path, + icon: "check.svg", + title: t('tools.url_checker.title'), + description: t('tools.url_checker.description') + } + } + + @title = "#{helpers.portal_name} #{t('layout.footer.tools')}" + render 'tools', layout: 'tool' + end def all_resources @conceptid = params[:conceptid] diff --git a/app/controllers/instances_controller.rb b/app/controllers/instances_controller.rb index 8b170f5e2..a980ccc61 100644 --- a/app/controllers/instances_controller.rb +++ b/app/controllers/instances_controller.rb @@ -1,55 +1,53 @@ class InstancesController < ApplicationController - include InstancesHelper - def index_by_ontology - get_ontology(params) - instances = get_instances_by_ontology_json(@ontology, get_query_parameters) - custom_render(instances, @ontology.acronym) - end + include InstancesHelper, SearchContent + + def index + if params[:type].blank? + concept_type = 'NamedIndividual' + else + is_concept_instance = true + concept_type = params[:type] + end - def index_by_class get_ontology(params) - get_class(params) - custom_render(get_instances_by_class_json(@concept, get_query_parameters), @ontology.acronym) + + query, page, page_size = helpers.search_content_params + + results, _, next_page, total_count = search_ontologies_content(query: query, + page: page, + page_size: page_size, + filter_by_ontologies: [@ontology.acronym], + filter_by_types: Array(concept_type)) + + view = helpers.render_search_paginated_list(container_id: (is_concept_instance ? 'concept_' : '') + 'instances_sorted_list', + next_page_url: "/ontologies/#{@ontology.acronym}/instances?type=#{helpers.escape(params[:type])}", + child_url: "/ontologies/#{@ontology.acronym}/instances/show?modal=#{is_concept_instance.to_s}", + child_turbo_frame: 'instance_show', + child_param: :instanceid, + show_count: is_concept_instance, + auto_click: params[:instanceid].blank? && page.eql?(1), + results: results, next_page: next_page, total_count: total_count) + + if is_concept_instance && page.eql?(1) + render turbo_stream: view + else + render inline: view + end end def show - @instance = get_instance_details_json(params[:ontology_id], params[:id] || params[:instance_id], {include: 'all'}) + get_ontology(params) + @instance = get_instance_details_json(params[:ontology], params[:id] || params[:instanceid], {include: 'all'}) + + redirect_to(ontology_path(id: params[:ontology], p: 'instances', instanceid: params[:id] || params[:instanceid], lang: request_lang)) and return unless turbo_frame_request? - render partial: 'instances/instance_details', layout: nil + render partial: 'instances/details', layout: nil end private def get_ontology(params) - @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology]).first + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology] || params[:acronym] || params[:ontology_id]).first ontology_not_found(params[:ontology]) if @ontology.nil? end - # json render + adding next and prev pages links - def custom_render(instances, ontology_acronym) - instances[:collection].map! { |i| add_labels_to_print(i, ontology_acronym)} - if (instances.respond_to? :links) && (!instances.respond_to? :errors) - instances.links = { - nextPage: get_page_link(instances.nextPage), - prevPage: get_page_link(instances.prevPage) - } - end - - render json: instances - end - - def get_page_link(page_number) - return nil if page_number.nil? - - if request.query_parameters.has_key?(:page) - request.original_url.gsub(/page=\d+/, "page=#{page_number}") - elsif request.query_parameters.empty? - request.original_url + "?" + "page=#{page_number}" - else - request.original_url + "&" + "page=#{page_number}" - end - end - - def get_query_parameters - request.query_parameters.slice(:include, :display, :page, :pagesize, :search , :sortby , :order) || {} - end end \ No newline at end of file diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb index 81feb68a9..11b113b17 100755 --- a/app/controllers/login_controller.rb +++ b/app/controllers/login_controller.rb @@ -125,8 +125,9 @@ def reset_password token = params[:tk] @user = LinkedData::Client::HTTP.post("/users/reset_password", {username: username, email: email, token: token}) if @user.is_a?(LinkedData::Client::Models::User) - @user.validate_password = true login(@user) + @user = LinkedData::Client::Models::User.find(@user.id, include: 'all') + @user.validate_password = true render "users/edit" else flash[:notice] = @user.errors.first + t('login.reset_password_again') diff --git a/app/controllers/mappings_controller.rb b/app/controllers/mappings_controller.rb index 89fc84212..e6c9fabd4 100644 --- a/app/controllers/mappings_controller.rb +++ b/app/controllers/mappings_controller.rb @@ -15,32 +15,26 @@ class MappingsController < ApplicationController INTERPORTAL_HASH = $INTERPORTAL_HASH ||= {} def index + @ontologies_mapping_count = LinkedData::Client::HTTP.get("#{MAPPINGS_URL}/statistics/ontologies") ontology_list = LinkedData::Client::Models::Ontology.all.select { |o| !o.summaryOnly } - ontologies_mapping_count = LinkedData::Client::HTTP.get("#{MAPPINGS_URL}/statistics/ontologies") ontologies_hash = {} ontology_list.each do |ontology| ontologies_hash[ontology.acronym] = ontology end - # TODO_REV: Views support for mappings - # views_list.each do |view| - # ontologies_hash[view.ontologyId] = view - # end - @options = {} - ontologies_mapping_count&.members&.each do |ontology_acronym| - # Adding external and interportal mappings to the dropdown list + @ontologies_mapping_count&.members&.each do |ontology_acronym| if ontology_acronym.to_s == EXTERNAL_MAPPINGS_GRAPH - mapping_count = ontologies_mapping_count[ontology_acronym.to_s] || 0 + mapping_count = @ontologies_mapping_count[ontology_acronym.to_s] || 0 select_text = t('mappings.external_mappings', number_with_delimiter: number_with_delimiter(mapping_count, delimiter: ',')) if mapping_count >= 0 ontology_acronym = EXTERNAL_URL_PARAM_STR elsif ontology_acronym.to_s.start_with?(INTERPORTAL_MAPPINGS_GRAPH) - mapping_count = ontologies_mapping_count[ontology_acronym.to_s] || 0 + mapping_count = @ontologies_mapping_count[ontology_acronym.to_s] || 0 select_text = t('mappings.interportal_mappings', acronym: ontology_acronym.to_s.split("/")[-1].upcase, number_with_delimiter: number_with_delimiter(mapping_count, delimiter: ',')) if mapping_count >= 0 ontology_acronym = INTERPORTAL_URL_PARAM_STR + ontology_acronym.to_s.split("/")[-1] else ontology = ontologies_hash[ontology_acronym.to_s] - mapping_count = ontologies_mapping_count[ontology_acronym] || 0 + mapping_count = @ontologies_mapping_count[ontology_acronym] || 0 next unless ontology && mapping_count > 0 select_text = "#{ontology.name} - #{ontology.acronym} (#{number_with_delimiter(mapping_count, delimiter: ',')})" end @@ -48,16 +42,8 @@ def index end @options = @options.sort - end - - def count - @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:id]).first - @ontology_acronym = @ontology&.acronym || params[:id] - @mapping_counts = mapping_counts(@ontology_acronym) - render partial: 'count' - end + @options.unshift([]) - def loader @example_code = [{ "classes": ["http://bioontology.org/ontologies/BiomedicalResourceOntology.owl#Image_Algorithm", "http://purl.org/incf/ontology/Computational_Neurosciences/cno_alpha.owl#cno_0000202"], @@ -74,9 +60,20 @@ def loader "source_contact_info": 'orcid:1234,orcid:5678', "date": '2020-05-30' }] - render partial: 'mappings/bulk_loader/loader' end + + def count + @ontology_acronym = params[:ontology] || params[:id] + @mapping_counts = mapping_counts(@ontology_acronym) + + respond_to do |format| + format.html { render partial: 'mappings/count' } + format.json { render json: @mapping_counts } + end + end + + def loader_process response = LinkedData::Client::HTTP.post('/mappings/load', file: params[:file]) errors = response.errors @@ -85,6 +82,7 @@ def loader_process created = response.created respond_to do |format| format.turbo_stream do + # TO test render turbo_stream: turbo_stream.replace('file_loader_result', partial: 'mappings/bulk_loader/loaded_mappings', locals: { errors: errors, created: created }) @@ -105,7 +103,7 @@ def show def show_mappings page = params[:page] || 1 @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:id]).first - @target_ontology = LinkedData::Client::Models::Ontology.find(params[:target]) + @target_ontology = LinkedData::Client::Models::Ontology.find(params[:target].split('/').last) # Cases if ontology or target are interportal or external if @ontology.nil? @@ -161,13 +159,9 @@ def get_concept_table @concept = @ontology.explore.single_class({ full: true }, params[:conceptid]) @mappings = @concept.explore.mappings - + @type = params[:type] @delete_mapping_permission = check_delete_mapping_permission(@mappings) - render turbo_stream: [ - replace('mapping_count') { "#{@mappings.size}" }, - replace('concept_mappings', partial: 'mappings/concept_mappings') - ] - + render partial: 'mappings/concept_mappings', layout: false end def new @@ -281,7 +275,7 @@ def mapping_form(mapping: nil) else mapping = LinkedData::Client::Models::Mapping.new @ontology_from = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology_from].split('/').last).first - @ontology_to = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology_to]&.split('/')&.last).first + @ontology_to = params[:ontology_to].present? ? LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology_to].split('/').last).first : nil @concept_from = @ontology_from.explore.single_class({ full: true }, params[:conceptid_from]) if @ontology_from if @ontology_to @concept_to = @ontology_to.explore.single_class({ full: true }, params[:conceptid_to]) @@ -347,4 +341,4 @@ def valid_values?(values) end errors end -end +end \ No newline at end of file diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb index 08b24d53d..e08836392 100644 --- a/app/controllers/notes_controller.rb +++ b/app/controllers/notes_controller.rb @@ -12,7 +12,8 @@ def show id = clean_note_id(params[:id]) @note = LinkedData::Client::Models::Note.get(id, include_threads: true) - @ontology = (@notes.explore.relatedOntology || []).first + @note_decorator = NoteDecorator.new(@note, view_context) + @ontology = (@note.explore.relatedOntology || []).first if request.xhr? render :partial => 'thread' diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index 07f1ad9a3..caec35f99 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -12,6 +12,8 @@ class OntologiesController < ApplicationController include TurboHelper include SparqlHelper include SubmissionFilter + include OntologyContentSerializer + include UriRedirection require 'multi_json' require 'cgi' @@ -31,45 +33,33 @@ class OntologiesController < ApplicationController def index @categories = LinkedData::Client::Models::Category.all(display_links: false, display_context: false) @groups = LinkedData::Client::Models::Group.all(display_links: false, display_context: false) + @filters = ontology_filters_init(@categories, @groups) init_filters(params) render 'ontologies/browser/browse' end def ontologies_filter - @ontologies = submissions_paginate_filter(params) - @object_count = count_objects(@ontologies) - - update_filters_counts = @object_count.map do |section, values_count| - values_count.map do |value, count| - replace("count_#{section}_#{value}") do - helpers.turbo_frame_tag("count_#{section}_#{value}") do - helpers.content_tag(:span, count.to_s, class: "hide-if-loading #{count.zero? ? 'disabled' : ''}") + @time = Benchmark.realtime do + @ontologies, @count, @count_objects, @request_params = submissions_paginate_filter(params) + end + + if @page.page.eql?(1) + streams = [prepend("ontologies_list_view-page-#{@page.page}", partial: 'ontologies/browser/ontologies')] + streams += @count_objects.map do |section, values_count| + values_count.map do |value, count| + replace("count_#{section}_#{value}") do + helpers.turbo_frame_tag("count_#{section}_#{value}") do + helpers.content_tag(:span, count.to_s, class: "hide-if-loading #{count.zero? ? 'disabled' : ''}") + end end end - end - end.flatten + end.flatten + else + streams = [replace("ontologies_list_view-page-#{@page.page}", partial: 'ontologies/browser/ontologies')] + end - count_streams = [ - replace('ontologies_filter_count_request') do - helpers.content_tag(:p, class: "browse-desc-text", style: "margin-bottom: 12px !important;") { t("ontologies.showing_ontologies_size", ontologies_size: @ontologies.size, analytics_size: @analytics.keys.size)} - end - ] + update_filters_counts - - streams = if params[:page].nil? - [ - prepend('ontologies_list_container', partial: 'ontologies/browser/ontologies'), - prepend('ontologies_list_container') { - helpers.turbo_frame_tag("ontologies_filter_count_request") do - helpers.browser_counter_loader - end - } - ] - else - [replace("ontologies_list_view-page-1", partial: 'ontologies/browser/ontologies')] - end - - render turbo_stream: streams + count_streams + render turbo_stream: streams end def classes @@ -106,6 +96,8 @@ def classes def properties @acronym = @ontology.acronym + @properties = LinkedData::Client::HTTP.get("/ontologies/#{@acronym}/properties/roots", { lang: request_lang }) + if request.xhr? return render 'ontologies/sections/properties', layout: false else @@ -147,6 +139,7 @@ def edit def mappings @ontology_acronym = @ontology.acronym || params[:id] @mapping_counts = mapping_counts(@ontology_acronym) + @ontologies_mapping_count = LinkedData::Client::HTTP.get("#{MAPPINGS_URL}/statistics/ontologies") if request.xhr? render partial: 'ontologies/sections/mappings', layout: false else @@ -180,35 +173,28 @@ def notes end def instances - if request.xhr? - render partial: 'instances/instances', locals: { id: 'instances-data-table' }, layout: false - else - render partial: 'instances/instances', locals: { id: 'instances-data-table' }, layout: 'ontology_viewer' + + if params[:instanceid] + @instance = helpers.get_instance_details_json(@ontology.acronym, params[:instanceid], {include: 'all'}) end + + render partial: 'instances/instances', locals: { id: 'instances-data-table' }, layout: 'ontology_viewer' end def schemes @schemes = get_schemes(@ontology) - scheme_id = params[:scheme_id] || @submission_latest.URI || nil + scheme_id = params[:schemeid] || @submission_latest.URI || nil @scheme = get_scheme(@ontology, scheme_id) if scheme_id - if request.xhr? - render partial: 'ontologies/sections/schemes', layout: false - else - render partial: 'ontologies/sections/schemes', layout: 'ontology_viewer' - end + render partial: 'ontologies/sections/schemes', layout: 'ontology_viewer' end def collections @collections = get_collections(@ontology) - collection_id = params[:collection_id] - @collection = get_collection(@ontology, collection_id) if collection_id + collection_id = params[:collectionid] + @collection = collection_id ? get_collection(@ontology, collection_id) : @collections.first - if request.xhr? - render partial: 'ontologies/sections/collections', layout: false - else - render partial: 'ontologies/sections/collections', layout: 'ontology_viewer' - end + render partial: 'ontologies/sections/collections', layout: 'ontology_viewer' end def sparql @@ -219,9 +205,18 @@ def sparql end end + def content_serializer + @result, _ = serialize_content(ontology_acronym: params[:acronym], + concept_id: params[:id], + format: params[:output_format]) + + render 'ontologies/content_serializer', layout: nil + end + # GET /ontologies/ACRONYM # GET /ontologies/1.xml def show + return redirect_to_file if redirect_to_file? # Hack to make ontologyid and conceptid work in addition to id and ontology params params[:id] = params[:id].nil? ? params[:ontologyid] : params[:id] @@ -257,7 +252,7 @@ def show # See: https://github.com/ncbo/bioportal_web_ui/issues/133. data_pages = KNOWN_PAGES - %w[summary notes] if @ontology.summaryOnly && params[:p].present? && data_pages.include?(params[:p].to_s) - redirect_to(ontology_path(params[:ontology]), status: :temporary_redirect) and return + params[:p] = "summary" end #@ob_instructions = helpers.ontolobridge_instructions_template(@ontology) @@ -266,9 +261,16 @@ def show @submission_latest = @ontology.explore.latest_submission(include: 'all', invalidate_cache: invalidate_cache?) rescue @ontology.explore.latest_submission(include: '') - if !helpers.submission_ready?(@submission_latest) && params[:p].present? && data_pages.include?(params[:p].to_s) - redirect_to(ontology_path(params[:ontology]), status: :temporary_redirect) and return + + unless helpers.submission_ready?(@submission_latest) + submissions = @ontology.explore.submissions(include: 'submissionId,submissionStatus') + if submissions.any?{|x| helpers.submission_ready?(x)} + @old_submission_ready = true + elsif !params[:p].blank? + params[:p] = "summary" + end end + # Is the ontology downloadable? @ont_restricted = ontology_restricted?(@ontology.acronym) @@ -303,7 +305,6 @@ def show else self.summary end - end def submit_success @@ -311,6 +312,7 @@ def submit_success render 'submit_success' end + # Main ontology description page (with metadata): /ontologies/ACRONYM def summary @@ -341,6 +343,7 @@ def summary @content_properties = properties_hash_values(category_attributes["content"]) @community_properties = properties_hash_values(category_attributes["community"] + [:notes]) @identifiers = properties_hash_values([:URI, :versionIRI, :identifier]) + @identifiers["ontology_portal_uri"] = ["#{$UI_URL}/ontologies/#{@ontology.acronym}", "#{portal_name} URI"] @projects_properties = properties_hash_values(category_attributes["usage"]) @ontology_icon_links = [%w[summary/download dataDump], %w[summary/homepage homepage], @@ -443,16 +446,16 @@ def ontologies_selector @groups = LinkedData::Client::Models::Group.all(display_links: false, display_context: false) @filters = ontology_filters_init(@categories, @groups) @select_id = params[:id] - render 'ontologies/ontologies_selector/ontologies_selector' , layout: false + render 'ontologies/ontologies_selector/ontologies_selector', layout: false end def ontologies_selector_results @ontologies = LinkedData::Client::Models::Ontology.all(include_views: params[:showOntologyViews]) @total_ontologies_number = @ontologies.length @input = params[:input] || '' - @ontologies = @ontologies.select { |ontology| ontology.name.downcase.include?(@input.downcase) || ontology.acronym.downcase.include?(@input.downcase)} + @ontologies = @ontologies.select { |ontology| ontology.name.downcase.include?(@input.downcase) || ontology.acronym.downcase.include?(@input.downcase) } - if params[:groups] + if params[:groups] @ontologies = @ontologies.select do |ontology| (ontology.group & params[:groups]).any? end @@ -465,9 +468,9 @@ def ontologies_selector_results end if params[:formats] || params[:naturalLanguage] || params[:formalityLevel] || params[:isOfType] || params[:showRetiredOntologies] - submissions = LinkedData::Client::Models::OntologySubmission.all({also_include_views: 'true'}) + submissions = LinkedData::Client::Models::OntologySubmission.all({ also_include_views: 'true' }) if params[:formats] - submissions = submissions.select { |submission| params[:formats].include?(submission.hasOntologyLanguage)} + submissions = submissions.select { |submission| params[:formats].include?(submission.hasOntologyLanguage) } end if params[:naturalLanguage] submissions = submissions.select do |submission| @@ -475,13 +478,13 @@ def ontologies_selector_results end end if params[:formalityLevel] - submissions = submissions.select { |submission| params[:formalityLevel].include?(submission.hasFormalityLevel)} + submissions = submissions.select { |submission| params[:formalityLevel].include?(submission.hasFormalityLevel) } end if params[:isOfType] - submissions = submissions.select { |submission| params[:isOfType].include?(submission.isOfType)} + submissions = submissions.select { |submission| params[:isOfType].include?(submission.isOfType) } end if params[:showRetiredOntologies] - submissions = submissions.reject { |submission| submission.status.eql?('retired')} + submissions = submissions.reject { |submission| submission.status.eql?('retired') } end @ontologies = @ontologies.select do |ontology| submissions.any? { |submission| submission.ontology.id == ontology.id } @@ -544,13 +547,6 @@ def properties_hash_values(properties, sub: @submission_latest, custom_labels: { properties.map { |x| [x.to_s, [sub.send(x.to_s), custom_labels[x.to_sym]]] }.to_h end - def get_metrics_hash - metrics_hash = {} - # TODO: Metrics do not return for views on the backend, need to enable include_views param there - @metrics = LinkedData::Client::Models::Metrics.all(include_views: true) - @metrics.each { |m| metrics_hash[m.links['ontology']] = m } - return metrics_hash - end def determine_layout case action_name @@ -561,4 +557,5 @@ def determine_layout end end + end diff --git a/app/controllers/ontologies_redirection_controller.rb b/app/controllers/ontologies_redirection_controller.rb new file mode 100644 index 000000000..43269e788 --- /dev/null +++ b/app/controllers/ontologies_redirection_controller.rb @@ -0,0 +1,93 @@ +class OntologiesRedirectionController < ApplicationController + include UriRedirection + include OntologyContentSerializer + + # GET /ontologies/:acronym/:id + def redirect + return not_found unless params[:acronym] && params[:id] + + request_accept_header = request.env["HTTP_ACCEPT"].split(",")[0] + type, resource_id = find_type_by_id(params[:id], params[:acronym]) + + if resource_id.nil? + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:acronym]).first + @submission_latest = @ontology.explore.latest_submission(include: "URI") + resource_id = @submission_latest.URI + end + + if request_accept_header == "text/html" + if type.nil? || resource_id.blank? + redirect_to ontology_path(id: params[:acronym], p: 'summary') + else + redirect_to link_by_type(resource_id, params[:acronym], type) + end + else + content, serializer_content_type = serialize_content(ontology_acronym: params[:acronym], concept_id: resource_id, format: request_accept_header) + render plain: content, content_type: serializer_content_type + end + end + + # GET /ontologies/:acronym/htaccess + def generate_htaccess + ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:acronym]).first + ontology_uri = ontology.explore.latest_submission(include: 'URI').URI + ontology_portal_url = "#{$UI_URL}/ontologies/#{params[:acronym]}" + + @htaccess_content = generate_htaccess_content(ontology_portal_url, ontology_uri) + @nginx_content = generate_nginx_content(ontology_portal_url, ontology_uri) + + render 'ontologies/htaccess', layout: nil + end + + # GET /ontologies/ACRONYM/download?format=FORMAT + def redirect_ontology + redirect_url = "#{rest_url}/ontologies/#{params[:acronym]}" + download_url = "#{redirect_url}/download?apikey=#{get_apikey}" + case params[:format] + when 'text/csv', 'csv' + redirect_to("#{download_url}&download_format=csv", allow_other_host: true) + when 'text/xml', 'text/rdf+xml', 'application/rdf+xml', 'application/xml', 'xml' + redirect_to("#{download_url}&download_format=rdf", allow_other_host: true) + when 'application/json', 'application/ld+json', 'application/*', 'json' + # redirect to the api + redirect_to("#{redirect_url}?apikey=#{get_apikey}", allow_other_host: true) + else + # redirect to download the original file + redirect_to("#{download_url}", allow_other_host: true) + end + end + + + private + + def generate_htaccess_content(ontology_portal_url, ontology_uri) + ontology_rule = nil + if ontology_uri + ontology_uri += '/' unless ontology_uri.end_with?('/') + ontology_rule = "RewriteRule ^#{URI.parse(ontology_uri).path[1..-1]}?$ #{ontology_portal_url} [R=301,L]" + end + + <<-HTACCESS.strip_heredoc + RewriteEngine On + #{ontology_rule if ontology_rule} + RewriteRule ^.*/([^/#]+)$ #{ontology_portal_url}/$1 [R=301,L] + HTACCESS + end + + def generate_nginx_content(ontology_portal_url, ontology_uri) + ontology_rule = nil + if ontology_uri + ontology_uri += '/' unless ontology_uri.end_with?('/') + ontology_rule = "rewrite ^#{URI.parse(ontology_uri).path[1..-1]}?$ #{ontology_portal_url} permanent" + end + + <<-NGINX.strip_heredoc + location / { + #{ontology_rule if ontology_rule} + if ($request_uri ~ ^.*/([^/]+)$){ + return 301 #{ontology_portal_url}/$1; + } + } + NGINX + end +end \ No newline at end of file diff --git a/app/controllers/properties_controller.rb b/app/controllers/properties_controller.rb index 994767c1a..0ff2a3167 100644 --- a/app/controllers/properties_controller.rb +++ b/app/controllers/properties_controller.rb @@ -1,34 +1,43 @@ class PropertiesController < ApplicationController - include TurboHelper + include TurboHelper, SearchContent - def show - @property = get_property(params[:id]) - @acronym = params[:acronym] - render partial: 'show' - end + def index + acronym = params[:ontology] + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(acronym).first + ontology_not_found(acronym) if @ontology.nil? - def show_tree - @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology]).first - ontology_not_found(params[:ontology]) if @ontology.nil? + if params[:search].blank? + index_tree('properties_sorted_list_view-page-1') + else + query, page, page_size = helpers.search_content_params + results, _, next_page, total_count = search_ontologies_content(query: query, + page: page, + page_size: page_size, + filter_by_ontologies: [acronym], + filter_by_types: %w[AnnotationProperty ObjectProperty DatatypeProperty]) - if !params[:propertyid].blank? - @root = OpenStruct.new({ children: property_tree(params[:propertyid], params[:ontology]) }) - not_found(@root.children.errors.join) if @root.children.respond_to?(:errors) - @property = get_property(params[:propertyid], params[:ontology]) - else - @root = OpenStruct.new({ children: property_roots(params[:ontology]) }) - not_found(@root.children.errors.join) if @root.children.respond_to?(:errors) - - @property ||= @root.children.first + render inline: helpers.render_search_paginated_list(container_id: 'properties_sorted_list', + next_page_url: "/ontologies/#{@ontology.acronym}/properties", + child_url: "/ontologies/#{@ontology.acronym}/properties/show", + child_turbo_frame: 'property_show', + child_param: :propertyid, + results: results, next_page: next_page, total_count: total_count) end + end - render inline: helpers.property_tree_component(@root, @property, - @ontology.acronym, request_lang, - id: 'properties_tree_view', auto_click: true) + + def show + @acronym = params[:ontology] + @property = get_property(params[:id], @acronym, include: 'all') + + redirect_to(ontology_path(id: params[:ontology], p: 'properties', propertyid: params[:id], lang: request_lang)) and return unless turbo_frame_request? + + render partial: 'show' end + def show_children acronym = params[:ontology] id = params[:propertyid] @@ -45,8 +54,8 @@ def show_children private - def get_property(id, acronym = params[:acronym], lang = request_lang) - LinkedData::Client::HTTP.get("/ontologies/#{acronym}/properties/#{helpers.encode_param(id)}", { lang: lang }) + def get_property(id, acronym = params[:acronym], lang = request_lang, include: nil) + LinkedData::Client::HTTP.get("/ontologies/#{acronym}/properties/#{helpers.encode_param(id)}", { lang: lang , include: include}) end def property_tree(id, acronym = params[:acronym], lang = request_lang) @@ -61,4 +70,25 @@ def property_children(id, acronym = params[:acronym], lang = request_lang) LinkedData::Client::HTTP.get("/ontologies/#{acronym}/properties/#{helpers.encode_param(id)}/children", { lang: lang }) end + + private + + def index_tree(container_id) + if !params[:propertyid].blank? + @root = OpenStruct.new({ children: property_tree(params[:propertyid], params[:ontology]) }) + not_found(@root.children.errors.join) if @root.children.respond_to?(:errors) + + @property = get_property(params[:propertyid], params[:ontology]) + else + @root = OpenStruct.new({ children: property_roots(params[:ontology]) }) + not_found(@root.children.errors.join) if @root.children.respond_to?(:errors) + + @property ||= @root.children.first + end + + render inline: helpers.property_tree_component(@root, @property, + @ontology.acronym, request_lang, + id: container_id, auto_click: true) + end + end diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index 046e89cc2..bea96a89d 100644 --- a/app/controllers/schemes_controller.rb +++ b/app/controllers/schemes_controller.rb @@ -1,7 +1,39 @@ class SchemesController < ApplicationController - include SchemesHelper + include SchemesHelper, SearchContent + + + def index + acronym = params[:ontology] + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(acronym).first + ontology_not_found(acronym) if @ontology.nil? + + if params[:search].blank? + @submission_latest = @ontology.explore.latest_submission(include: 'all', invalidate_cache: invalidate_cache?) rescue @ontology.explore.latest_submission(include: '') + @schemes = get_schemes(@ontology) + + render partial: 'schemes/tree_view' + else + query, page, page_size = helpers.search_content_params + + results, _, next_page, total_count = search_ontologies_content(query: query, + page: page, + page_size: page_size, + filter_by_ontologies: [acronym], + filter_by_types: ['ConceptScheme']) + + + render inline: helpers.render_search_paginated_list(container_id: 'schemes_sorted_list', + next_page_url: "/ontologies/#{@ontology.acronym}/schemes", + child_url: "/ontologies/#{@ontology.acronym}/schemes/show", child_turbo_frame: 'scheme', + child_param: :schemeid, + results: results, next_page: next_page, total_count: total_count + ) + end + end def show + redirect_to(ontology_path(id: params[:ontology_id], p: 'schemes', schemeid: params[:id],lang: request_lang)) and return unless turbo_frame_request? + @scheme = get_request_scheme end @@ -16,7 +48,7 @@ def show_label private def get_request_scheme - params[:id] = params[:id] ? params[:id] : params[:scheme_id] + params[:id] = params[:id] ? params[:id] : params[:schemeid] params[:ontology_id] = params[:ontology_id] ? params[:ontology_id] : params[:ontology] if params[:id].nil? || params[:id].empty? diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index f61ce08a6..eeb510ca3 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,7 +1,8 @@ require 'uri' class SearchController < ApplicationController - include SearchAggregator + include SearchAggregator, SearchContent + skip_before_action :verify_authenticity_token layout :determine_layout @@ -99,6 +100,22 @@ def json_search render plain: response, content_type: content_type end + def json_ontology_content_search + query = params[:search] || '*' + page = (params[:page] || 1).to_i + acronyms = params[:ontologies]&.split(',') || [] + page_size = (params[:page_size] || 10).to_i + type = params[:types]&.split(',') || [] + + + results, page, next_page, total_count = search_ontologies_content(query: query, + page: page, + page_size: page_size, + filter_by_ontologies: acronyms, + filter_by_types: type) + + render json: results + end private diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 84854de0a..6dfeaac8c 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -60,7 +60,7 @@ def edit_properties display_submission_attributes params[:ontology_id], params[:properties]&.split(','), submissionId: params[:submission_id], inline_save: params[:inline_save]&.eql?('true') - attribute_template_output = render_to_string(inline: helpers.render_submission_inputs(params[:container_id] || 'metadata_by_ontology')) + attribute_template_output = render_to_string(inline: helpers.render_submission_inputs(params[:container_id] || 'metadata_by_ontology', @submission)) render inline: attribute_template_output diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb index 2acc73037..54d262c56 100644 --- a/app/controllers/subscriptions_controller.rb +++ b/app/controllers/subscriptions_controller.rb @@ -3,7 +3,7 @@ class SubscriptionsController < ApplicationController def create # Try to get the user linked data instance user_id = params[:user_id] - u = LinkedData::Client::Models::User.find(user_id, include: 'all') + u = LinkedData::Client::Models::User.get(helpers.escape(user_id), include: 'all') raise Exception if u.nil? # Try to get the ontology linked data instance diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4d0f15131..da3a01753 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -208,7 +208,7 @@ def user_params params[:user]["orcidId"] = extract_id_from_url(params[:user]["orcidId"], 'orcid.org') params[:user]["githubId"] = extract_id_from_url(params[:user]["githubId"], 'github.com') p = params.require(:user).permit(:firstName, :lastName, :username, :orcidId, :githubId, :email, :email_confirmation, :password, - :password_confirmation, :register_mail_list, :admin) + :password_confirmation, :register_mail_list, :admin, :terms_and_conditions) p.to_h end @@ -265,6 +265,10 @@ def validate(params) if params[:username].nil? || params[:username].length < 1 || !params[:username].match(/^[a-zA-Z0-9]([._-](?![._-])|[a-zA-Z0-9]){3,18}[a-zA-Z0-9]$/) errors << t('users.validate_username') end + + unless params[:terms_and_conditions] + errors << t('users.validate_terms_and_conditions') + end return errors end diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 9df4e07ed..b7814bef3 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -17,7 +17,7 @@ def visits_evolution @users_visits[:visits].last - @users_visits[:visits][-2] end - def action_button(name, link, method: :post, class_style: 'btn btn-link mb-3') + def action_button(name, link, method: :post, class_style: 'btn btn-link') button_to name, link, method: method, class: class_style, form: {data: { turbo: true, turbo_confirm: t('admin.turbo_confirm', name: name), turbo_frame: '_top'}} diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1e78d1389..e768f3b95 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -9,7 +9,7 @@ module ApplicationHelper REST_URI = $REST_URL API_KEY = $API_KEY - include ModalHelper, MultiLanguagesHelper + include ModalHelper, MultiLanguagesHelper, UrlsHelper RESOLVE_NAMESPACE = {:omv => "http://omv.ontoware.org/2005/05/ontology#", :skos => "http://www.w3.org/2004/02/skos/core#", :owl => "http://www.w3.org/2002/07/owl#", :rdf => "http://www.w3.org/1999/02/22-rdf-syntax-ns#", :rdfs => "http://www.w3.org/2000/01/rdf-schema#", :metadata => "http://data.bioontology.org/metadata/", @@ -21,7 +21,8 @@ module ApplicationHelper :cc => "http://creativecommons.org/ns#", :schema => "http://schema.org/", :doap => "http://usefulinc.com/ns/doap#", :bibo => "http://purl.org/ontology/bibo/", :wdrs => "http://www.w3.org/2007/05/powder-s#", :cito => "http://purl.org/spar/cito/", :pav => "http://purl.org/pav/", :nkos => "http://w3id.org/nkos/nkostype#", :oboInOwl => "http://www.geneontology.org/formats/oboInOwl#", :idot => "http://identifiers.org/idot/", :sd => "http://www.w3.org/ns/sparql-service-description#", - :cclicense => "http://creativecommons.org/licenses/"} + :cclicense => "http://creativecommons.org/licenses/", + 'skos-xl' => "http://www.w3.org/2008/05/skos-xl#"} def url_to_endpoint(url) uri = URI.parse(url) endpoint = uri.path.sub(/^\//, '') @@ -92,14 +93,6 @@ def encode_param(string) CGI.escape(string) end - def escape(string) - CGI.escape(string) if string - end - - def unescape(string) - CGI.unescape(string) if string - end - def clean(string) string = string.gsub("\"",'\'') return string.gsub("\n",'') @@ -183,7 +176,7 @@ def error_message_alert def onts_for_select ontologies ||= LinkedData::Client::Models::Ontology.all(include: "acronym,name") - onts_for_select = [] + onts_for_select = [['', '']] ontologies.each do |ont| next if ( ont.acronym.nil? or ont.acronym.empty? ) acronym = ont.acronym @@ -196,6 +189,16 @@ def onts_for_select onts_for_select end + def link_last_part(url) + return "" if url.nil? + + if url.include?('#') + url.split('#').last + else + url.split('/').last + end + end + def get_categories_data(categories = nil) @categories_for_select = [] @categories_map = {} @@ -266,10 +269,10 @@ def group_chip_component(id: nil, name: , object: , checked: , value: nil, title def add_comment_button(parent_id, parent_type) if session[:user].nil? - link_to t('application.add_comment'), login_index_path(redirect: request.url), class: "secondary-button regular-button" + link_to t('application.add_comment'), login_index_path(redirect: request.url), class: "secondary-button regular-button slim" else link_to_modal t('application.add_comment'), notes_new_comment_path(parent_id: parent_id, parent_type: parent_type, ontology_id: @ontology.acronym), - class: "secondary-button regular-button", data: { show_modal_title_value: t('application.add_new_comment')} + class: "secondary-button regular-button slim", data: { show_modal_title_value: t('application.add_new_comment')} end end @@ -284,12 +287,14 @@ def add_reply_button(parent_id) def add_proposal_button(parent_id, parent_type) if session[:user].nil? - link_to t('application.add_proposal'), login_index_path(redirect: request.url), class: "secondary-button regular-button" + link_to t('application.add_proposal'), login_index_path(redirect: request.url), class: "secondary-button regular-button slim" else link_to_modal t('application.add_proposal'), notes_new_proposal_path(parent_id: parent_id, parent_type: parent_type, ontology_id: @ontology.acronym), - class: "secondary-button regular-button", data: { show_modal_title_value: t('application.add_new_proposal')} + class: "secondary-button regular-button slim", data: { show_modal_title_value: t('application.add_new_proposal')} end end + + def link?(str) # Regular expression to match strings starting with "http://" or "https://" link_pattern = /\Ahttps?:\/\// @@ -411,8 +416,9 @@ def label_ajax_link(link, cls_id, ont_acronym, ajax_uri, cls_url, target = nil) data = label_ajax_data(cls_id, ont_acronym, ajax_uri, cls_url) options = { 'data-controller': 'label-ajax' }.merge(data) options = options.merge({ target: target }) if target - - render ChipButtonComponent.new(url: link, text: cls_id, type: 'clickable', **options) + content_tag(:span, class: 'mx-1') do + render ChipButtonComponent.new(url: link, text: cls_id, type: 'clickable', **options) + end end def get_link_for_cls_ajax(cls_id, ont_acronym, target = nil) @@ -422,7 +428,7 @@ def get_link_for_cls_ajax(cls_id, ont_acronym, target = nil) cls_url = "/ontologies/#{ont_acronym}?p=classes&conceptid=#{CGI.escape(cls_id)}" label_ajax_link(link, cls_id, ont_acronym, ajax_url , cls_url ,target) else - auto_link(cls_id, :all, target: '_blank') + content_tag(:div, auto_link(cls_id, :all, target: '_blank')) end end @@ -455,9 +461,9 @@ def get_link_for_label_xl_ajax(label_xl, ont_acronym, cls_id, modal: true) data = label_ajax_data_h(label_xl, ont_acronym, ajax_uri, label_xl_url) data[:data][:controller] = 'label-ajax' if modal - link_to_modal(cls_id, link, {data: data[:data] , class: 'btn btn-sm btn-light'}) + link_to_modal(cls_id, link, {data: data[:data] , class: 'btn btn-sm btn-light m-1'}) else - link_to(link,'', {data: data[:data], class: 'btn btn-sm btn-light', target: '_blank'}) + link_to(link,'', {data: data[:data], class: 'btn btn-sm btn-light m-1', target: '_blank'}) end @@ -520,9 +526,11 @@ def bp_config_json config[:ncbo_slice] = @subdomain_filter[:acronym] if (@subdomain_filter[:active] && !@subdomain_filter[:acronym].empty?) config.to_json end + def portal_name $SITE - end + end + def navitems items = [["/ontologies", t('layout.header.browse')], ["/mappings", t('layout.header.mappings')], @@ -550,13 +558,29 @@ def prefix_properties(concept_properties) next if key_string.include?('metadata') modified_key = prefix_property_url(key_string, key) - modified_properties[modified_key] = value unless modified_key.nil? + + if modified_key + modified_properties[modified_key] = value + else + modified_properties[link_last_part(key_string)] = value + end + end end modified_properties end + def rest_url + # Split the URL into protocol and path parts + protocol, path = $REST_URL.split("://", 2) + + # Remove the last '/' in the path part + cleaned_path = path.chomp('/') + # Reconstruct the cleaned URL + "#{protocol}://#{cleaned_path}" + end + def prefix_property_url(key_string, key = nil) namespace_key, _ = RESOLVE_NAMESPACE.find { |_, value| key_string.include?(value) } @@ -566,10 +590,15 @@ def prefix_property_url(key_string, key = nil) elsif key.nil? && namespace_key namespace_key else # we don't try to guess the prefix - nil + nil end end + def prefixed_url(url) + key = link_last_part(url) + prefix_property_url(url.split(key).first, key) + end + def show_advanced_options_button(text: nil, init: nil) content_tag(:div, class: "#{init ? 'd-none' : ''} advanced-options-button", 'data-action': 'click->reveal-component#show', 'data-reveal-component-target': 'showButton') do inline_svg_tag('icons/settings.svg') + @@ -601,9 +630,9 @@ def empty_state(text) end - def ontologies_selector(id:, label: nil, name: nil, selected: nil) + def ontologies_selector(id:, label: nil, name: nil, selected: nil, placeholder: nil, multiple: true) content_tag(:div) do - render(Input::SelectComponent.new(id: id, label: label, name: name, value: onts_for_select, multiple: "multiple", selected: selected)) + + render(Input::SelectComponent.new(id: id, label: label, name: name, value: onts_for_select, multiple: multiple, selected: selected, placeholder: placeholder)) + content_tag(:div, class: 'ontologies-selector-button', 'data-controller': 'ontologies-selector', 'data-ontologies-selector-id-value': id) do content_tag(:div, t('ontologies_selector.clear_selection'), class: 'clear-selection', 'data-action': 'click->ontologies-selector#clear') + link_to_modal(t('ontologies_selector.ontologies_advanced_selection'), "/ontologies_selector?id=#{id}", data: { show_modal_title_value: t('ontologies_selector.ontologies_advanced_selection')}) @@ -630,4 +659,7 @@ def cancel_button_component(class_name: nil, id: , value:, data: nil) end end end + + + end diff --git a/app/helpers/auto_complete_helper.rb b/app/helpers/auto_complete_helper.rb new file mode 100644 index 000000000..69dd123af --- /dev/null +++ b/app/helpers/auto_complete_helper.rb @@ -0,0 +1,26 @@ +module AutoCompleteHelper + + def ontologies_autocomplete + render OntologySearchInputComponent.new + end + + def ontologies_content_autocomplete(id: '', name: '', search: '', ontologies: [], types: [], search_icon_type: 'home') + render SearchInputComponent.new(id: id, name: name, ajax_url: "#{ajax_search_ontologies_content_path}?ontologies=#{ontologies.join(',')}&types=#{types.join(',')}&search=#{search}", + item_base_url: "", id_key: 'id', placeholder: t("ontologies.ontology_search_prompt"), + use_cache: false, search_icon_type: search_icon_type, + actions_links: { search_ontology_content: "/search?query=o", browse_all_ontologies: "/ontologies?search=o" }) do |s| + s.template do + link_to "LINK", class: "search-content", 'data-turbo-frame': '_top' do + content_tag(:div, class: 'search-element home-searched-ontology flex-column') do + content_tag(:p, "LABEL") + content_tag(:small, "NAME") + content_tag(:small, "ACRONYM", class: 'text-primary') + end + content_tag(:p, "TYPE", class: 'home-result-type') + end + end + end + end + + def ontology_content_autocomplete(search: '', ontologies: [], types: []) + ontologies_content_autocomplete(ontologies: ontologies, types: types, search: "#{search}") + end + +end \ No newline at end of file diff --git a/app/helpers/check_resolvability_helper.rb b/app/helpers/check_resolvability_helper.rb index 0570abbfb..577576620 100644 --- a/app/helpers/check_resolvability_helper.rb +++ b/app/helpers/check_resolvability_helper.rb @@ -1,7 +1,21 @@ module CheckResolvabilityHelper + def formats_equivalents(format = nil) + all = { + 'application/json' => ['application/ld+json'], + 'application/rdf+xml' => %w[application/xml application/rdf+xml text/xml application/octet-stream], + 'text/turtle' => ['application/turtle'], + 'text/n3' => ['application/n-triples'], + 'text/html' => [] + } + + return all unless format + + all[format] + end + def resolvability_formats - %w[application/rdf+xml text/turtle application/json text/n3 text/html] + formats_equivalents.keys end def resolvability_timeout @@ -28,12 +42,10 @@ def resolvability_status(status, allowed_format, redirections, result: nil) { result: result, status: status, allowed_format: supported_format, redirections: redirections } end - def follow_redirection(url, format, timeout_seconds) - # Follow redirects + def follow_redirection(url, format, timeout_seconds, redirect_limit = resolvability_max_redirections) url = url.strip uri = URI.parse(url) response = nil - redirect_limit = resolvability_max_redirections # Set a limit to prevent infinite loops redirect_count = 0 redirections = [uri] @@ -42,7 +54,7 @@ def follow_redirection(url, format, timeout_seconds) http.use_ssl = (uri.scheme == 'https') http.open_timeout = timeout_seconds begin - response = Timeout.timeout(timeout_seconds) { http.request_head(uri.path, 'Accept' => format) } + response = Timeout.timeout(timeout_seconds) { http.request_head(uri, 'Accept' => format) } rescue Timeout::Error, Net::OpenTimeout return resolvability_status('Timeout', [], redirections, result: 0) end @@ -57,7 +69,8 @@ def follow_redirection(url, format, timeout_seconds) redirect_count += 1 end end - if response&.code.to_s.eql?('200') && response&.content_type.to_s.include?(format) + + if response&.code.to_s.eql?('200') && (response&.content_type.to_s.include?(format) || formats_equivalents(format)&.include?(response&.content_type.to_s)) result = 2 elsif response&.code.to_s.eql?('200') result = 1 @@ -116,7 +129,7 @@ def check_resolvability_message(resolvable, allowed_formats, status, url = nil) end - text = text + link_to(' See details', check_resolvability_path(url: url), target: '_blank') if url + text = text + link_to(', click to see details', check_resolvability_path(url: url), target: '_blank') if url text end end diff --git a/app/helpers/collections_helper.rb b/app/helpers/collections_helper.rb index 5b0f28462..595cddabf 100644 --- a/app/helpers/collections_helper.rb +++ b/app/helpers/collections_helper.rb @@ -53,7 +53,7 @@ def collection_path(collection_id = '', language = '') end def request_collection_id - params[:id] || params[:collection_id] || params[:concept_collection] + params[:id] || params[:collectionid] || params[:concept_collection] end def sort_collections_label(collections_labels) diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb index 1bcfe5e64..03eef072c 100644 --- a/app/helpers/components_helper.rb +++ b/app/helpers/components_helper.rb @@ -1,5 +1,9 @@ module ComponentsHelper + def rdf_highlighter_container(format, content) + render Display::RdfHighlighterComponent.new(format: format, text: content) + end + def check_resolvability_container(url) turbo_frame_tag("#{escape(url)}_container", src: "/check_url_resolvability?url=#{escape(url)}", loading: "lazy", class: 'd-inline-block') do content_tag(:div, class: 'p-1', data: { controller: 'tooltip' }, title: t('components.check_resolvability')) do @@ -7,9 +11,67 @@ def check_resolvability_container(url) end end end + + def search_page_input_component(name:, value: nil, placeholder: , button_icon: 'icons/search.svg', type: 'text', &block) + content_tag :div, class: 'search-page-input-container', data: { controller: 'reveal' } do + search_input = content_tag :div, class: 'search-page-input' do + concat text_field_tag(name, value, type: type, placeholder: placeholder) + concat(content_tag(:div, class: 'search-page-button') do + render Buttons::RegularButtonComponent.new(id:'search-page-button', value: "Search", variant: "primary", type: "submit") do |btn| + btn.icon_right { inline_svg_tag button_icon} + end + end) + end + + options = block_given? ? capture(&block) : '' + + (search_input + options).html_safe + end + end + + def paginated_list_component(id:, results:, next_page_url:, child_url:, child_turbo_frame:, child_param:, open_in_modal: false , selected: nil, auto_click: false) + render(TreeInfiniteScrollComponent.new( + id: id, + collection: results.collection, + next_url: next_page_url, + current_page: results.page, + next_page: results.nextPage, + auto_click: auto_click, + )) do |c| + if results.page.eql?(1) + concat(content_tag(:div, class: 'ontologies-selector-results') do + content_tag(:div, class: 'results-number small ml-2') do + "#{t('ontologies.showing')} #{results.totalCount}".html_safe + end + end) + end + + concepts = c.collection + if concepts && !concepts.empty? + concepts.each do |concept| + concept.id = concept["@id"] unless concept.id + data = { child_param => concept.id } + href = child_url.include?('?') ? "#{child_url}&id=#{escape(concept.id)}" : "#{child_url}?id=#{escape(concept.id)}" + concat(render(TreeLinkComponent.new( + child: concept, + href: href, + children_href: '#', + selected: selected.blank? ? false : concept.id.eql?(selected) , + target_frame: child_turbo_frame, + data: data, + open_in_modal: open_in_modal + ))) + end + end + c.error do + t('components.tree_view_empty') + end + end + end + def resolvability_check_tag(url) - content_tag(:span, check_resolvability_container(url), style: 'display: inline-block;') + content_tag(:span, check_resolvability_container(url), style: 'display: inline-block;', onClick: "window.open('#{check_resolvability_url(url: url)}', '_blank');") end def rounded_button_component(link) @@ -18,16 +80,38 @@ def rounded_button_component(link) def copy_link_to_clipboard(url, show_content: false) content_tag(:span, style: 'display: inline-block;') do - render ClipboardComponent.new(message: url, show_content: show_content) + render ClipboardComponent.new(title: t("components.copy_original_uri"), message: url, show_content: show_content) + end + end + + + def generated_link_to_clipboard(url, acronym) + url = "#{$UI_URL}/ontologies/#{acronym}/#{link_last_part(url)}" + content_tag(:span, style: 'display: inline-block;') do + render ClipboardComponent.new(icon: 'icons/copy_link.svg', title: "#{t("components.copy_portal_uri", portal_name: portal_name)} #{link_to(url)}", message: url, show_content: false) end end - def link_to_with_actions(link_to_tag, url: nil, copy: true, check_resolvability: true) + def htaccess_tag(acronym) + content_tag(:span, 'data-controller': 'tooltip', style: 'display: inline-block; width: 18px;', title: "See #{t("ontologies.htaccess_modal_title", acronym: acronym)}") do + link_to_modal(nil, "/ontologies/#{acronym}/htaccess", data: {show_modal_title_value: "#{t("ontologies.htaccess_modal_title", acronym: acronym)}", show_modal_size_value: 'modal-xl'}) do + inline_svg_tag("icons/copy_link.svg") + end + end + end + + + def link_to_with_actions(link_to_tag, acronym: nil, url: nil, copy: true, check_resolvability: true, generate_link: true, generate_htaccess: false) tag = link_to_tag url = link_to_tag if url.nil? - tag = tag + copy_link_to_clipboard(url) if copy + + tag += content_tag(:span, class: 'mx-1') do + concat copy_link_to_clipboard(url) if copy + concat generated_link_to_clipboard(url, acronym) if generate_link + concat resolvability_check_tag(url) if check_resolvability + concat htaccess_tag(acronym) if generate_htaccess + end - tag = tag + resolvability_check_tag(url) if check_resolvability tag.html_safe end @@ -67,8 +151,12 @@ def chart_component(title: '', type:, labels:, datasets:, index_axis: 'x', show_ content_tag(:canvas, nil, data: data) end - def info_tooltip(text) - render Display::InfoTooltipComponent.new(text: text) + def loader_component(type = 'pulsing') + render LoaderComponent.new(type: type) + end + + def info_tooltip(text, interactive: true) + render Display::InfoTooltipComponent.new(text: text, interactive: interactive) end def empty_state_message(message) @@ -148,6 +236,14 @@ def properties_dropdown(id, title, tooltip, properties, is_open: false, &block) end end + + def regular_button(id, value, variant: "secondary", state: "regular", size: "slim", &block) + render Buttons::RegularButtonComponent.new(id:id, value: value, variant: variant, state: state, size: size) do |btn| + capture(btn, &block) if block_given? + end + end + + def form_save_button render Buttons::RegularButtonComponent.new(id: 'save-button', value: t('components.save_button'), variant: "primary", size: "slim", type: "submit") do |btn| btn.icon_left do diff --git a/app/helpers/concepts_helper.rb b/app/helpers/concepts_helper.rb index 15ca7c4ba..06949543e 100644 --- a/app/helpers/concepts_helper.rb +++ b/app/helpers/concepts_helper.rb @@ -2,11 +2,11 @@ module ConceptsHelper def concept_link(acronym, child, language) - child.id.eql?('bp_fake_root') ? '#' : "/ontologies/#{acronym}/concepts/?id=#{CGI.escape(child.id)}&language=#{language}" + child.id.eql?('bp_fake_root') ? '#' : "/ontologies/#{acronym}/concepts/show?id=#{CGI.escape(child.id)}&language=#{language}" end def concept_children_link(acronym, child, language, concept_schemes) - "/ajax_concepts/#{acronym}/?conceptid=#{CGI.escape(child.id)}&concept_schemes=#{concept_schemes.join(',')}&language=#{language}" + "/ontologies/#{acronym}/concepts?conceptid=#{CGI.escape(child.id)}&concept_schemes=#{concept_schemes.join(',')}&language=#{language}" end def concept_tree_data(acronym, child, language, concept_schemes) @@ -145,7 +145,7 @@ def render_concepts_by_dates(auto_click: false) end def concept_list_url(page = 1, collection_id, acronym) - "/ajax/classes/list?ontology_id=#{acronym}&collection_id=#{collection_id}&page=#{page}" + "/ajax/classes/list?ontology_id=#{acronym}&collectionid=#{collection_id}&page=#{page}" end diff --git a/app/helpers/fair_score_helper.rb b/app/helpers/fair_score_helper.rb index ec64d8604..f3fc4f39c 100644 --- a/app/helpers/fair_score_helper.rb +++ b/app/helpers/fair_score_helper.rb @@ -11,16 +11,24 @@ def fairness_service_enabled? def get_fairness_service_url(apikey = user_apikey) "#{$FAIRNESS_URL}?portal=#{$HOSTNAME.split('.')[0]}#{apikey.nil? || apikey.empty? ? '' : "&apikey=#{apikey}"}" end + def get_fairness_json(ontologies_acronyms, apikey = user_apikey) - begin - conn = Faraday.new do |conn| - conn.options.timeout = 30 + Rails.cache.fetch("fairness-#{ontologies_acronyms.gsub(',', '-')}-#{apikey}", expires: 24.hours) do + begin + out = {} + time = Benchmark.realtime do + conn = Faraday.new do |conn| + conn.options.timeout = 30 + end + response = conn.get(get_fairness_service_url(apikey) + "&ontologies=#{ontologies_acronyms}&combined") + out = MultiJson.load(response.body.force_encoding('ISO-8859-1').encode('UTF-8')) + end + puts "Call fairness service for: #{ontologies_acronyms} (#{time}s)" + rescue + Rails.logger.warn t('fair_score.fairness_unreachable_warning') end - response = conn.get(get_fairness_service_url(apikey) + "&ontologies=#{ontologies_acronyms}&combined") - MultiJson.load(response.body.force_encoding('ISO-8859-1').encode('UTF-8')) - rescue - Rails.logger.warn t('fair_score.fairness_unreachable_warning') - {} + + out end end @@ -72,7 +80,7 @@ def create_fair_scores_data(fair_scores, count = nil) end def get_not_obtained_score(fair_scores_data, index) - fair_scores_data[:criteria][:portalMaxCredits][index] - fair_scores_data[:criteria][:scores][index] + fair_scores_data[:criteria][:portalMaxCredits][index] - fair_scores_data[:criteria][:scores][index] end def get_not_obtained_score_normalized(fair_scores_data, index) @@ -84,7 +92,7 @@ def get_not_obtained_score_normalized(fair_scores_data, index) elsif score_rest.zero? 100 - fair_scores_data[:criteria][:normalizedScores][index] else - 0 + 0 end end diff --git a/app/helpers/inputs_helper.rb b/app/helpers/inputs_helper.rb index 15ed566e3..a844ce0d2 100644 --- a/app/helpers/inputs_helper.rb +++ b/app/helpers/inputs_helper.rb @@ -22,6 +22,12 @@ def select_input(name:, values:, id: nil, label: nil, selected: nil, multiple: f data: data) end + def number_input(name: , label: '', value: ) + render Input::NumberComponent.new(label:label, + name: name, + value: value) + end + def check_input(id:, name:, value:, label: '', checked: false, &block) render ChipsComponent.new(name: name, id: id, label: label, value: value, checked: checked) do |c| if block_given? diff --git a/app/helpers/instances_helper.rb b/app/helpers/instances_helper.rb index bb7309711..db5170b23 100644 --- a/app/helpers/instances_helper.rb +++ b/app/helpers/instances_helper.rb @@ -1,25 +1,18 @@ module InstancesHelper include ConceptsHelper include ApplicationHelper - def get_instances_by_class_json(concept, query_parameters) - concept.explore.instances(query_parameters) - end - - def get_instances_by_ontology_json(ontology, query_parameters) - ontology.explore.instances(query_parameters) - end - + def get_instance_details_json(ontology_acronym, instance_uri , query_parameters, raw: false) LinkedData::Client::HTTP .get("/ontologies/#{ontology_acronym}/instances/#{CGI.escape(instance_uri)}", query_parameters, raw: raw) end - def get_instance_and_type(instance_id) + def get_instance_and_type(instance_id, acronym = @ontology.acronym) if instance_id.nil? [{}, nil] else - instance_details = JSON.parse(get_instance_details_json(@ontology.acronym,instance_id, {}, raw: true)) + instance_details = JSON.parse(get_instance_details_json(acronym,instance_id, {}, raw: true)) if(!instance_details['types'].nil?) types = instance_details['types'].reject{ |type| type.eql? 'http://www.w3.org/2002/07/owl#NamedIndividual'} [instance_details, types[0]] @@ -65,18 +58,10 @@ def link_to_property(property, ontology_acronym) def instance_property_value(property, ontology_acronym) if uri?(property) - instance, types = get_instance_and_type(property) - return link_to_instance instance, ontology_acronym unless instance.empty? + instance, types = get_instance_and_type(property, ontology_acronym) + return link_to_instance(instance, ontology_acronym) unless instance.empty? end property end - def add_labels_to_print(instance, ontology_acronym) - instance['labelToPrint'] = instance_label(instance) - instance['types'].reject!{|t| t['NamedIndividual']} - instance['types'].map!{ |t| {type:t, labelToPrint: t}} - instance['ontology'] = ontology_acronym - instance - end - end diff --git a/app/helpers/mappings_helper.rb b/app/helpers/mappings_helper.rb index 16b4485d9..352b42a20 100644 --- a/app/helpers/mappings_helper.rb +++ b/app/helpers/mappings_helper.rb @@ -1,12 +1,5 @@ module MappingsHelper - RELATIONSHIP_URIS = { - "http://www.w3.org/2004/02/skos/core" => "skos:", - "http://www.w3.org/2000/01/rdf-schema" => "rdfs:", - "http://www.w3.org/2002/07/owl" => "owl:", - "http://www.w3.org/1999/02/22-rdf-syntax-ns" => "rdf:" - } - # Used to replace the full URI by the prefixed URI RELATIONSHIP_PREFIX = { "http://www.w3.org/2004/02/skos/core#" => "skos:", @@ -19,11 +12,6 @@ module MappingsHelper INTERPORTAL_HASH = $INTERPORTAL_HASH - def get_short_id(uri) - split = uri.split("#") - name = split.length > 1 && RELATIONSHIP_URIS.keys.include?(split[0]) ? RELATIONSHIP_URIS[split[0]] + split[1] : uri - "#{name}" - end # a little method that returns true if the URIs array contain a gold:translation or gold:freeTranslation def translation?(relation_array) @@ -106,35 +94,6 @@ def get_inter_portal_ui_link(uri, process_name) end end - def onts_and_views_for_select - @onts_and_views_for_select = [] - ontologies = LinkedData::Client::Models::Ontology.all(include: "acronym,name", include_views: true) - ontologies.each do |ont| - next if (ont.acronym.nil? || ont.acronym.empty?) - ont_acronym = ont.acronym - ont_display_name = "#{ont.name.strip} (#{ont_acronym})" - @onts_and_views_for_select << [ont_display_name, ont_acronym] - end - @onts_and_views_for_select.sort! { |a, b| a[0].downcase <=> b[0].downcase } - return @onts_and_views_for_select - end - - def get_concept_mappings(concept) - mappings = concept.explore.mappings - # Remove mappings where the destination class exists in an ontology that the logged in user doesn't have permissions to view. - # Workaround for https://github.com/ncbo/ontologies_api/issues/52. - mappings.delete_if do |mapping| - #mapping.classes.reject! { |cls| (cls.id == concept.id) && (cls.links['ontology'] == concept.links['ontology']) } - begin - ont = mapping.classes[0].explore.ontology - ont.errors && ont.errors.grep(/Access denied/).any? - rescue => e - Rails.logger.warn t('mappings.mapping_issue', mapping: mapping.inspect, message: e.message) - false - end - end - end - def internal_mapping?(cls) cls.links['self'].to_s.start_with?(LinkedData::Client.settings.rest_url) || ($LOCAL_IP.present? && cls.links['self'].to_s.include?($LOCAL_IP)) end @@ -198,4 +157,60 @@ def get_mappings_target def type?(type) @mapping_type.nil? && type.eql?('internal') || @mapping_type.eql?(type) end + + def concept_mappings_loader(ontology_acronym: ,concept_id: ) + content_tag(:span, id: 'mapping_count') do + concat(content_tag(:div, class: 'concepts-mapping-count ml-1 mr-1') do + render(TurboFrameComponent.new( + id: 'mapping_count', + src: "/ajax/mappings/get_concept_table?ontologyid=#{ontology_acronym}&conceptid=#{CGI.escape(concept_id)}", + loading: 'lazy' + )) do |t| + concat(t.loader { render(LoaderComponent.new(small: true)) }) + end + end) + end + end + + def client_filled_modal + link_to_modal "", "" + end + + def mappings_bubble_view_legend + content_tag(:div, class: 'mappings-bubble-view-legend') do + mappings_legend_section(t('mappings.bubble_view_legend.bubble_size'), t('mappings.bubble_view_legend.bubble_size_desc'), 'mappings-bubble-size-legend') + + mappings_legend_section( + t('mappings.bubble_view_legend.color_degree'),t('mappings.bubble_view_legend.color_degree_desc'),'mappings-bubble-color-legend') + + content_tag(:div, class: 'content-container') do + content_tag(:div, class: 'bubble-view-legend-item') do + content_tag(:div, class: 'title') do + content_tag(:div, t('mappings.bubble_view_legend.yellow_bubble'), class: 'd-inline') + content_tag(:span, t('mappings.bubble_view_legend.selected_bubble')) + end + + content_tag(:div, class: "mappings-bubble-size-legend d-flex justify-content-center") do + content_tag(:div, '', class: "bubble yellow") + end + end + end + end + end + + def mappings_legend_section(title_text, description_text, css_class) + content_tag(:div, class: 'content-container') do + content_tag(:div, class: 'bubble-view-legend-item') do + content_tag(:div, class: 'title') do + content_tag(:div, "#{title_text} ", class: 'd-inline') + + content_tag(:span, description_text) + end + + mappings_legend(css_class) + end + end + end + + def mappings_legend(css_class) + content_tag(:div, class: css_class) do + content_tag(:div, t('mappings.bubble_view_legend.less_mappings'), class: 'mappings-legend-text') + + (1..6).map { |i| content_tag(:div, "", class: "bubble bubble#{i}") }.join.html_safe + + content_tag(:div, t('mappings.bubble_view_legend.more_mappings'), class: 'mappings-legend-text') + end + end end diff --git a/app/helpers/metadata_helper.rb b/app/helpers/metadata_helper.rb index a82d20a61..027e48de2 100644 --- a/app/helpers/metadata_helper.rb +++ b/app/helpers/metadata_helper.rb @@ -5,7 +5,9 @@ def input_type?(attr, type) end def submission_metadata - @metadata ||= JSON.parse(Net::HTTP.get(URI.parse("#{$REST_URL}/submission_metadata?apikey=#{$API_KEY}"))) + @metadata ||= Rails.cache.fetch('submission_metadata') do + JSON.parse(LinkedData::Client::HTTP.get("/submission_metadata", {}, {raw: true})) + end end def attr_metadata(attr_key) diff --git a/app/helpers/modal_helper.rb b/app/helpers/modal_helper.rb index fb2e0cd58..58d557047 100644 --- a/app/helpers/modal_helper.rb +++ b/app/helpers/modal_helper.rb @@ -19,8 +19,12 @@ def modal_frame_container(id = 'application_modal') render TurboModalComponent.new(id: id) end - def render_in_modal(id = 'application_modal', &block) - render TurboFrameComponent.new(id: "#{id}_content") do + def modal_frame_id(id = 'application_modal') + "#{id}_content" + end + + def render_in_modal(id = modal_frame_id, &block) + render TurboFrameComponent.new(id: id) do capture(&block).html_safe if block_given? end end diff --git a/app/helpers/multi_languages_helper.rb b/app/helpers/multi_languages_helper.rb index 304f6190d..17a96bd6b 100644 --- a/app/helpers/multi_languages_helper.rb +++ b/app/helpers/multi_languages_helper.rb @@ -2,6 +2,7 @@ module MultiLanguagesHelper def portal_lang session[:locale] || 'en' end + def request_lang lang = params[:language] || params[:lang] lang = portal_lang unless lang @@ -11,6 +12,7 @@ def request_lang def portal_language_help_text t('language.portal_language_help_text') end + def portal_languages { en: { badge: nil, disabled: false }, @@ -32,7 +34,7 @@ def portal_language_selector text = content_tag(:div, class: 'd-flex align-items-center') do content_tag(:span, render(LanguageFieldComponent.new(value: lang, auto_label: true)), class: 'mr-1') + beta_badge(metadata[:badge]) end - link_options = { data: { turbo: true } } + link_options = { data: { turbo: false } } if metadata[:disabled] link_options[:class] = 'disabled-link' @@ -52,10 +54,12 @@ def search_language_help_text t('language.search_language_help_text') end end + def search_languages # top ten spoken languages portal_languages.keys + %w[zh es hi ar bn pt ru ur id] end + def search_language_selector(id: 'search_language', name: 'search_language', selected: nil) render Input::LanguageSelectorComponent.new(id: id, name: name, enable_all: true, languages: search_languages, @@ -78,14 +82,16 @@ def content_languages(submission = @submission || @submission_latest) [submission_lang, current_lang] end + def content_language_help_text content_tag(:div, style: 'width: 350px;') do concat content_tag(:div, t('language.content_language_help_text_1')) - concat(content_tag(:div, class: "mt-1" ) do + concat(content_tag(:div, class: "mt-1") do content_tag(:span, t('language.content_language_help_text_2')) + edit_sub_languages_button end) end end + def content_language_selector(id: 'content_language', name: 'content_language') languages, selected = content_languages render Input::LanguageSelectorComponent.new(id: id, name: name, enable_all: true, @@ -97,10 +103,12 @@ def content_language_selector(id: 'content_language', name: 'content_language') end + def language_hash(concept_label, multiple: false) + if concept_label.is_a?(Array) + return concept_label.first unless multiple + return concept_label + end - def language_hash(concept_label) - - return concept_label.first if concept_label.is_a?(Array) return concept_label.to_h.reject { |key, _| %i[links context].include?(key) } if concept_label.is_a?(OpenStruct) concept_label @@ -133,26 +141,36 @@ def main_language_label(label) select_language_label(label)&.last end + # @param label String | Array | OpenStruct def display_in_multiple_languages(label) - label = language_hash(label) - - if label.nil? + if label.blank? return render Display::AlertComponent.new(message: t('ontology_details.concept.no_preferred_name_for_selected_language'), type: "warning", closable: true) end - return content_tag(:p, label) if label.is_a?(String) - raw(label.map do |key, value| - content_tag(:div, class: 'd-flex align-items-center') do - concat content_tag(:p, Array(value).join(', '), class: 'm-0') - unless key.to_s.upcase.eql?('NONE') || key.to_s.upcase.eql?('@NONE') - concat content_tag(:span, key.upcase, class: 'badge badge-secondary ml-1') - end + label = label.to_h.reject { |key, _| %i[links context].include?(key) } if label.is_a?(OpenStruct) + + if label.is_a?(String) + content_tag(:p, label) + elsif label.is_a?(Array) + content_tag(:div) do + raw(label.map { |x| content_tag(:div, x) }.join) end - end.join) + else + content_tag(:div) do + raw(label.map do |key, value| + Array(value).map do |v| + content_tag(:div) do + concat content_tag(:span, v) + concat content_tag(:span, key.upcase, class: 'badge badge-secondary ml-1') unless key.to_s.upcase.eql?('NONE') || key.to_s.upcase.eql?('@NONE') + end + end.join + end.join) + end + end end def selected_language_label(label) diff --git a/app/helpers/ontologies_helper.rb b/app/helpers/ontologies_helper.rb index c233552fc..46c5d3a4f 100644 --- a/app/helpers/ontologies_helper.rb +++ b/app/helpers/ontologies_helper.rb @@ -5,6 +5,24 @@ module OntologiesHelper API_KEY = $API_KEY LANGUAGE_FILTERABLE_SECTIONS = %w[classes schemes collections instances properties].freeze + def concept_search_input(placeholder) + content_tag(:div, class: 'search-inputs p-1') do + text_input(placeholder: placeholder, label: '', name: "search", value: '', data: { action: "input->browse-filters#dispatchInputEvent" }) + end + end + + def tree_container_component(id:, placeholder:, frame_url:, tree_url:) + content_tag(:div, class: 'search-page-input-container', data: { controller: "turbo-frame history browse-filters", "turbo-frame-url-value": frame_url, action: "changed->turbo-frame#updateFrame" }) do + concat(concept_search_input(placeholder)) + concat(content_tag(:div, class: 'tree-container') do + render(TurboFrameComponent.new( + id: id, + src: tree_url, + data: { 'turbo-frame-target': 'frame' } + )) + end) + end + end def ontology_retired?(submission) submission[:status].to_s.eql?('retired') || submission[:deprecated].to_s.eql?('true') @@ -429,8 +447,7 @@ def submission_ready?(submission) def sections_to_show sections = ['summary'] - - if !@ontology.summaryOnly && submission_ready?(@submission_latest) + if !@ontology.summaryOnly && (submission_ready?(@submission_latest) || @old_submission_ready) sections += ['classes'] sections += %w[properties] sections += %w[schemes collections] if skos? @@ -440,7 +457,7 @@ def sections_to_show sections end - def not_ready_submission_alert(ontology: @ontology, submission: @submission) + def not_ready_submission_alert(ontology: @ontology, submission: @submission, old_submission_ready: @old_submission_ready) if ontology.admin?(session[:user]) status = status_string(submission) type = nil @@ -456,8 +473,10 @@ def not_ready_submission_alert(ontology: @ontology, submission: @submission) type = 'info' if submission.nil? message = t('ontologies.upload_an_ontology', ontology: ontology_data_sections.join(', ')) - else + elsif old_submission_ready message = t('ontologies.ontology_is_processing', ontology: ontology_data_sections.join(', ')) + else + message = t('ontologies.new_ontology_is_processing', ontology: ontology_data_sections.join(', ')) end end render Display::AlertComponent.new(message: message, type: type, button: Buttons::RegularButtonComponent.new(id:'regular-button', value: t('ontologies.contact_support', site: "#{$SITE}"), variant: "primary", href: "/feedback", color: type, size: "slim")) if type @@ -479,7 +498,7 @@ def dispaly_complex_text(definitions) def edit_sub_languages_button(ontology = @ontology, submission = @submission_latest) return unless ontology.admin?(session[:user]) - link = edit_ontology_submission_path(ontology.acronym, submission.submissionId, properties: 'naturalLanguage', container_id: 'application_modal_content') + link = edit_ontology_submission_path(ontology.acronym, submission&.submissionId || '', properties: 'naturalLanguage', container_id: 'application_modal_content') link_to_modal(nil, link, class: "btn", id:'fair-details-link', data: { show_modal_title_value: t('ontologies.edit_natural_languages', acronym: ontology.acronym), show_modal_size_value: 'modal-md' }) do render ChipButtonComponent.new(type: 'clickable', class: 'admin-background chip_button_small' ) do @@ -681,6 +700,61 @@ def service_button(link:, title: ) render IconWithTooltipComponent.new(icon: "json.svg",link: link, target: '_blank', title: title) end + def n_triples_to_table(n_triples_string) + grouped_by_id = n_triples_string.split(".\n").map do |x| + x.strip.scan(/^<([^>]+)> <([^>]+)> (.+)/).flatten + end.group_by { |x| x.shift } + + grouped_by_id_and_properties = grouped_by_id.transform_values do |x| + x.group_by { |y| y.shift } + end + + render TableComponent.new do |t| + resource_id, resource_id_values = grouped_by_id_and_properties.shift + + t.add_row({ td: "ID" }, { td: resource_id }) + + resource_id_values.each do |property, values| + t.row do |row| + url = property.gsub(/[<>]/, '') + row.td do + content_tag(:span, prefixed_url(url), title: link_to(url, url), 'data-controller': 'tooltip') + end + + row.td do + horizontal_list_container(values.flatten) do |v| + v = v.strip.match?(/^<(.+)>/) ? v.strip.match(/^<(.+)>/)[1] : v + link?(v) ? render(LinkFieldComponent.new(value: v)) : "#{v}, " + end + end + end + end + + inverse_grouped_by_properties = grouped_by_id_and_properties.transform_values(&:to_a) + .to_a + .map(&:flatten) + .group_by { |x| x.delete_at(1) } + inverse_grouped_by_properties.each do |property, values| + t.row do |row| + url = property.gsub(/[<>]/, '') + row.td do + content_tag(:span, prefixed_url(url), title: link_to(url, url), 'data-controller': 'tooltip') + end + + row.td do + horizontal_list_container(values.flatten) do |v| + v = v.strip.match?(/^<(.+)>/) ? v.strip.match(/^<(.+)>/)[1] : v + next if v.eql?(resource_id) + + link?(v) ? render(LinkFieldComponent.new(value: v)) : "#{v}, " + end + end + end + end + + end + end + private def submission_languages(submission = @submission) @@ -690,4 +764,6 @@ def submission_languages(submission = @submission) def id_to_acronym(id) id.split('/').last end + + end diff --git a/app/helpers/properties_helper.rb b/app/helpers/properties_helper.rb index a1ac0a241..a485e79e2 100644 --- a/app/helpers/properties_helper.rb +++ b/app/helpers/properties_helper.rb @@ -22,4 +22,14 @@ def property_tree_component(root, selected_concept, acronym, language, sub_tree: property_tree_data(acronym, child, language) end end + + def no_properties? + @properties.nil? || @properties.empty? + end + + def no_properties_alert + render Display::AlertComponent.new do + t('properties.no_properties_alert', acronym: @ontology.acronym) + end + end end \ No newline at end of file diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index b808ea281..39bc77a73 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -48,7 +48,7 @@ def section_name(section) end def scheme_path(scheme_id = '', language = '') - "/ontologies/#{@ontology.acronym}/schemes/show_scheme?id=#{escape(scheme_id)}&lang=#{language}" + "/ontologies/#{@ontology.acronym}/schemes/show?id=#{escape(scheme_id)}&lang=#{language}" end def no_main_scheme? @@ -106,7 +106,7 @@ def schemes_tree(schemes_labels, main_scheme_label, selected_scheme_id) selected_scheme = selected_scheme || main_scheme || root.children.first - tree_component(root, selected_scheme, target_frame: 'scheme', auto_click: true) do |child| + tree_component(root, selected_scheme, target_frame: 'scheme', auto_click: false) do |child| href = scheme_path(child['@id'], request_lang) rescue '' data = { schemeid: (child['@id'] rescue '')} ["#", data, href] diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb new file mode 100644 index 000000000..524cd521e --- /dev/null +++ b/app/helpers/search_helper.rb @@ -0,0 +1,51 @@ +module SearchHelper + def search_content_params + query = params[:search].presence || '*' + page = (params[:page] || 1).to_i + page_size = (params[:page_size] || 100).to_i + [query, page, page_size] + end + + def render_search_paginated_list(container_id:, next_page_url:, child_url:, child_turbo_frame:, + child_param:, show_count: nil, lang: request_lang, + auto_click: false, results: , next_page: ,total_count: ) + query, page, _ = search_content_params + + @results = OpenStruct.new + @results.nextPage = next_page + @results.page = page + @results.totalCount = total_count + @results.collection = results.map { |x| o = OpenStruct.new(x); o.id = x[:name]; o } + @results.collection = @results.collection.drop(1) # remove ontology + @search = query + + next_page_link = next_page_url.include?('?') ? "#{next_page_url}&page=#{@results.nextPage}&search=#{@search}" : "#{next_page_url}?page=#{@results.nextPage}&search=#{@search}" + next_page_link = "#{next_page_link}&lang=#{lang}" + next_page_link = "#{next_page_link}&#{child_param}=#{escape(params[child_param.to_sym])}" + selected = params[child_param.to_sym].blank? && page.eql?(1) ? @results.collection.first&.id : params[child_param.to_sym] + + if show_count && page.eql?(1) + [ + replace("#{container_id}_count", content_tag(:span, @results.totalCount).html_safe), + prepend("#{container_id}_view-page-1", paginated_list_component(id: container_id, + results: @results, + next_page_url: next_page_link, + child_url: child_url.include?('?') ? "#{child_url}&lang=#{lang}" : "#{child_url}?lang=#{lang}", + child_param: child_param, + child_turbo_frame: child_turbo_frame, + open_in_modal: show_count, + selected: selected)) + ] + else + render inline: paginated_list_component(id: container_id, + results: @results, + next_page_url: next_page_link, + child_url: child_url.include?('?') ? "#{child_url}&lang=#{lang}" : "#{child_url}?lang=#{lang}", + child_param: child_param, + child_turbo_frame: child_turbo_frame, + open_in_modal: show_count, + selected: selected, + auto_click: auto_click) + end + end +end \ No newline at end of file diff --git a/app/helpers/submission_inputs_helper.rb b/app/helpers/submission_inputs_helper.rb index d609a8ee9..e79060bf4 100644 --- a/app/helpers/submission_inputs_helper.rb +++ b/app/helpers/submission_inputs_helper.rb @@ -172,7 +172,7 @@ def ontology_umls_language_help end def has_ontology_language_input(submission = @submission) - render(Layout::RevealComponent.new(possible_values: %w[SKOS OBO UMLS OWL], selected: @submission.hasOntologyLanguage)) do |c| + render(Layout::RevealComponent.new(possible_values: %w[SKOS OBO UMLS OWL], selected: submission.hasOntologyLanguage)) do |c| c.button do attribute_input("hasOntologyLanguage") end diff --git a/app/helpers/submissions_helper.rb b/app/helpers/submissions_helper.rb index 6e75061bd..8610dda0e 100644 --- a/app/helpers/submissions_helper.rb +++ b/app/helpers/submissions_helper.rb @@ -219,8 +219,9 @@ def agent_attributes submission_metadata.select { |x| x["enforce"].include?('Agent') }.map { |x| x["attribute"] } end - def render_submission_inputs(frame_id) + def render_submission_inputs(frame_id, submission) output = "" + @submission = submission if selected_attribute?('acronym') output += ontology_acronym_input(update: true) diff --git a/app/helpers/urls_helper.rb b/app/helpers/urls_helper.rb new file mode 100644 index 000000000..5e26db110 --- /dev/null +++ b/app/helpers/urls_helper.rb @@ -0,0 +1,9 @@ +module UrlsHelper + def escape(string) + CGI.escape(string) if string + end + + def unescape(string) + CGI.unescape(string) if string + end +end diff --git a/app/javascript/component_controllers/index.js b/app/javascript/component_controllers/index.js index ed08af5dc..eaf1728f0 100644 --- a/app/javascript/component_controllers/index.js +++ b/app/javascript/component_controllers/index.js @@ -24,7 +24,10 @@ import Reveal_component_controller from '../../components/layout/reveal_componen import Table_component_controller from '../../components/table_component/table_component_controller' import clipboard_component_controller from '../../components/clipboard_component/clipboard_component_controller' import range_slider_component_controller from '../../components/input/range_slider_component/range_slider_component_controller' +import RDFHighlighter from '../../components/display/rdf_highlighter_component/rdf_highlighter_component_controller' + +application.register("rdf-highlighter", RDFHighlighter) application.register('turbo-modal', TurboModalController) application.register('file-input', FileInputLoaderController) application.register('radio-chip', RadioChipController) diff --git a/app/javascript/controllers/browse_filters_controller.js b/app/javascript/controllers/browse_filters_controller.js index 082bde42f..74272dc26 100644 --- a/app/javascript/controllers/browse_filters_controller.js +++ b/app/javascript/controllers/browse_filters_controller.js @@ -8,10 +8,14 @@ export default class extends Controller { } dispatchInputEvent(event) { - if (event.target.name !== "search") { - return + let value + if (event.target instanceof HTMLSelectElement) { + value = Array.from(event.target.selectedOptions).map(x => x.value) + } else { + value = [event.target.value] } - this.#dispatchEvent("search", [event.target.value]) + + this.#dispatchEvent(event.target.name, value) } dispatchFilterEvent(event) { @@ -43,7 +47,7 @@ export default class extends Controller { break; default: checks = this.#getSelectedChecks().map(x => x.value) - filter = this.element.id.split("_")[0] + filter = event.target.name } this.#dispatchEvent(filter, checks) diff --git a/app/javascript/controllers/content_finder_controller.js b/app/javascript/controllers/content_finder_controller.js new file mode 100644 index 000000000..177b7d070 --- /dev/null +++ b/app/javascript/controllers/content_finder_controller.js @@ -0,0 +1,105 @@ +import { Controller } from '@hotwired/stimulus' +import * as jsonld from 'jsonld' +import hljs from 'highlight.js/lib/core' +import xml from 'highlight.js/lib/languages/xml' +import json from 'highlight.js/lib/languages/json' + +export default class extends Controller { + static targets = ["content"] + static values = { + format: String + } + connect() { + switch (this.formatValue) { + case 'json': + hljs.registerLanguage('json', json) + this.showJSON() + break + case 'xml': + hljs.registerLanguage('xml', xml) + this.showXML() + break + case 'ntriples': + hljs.registerLanguage('ntriples', function (hljs) { + var URL_PATTERN = /<[^>]+>/; // Regex pattern for matching URLs in angle brackets + return { + case_insensitive: true, + contains: [ + { + className: 'subject', + begin: /^<[^>]+>/, + }, + { + className: 'predicate', + begin: /<[^>]+>/, + }, + { + className: 'object', + begin: /\s([^\s]+)\s\./, + }, + hljs.COMMENT('^#', '$') + ] + }; + }); + this.showNTriples() + break + case 'turtle': + hljs.registerLanguage('turtle', function (hljs) { + var URL_PATTERN = /(?:<[^>]*>)|(?:https?:\/\/[^\s]+)/; + + return { + case_insensitive: true, + contains: [ + { + className: 'custom-prefixes', + begin: '@prefix', + relevance: 10 + }, + { + className: 'meta', + begin: /@base/, + end: /[\r\n]|$/, + relevance: 10 + }, + { + className: 'variable', + begin: /\?[\w\d]+/ + }, + { + className: 'custom-symbol', + begin: /@?[A-Za-z_][A-Za-z0-9_]*(?= *:)/, + relevance: 10 + }, + { + className: 'custom-concepts', + begin: /:\s*(\w+)/, + relevance: 10 + }, + { + className: 'string', + begin: URL_PATTERN + } + ] + }; + }); + this.showTURTLE() + break + } + } + + showJSON() { + this.contentTarget.innerHTML = hljs.highlight(JSON.stringify(JSON.parse(this.contentTarget.textContent), null, " "), { language: 'json' }).value + } + + showXML() { + this.contentTarget.innerHTML = hljs.highlight(this.contentTarget.textContent, { language: 'xml' }).value + } + + showNTriples() { + this.contentTarget.innerHTML = hljs.highlight(this.contentTarget.textContent, { language: 'ntriples' }).value + } + + showTURTLE() { + this.contentTarget.innerHTML = hljs.highlight(this.contentTarget.textContent, { language: 'turtle' }).value + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 14bf869e4..e490eb3a1 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -46,9 +46,6 @@ application.register("language-change", LanguageChangeController) import LoadChartController from "./load_chart_controller" application.register("load-chart", LoadChartController) -import MetadataDownloaderController from "./metadata_downloader_controller" -application.register("metadata-downloader", MetadataDownloaderController) - import OntologyRelationsNetworkController from "./ontology_relations_network_controller" application.register("ontology-relations-network", OntologyRelationsNetworkController) @@ -102,3 +99,7 @@ application.register('form-url', FormUrlController) import OntologiesSelector from "./ontologies_selector_controller" application.register("ontologies-selector", OntologiesSelector) + + +import MappingsController from "./mappings_visualization_controller" +application.register('mappings', MappingsController) \ No newline at end of file diff --git a/app/javascript/controllers/mappings_visualization_controller.js b/app/javascript/controllers/mappings_visualization_controller.js new file mode 100644 index 000000000..e75813d36 --- /dev/null +++ b/app/javascript/controllers/mappings_visualization_controller.js @@ -0,0 +1,259 @@ +import { Controller } from '@hotwired/stimulus' +import { useMappingsDrawBubbles } from '../mixins/useMappingsBubbles' + +export default class extends Controller { + + static values = { + mappingsList: Object, + zoomRatio: { type: Number, default: 1 }, + acronym: String, + containerId: { type: String, default: 'mappings-bubbles-view' } + } + + static targets = ['frame', 'bubbles', 'submit', 'modal', 'selector', 'ontologies', 'loader'] + + connect () { + + this.drawBubbles = (mappingsList) => { + const zoomRatio = this.zoomRatioValue + const width = 600 * zoomRatio + const height = 600 * zoomRatio + const margin = 1 + const logScaleFactor = 10 + const normalization_ratio = this.#normalizationRatio(mappingsList) + + const data = Object.entries(mappingsList).map(([key, value]) => ({ + ontology_name: key.split('/').pop(), + ontology_mappings: value, + })) + + useMappingsDrawBubbles(data, width, height, margin, this.bubblesTarget, normalization_ratio, logScaleFactor) + + this.#centerScroll(this.frameTarget) + } + + this.drawBubbles(this.mappingsListValue) + + if (this.#selectionDisabled()) { + this.#clickOnSelectedAcronymBubble() + } + + } + + filterOntologies () { + const selectOptions = Array.from(this.ontologiesTarget.querySelector('select').selectedOptions) + const acronyms = selectOptions.map(option => option.value) + + const filteredList = Object.fromEntries( + Object.entries(this.mappingsListValue).filter(([key]) => acronyms.includes(key)) + ) + + this.drawBubbles(filteredList) + } + + submit (event) { + const itemElement = event.currentTarget.querySelector('.item') + if (!itemElement) return + + this.submitTarget.click() + + const selectAcronym = event.currentTarget.querySelector('select').value + + const bubblesContainer = document.getElementById(this.containerIdValue) + const selectedBubble = bubblesContainer.querySelector('[data-selected="true"]') + const currentBubble = bubblesContainer.querySelector(`[data-acronym="${selectAcronym}"]`) + + if (selectedBubble && selectedBubble.dataset.acronym === selectAcronym) return + + const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, view: window }) + + if (currentBubble && (currentBubble.getAttribute('data-enabled') === 'false' || currentBubble.getAttribute('data-highlighted') === 'true')) { + selectedBubble.dispatchEvent(clickEvent) + } + + if (currentBubble) currentBubble.dispatchEvent(clickEvent) + } + + zoomIn () { + this.zoomRatioValue++ + this.drawBubbles(this.mappingsListValue) + } + + zoomOut () { + if (this.zoomRatioValue > 1) { + this.zoomRatioValue-- + this.drawBubbles(this.mappingsListValue) + } + } + + selectBubble (event) { + const selected_bubble = event.currentTarget + + if (selected_bubble.getAttribute('data-enabled') === 'false') { + // user clicks on a bubble that is disabled (has no mappings with the current bubble) do nothing + return + } + + this.#toggleAnimation() + + if (selected_bubble.getAttribute('data-highlighted') === 'true') { + // user clicks on a bubble that have mapping with the current highlighted bubble, should show a modal with the mappings + this.#showMappingsModal(selected_bubble) + this.#toggleAnimation() + } else if (selected_bubble.getAttribute('data-selected') === 'true') { + // user clicks on current bubble (should deselect it, but nothing happen if we're in ontology mappings section not the page) + this.#unSelectBubble(selected_bubble) + this.#toggleAnimation() + } else { + this.#selectBubble(selected_bubble) + } + } + + #selectBubble (selected_bubble) { + + const acronym = selected_bubble.getAttribute('data-acronym') + let url = '/mappings/count/' + acronym + selected_bubble.setAttribute('data-selected', 'true') + + if (this.#selectionEnabled()) { + const input = this.selectorTarget.querySelector('input') + input.value = acronym + input.dispatchEvent(new Event('input', { bubbles: true })) + + const selectValue = Array.from(this.selectorTarget.querySelectorAll('.option')) + .find(option => option.getAttribute('data-value') === acronym) + + if (selectValue) selectValue.click() + } + + this.#fetchMappingsDataAndSetBubblesColor(url) + } + + #unSelectBubble (selected_bubble) { + + if (this.#selectionDisabled()) return + + selected_bubble.setAttribute('data-selected', 'false') + + const selected_circle = selected_bubble.querySelector('circle') + selected_circle.style.fill = 'var(--primary-color)' + + const leafs = this.bubblesTarget.querySelectorAll('.leaf') + leafs.forEach(leaf => { + const circle = leaf.querySelector('circle') + circle.style.fill = 'var(--primary-color)' + circle.style.opacity = '1' + leaf.setAttribute('data-enabled', 'true') + leaf.setAttribute('data-highlighted', 'false') + }) + } + + #showMappingsModal (selected_bubble) { + const selected_leaf = this.bubblesTarget.querySelector('[data-selected="true"]') + const acronym = selected_leaf.getAttribute('data-acronym') + const target_acronym = selected_bubble.getAttribute('data-acronym') + this.modalTarget.querySelector('a').href = `/mappings/show_mappings?id=${acronym}&target=${target_acronym}` + this.modalTarget.querySelector('a').click() + } + + #fetchMappingsDataAndSetBubblesColor (url) { + fetch(url, { + method: 'GET', + headers: { + 'Accept': 'application/json' + }, + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok') + } + return response.json() + }) + .then(data => { + const mappings_list = data.map(item => ({ + acronym: item.target_ontology.acronym, + count: item.count + })) + + this.#setBubblesColors(mappings_list) + + this.#toggleAnimation() + }) + .catch(error => { + console.error('Error fetching or processing data:', error) + // Handle errors here + }) + } + + #setBubblesColors (mappings_list) { + const bubblesContainer = this.bubblesTarget + const leafs = bubblesContainer.querySelectorAll('.leaf') + const max_mappings_count = mappings_list.reduce((max, item) => Math.max(max, item.count), -Infinity) + + leafs.forEach(leaf => { + const circle = leaf.querySelector('circle') + const acronym = leaf.getAttribute('data-acronym') + + const matchingMapping = mappings_list.find(item => item.acronym === acronym) + + if (matchingMapping) { + leaf.setAttribute('data-highlighted', 'true') + circle.style.fill = 'var(--primary-color)' + + const opacity = (matchingMapping.count / max_mappings_count + Math.log(matchingMapping.count + 1)) / 10 + 0.3 + circle.style.opacity = `${opacity}` + } else { + leaf.setAttribute('data-enabled', 'false') + circle.style.fill = 'var(--light-color)' + } + }) + + const selected_leaf = bubblesContainer.querySelector('[data-selected="true"]') + selected_leaf.setAttribute('data-enabled', 'true') + + const selected_circle = selected_leaf.querySelector('circle') + selected_circle.style.fill = 'var(--secondary-color)' + } + + + + #centerScroll (frame) { + frame.scrollTop = frame.scrollHeight / 2 - frame.clientHeight / 2 + frame.scrollLeft = frame.scrollWidth / 2 - frame.clientWidth / 2 + } + + #normalizationRatio (ontologies_hash) { // try to find the biggest multiple of 10 inferior to the max mappings value + const maxValue = Math.max(...Object.values(ontologies_hash)) + let normalization_ratio = 1 + while (maxValue / normalization_ratio > 10) { + normalization_ratio *= 10 + } + return normalization_ratio + } + + #toggleAnimation () { + this.loaderTarget.classList.toggle('d-none') + this.bubblesTarget.classList.toggle('d-none') + } + + #selectionEnabled () { + return !this.hasAcronymValue + } + + #selectionDisabled () { + return !this.#selectionEnabled() + } + + #clickOnSelectedAcronymBubble () { + setTimeout(() => { + const currentBubble = this.bubblesTarget.querySelector(`[data-acronym="${this.acronymValue}"]`) + let clickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window + }) + currentBubble.dispatchEvent(clickEvent) + }, 100) + + } +} \ No newline at end of file diff --git a/app/javascript/mixins/useHighLight.js b/app/javascript/mixins/useHighLight.js new file mode 100644 index 000000000..7b34116f7 --- /dev/null +++ b/app/javascript/mixins/useHighLight.js @@ -0,0 +1,88 @@ +import hljs from 'highlight.js/lib/core' +import xml from 'highlight.js/lib/languages/xml' +import json from 'highlight.js/lib/languages/json' + +class HighLighter { + constructor (highlighter, format) { + switch (format) { + case 'xml': + highlighter.registerLanguage('xml', xml) + break + case 'json': + highlighter.registerLanguage('json', json) + break + case 'triples': + case 'ntriples': + highlighter.registerLanguage('ntriples', function (hljs) { + return { + case_insensitive: true, + contains: [ + { + className: 'subject', + begin: /^<[^>]+>/, + }, + { + className: 'predicate', + begin: /<[^>]+>/, + }, + { + className: 'object', + begin: /\s([^\s]+)\s\./, + }, + hljs.COMMENT('^#', '$') + ] + } + }) + break + case 'turtle': + highlighter.registerLanguage('turtle', function (hljs) { + let URL_PATTERN = /(?:<[^>]*>)|(?:https?:\/\/[^\s]+)/ + + return { + case_insensitive: true, + contains: [ + { + className: 'custom-prefixes', + begin: '@prefix', + relevance: 10 + }, + { + className: 'meta', + begin: /@base/, + end: /[\r\n]|$/, + relevance: 10 + }, + { + className: 'variable', + begin: /\?[\w\d]+/ + }, + { + className: 'custom-symbol', + begin: /@?[A-Za-z_][A-Za-z0-9_]*(?= *:)/, + relevance: 10 + }, + { + className: 'custom-concepts', + begin: /:\s*(\w+)/, + relevance: 10 + }, + { + className: 'string', + begin: URL_PATTERN + } + ] + } + }) + break + } + this.highlighter = highlighter + } + + highlight (text, format) { + return this.highlighter.highlight(text, { language: format }).value + } +} + +export function useHighLighter (format) { + return new HighLighter(hljs, format) +} \ No newline at end of file diff --git a/app/javascript/mixins/useMappingsBubbles.js b/app/javascript/mixins/useMappingsBubbles.js new file mode 100644 index 000000000..60c5a3683 --- /dev/null +++ b/app/javascript/mixins/useMappingsBubbles.js @@ -0,0 +1,110 @@ +import * as d3 from 'd3' + +class BubbleData { + constructor(ontology_name, ontology_mappings) { + this.ontology_name = ontology_name; + this.ontology_mappings = ontology_mappings; + } +} + +/** + * Draws bubbles using D3.js based on the provided data. + * @param {Array} data - The array of BubbleData objects containing ontology names and mappings. + * @param {number} width - The width of the SVG container. + * @param {number} height - The height of the SVG container. + * @param {number} margin - The margin for the SVG container. + * @param {HTMLElement} bubblesTarget - The target HTML element to append the SVG container to. + * @param {number} normalization_ratio - The normalization ratio for bubble size calculation. + * @param {number} logScaleFactor - The logarithmic scale factor for bubble size calculation. + */ + +export function useMappingsDrawBubbles(data, width, height, margin, bubblesTarget, normalization_ratio, logScaleFactor) { + // Define pack layout + const pack = d3.pack() + .size([width - margin, height - margin]) + .padding(3); + + // Create hierarchy and sum for bubble sizes + const root = d3.hierarchy({ children: data }) + .sum(d => calculateBubbleSize(d)); + + // Create SVG container + const svg = d3.select(`#${bubblesTarget.id}`) + .append('svg') + .attr('width', width) + .attr('height', height) + .append('g') + .attr('transform', `translate(${margin}, ${margin})`) + + // Create nodes and bind data + const node = svg.selectAll('.node') + .data(pack(root).descendants().slice(1)) // Exclude the root node + .enter().append('g') + .attr('class', d => d.children ? 'node mappings-bubble' : 'leaf mappings-bubble') + .attr('transform', d => `translate(${d.x},${d.y})`) + .attr('data-action', 'click->mappings#selectBubble') + .attr('data-acronym', d => d.data.ontology_name) + .attr('data-enabled', d => 'true'); + + // Create circles + const circle = node.append('circle') + .attr('r', d => d.r) + .style('fill', 'var(--primary-color)'); + + // Display ontology names and mappings + const textOntology = node.append('text') + .attr('dy', '.35em') + .style('text-anchor', 'middle') + .style('font-size', '16px') + .style('fill', 'white') + .style('font-weight', '600') + .text(d => displayOntologyName(d)); + + const textMappings = node.append('text') + .attr('dy', '1.5em') + .style('text-anchor', 'middle') + .style('font-size', '12px') + .style('fill', 'white') + .text(d => displayMappings(d)); + + // Show tooltips on hover + circle.on('mouseover', (event, d) => showTooltip(event, d)) + .on('mouseout', () => hideTooltip()); + + // Function to calculate bubble size + function calculateBubbleSize(d) { + return d.ontology_mappings / normalization_ratio + Math.log(d.ontology_mappings + 1) / logScaleFactor; + } + + // Function to display ontology name + function displayOntologyName(d) { + return (d.r > d.data.ontology_name.length * 5 && d.r > 20) ? d.data.ontology_name : ''; + } + + // Function to display mappings count + function displayMappings(d) { + return (d.r > d.data.ontology_name.length * 5 && d.r > 20) ? d.data.ontology_mappings : ''; + } + + // Function to show tooltip + function showTooltip(event, d) { + if (!(d.r > d.data.ontology_name.length * 5 && d.r > 20)) { + // Remove existing tooltip + d3.selectAll('.bubble-tooltip').remove(); + + // Calculate tooltip position based on mouse coordinates + const tooltip = d3.select('body') + .append('div') + .attr('class', 'bubble-tooltip') + .style('left', `${event.pageX + 10}px`) // Adjust position relative to mouse pointer + .style('top', `${event.pageY + 10}px`) // Adjust position relative to mouse pointer + .html(`${d.data.ontology_name}
${d.data.ontology_mappings}`); + } + } + + // Function to hide tooltip + function hideTooltip() { + // Remove tooltip on mouseout + d3.selectAll('.bubble-tooltip').remove(); + } +} diff --git a/app/views/admin/_analytics.html.haml b/app/views/admin/_analytics.html.haml index e4f97cb17..93bbd4f9d 100644 --- a/app/views/admin/_analytics.html.haml +++ b/app/views/admin/_analytics.html.haml @@ -1,47 +1,47 @@ -%div.container - %div.analytics.my-5 - %dive.site-admin-page-section.flex-wrap.justify-content-center - %div.d-flex.flex-wrap.m-2.justify-content-center.w-100 - %div.col.card.d-flex.justify-content-center.mr-2.p-2.text-center.container-gradient - %div.card-header-2 - = t('admin.analytics.total_ontologies') - %div.card-header-1 - = @ontologies_count - %div.card-header-3{data: {controller: 'tooltip'}, title: new_ontologies_created_title} - = t('admin.analytics.ontologies_count', size: @new_ontologies_count.size) - %div.col.card.d-flex.justify-content-center.p-2.mr-2.text-center.container-gradient - %div.card-header-2 - = t('admin.analytics.ontologies_with_errors') - %div.card-header-1 - = @ontologies_problems_count - %div.card-header-3.py-2.mt-1 - %div.col.card.d-flex.justify-content-center.mr-2.p-2.text-center.container-gradient - %div.card-header-2 - = t('admin.analytics.total_visits') - %div.card-header-1 - = format_number_abbreviated(@page_visits[:visits].sum) - %div.card-header-3.py-2.mt-1 +%div.analytics + %dive.site-admin-page-section + %div.d-flex + %div.col.card.d-flex.justify-content-center.mr-2.p-2.text-center.container-gradient + %div.card-header-2 + = t('admin.analytics.total_ontologies') + %div.card-header-1 + = @ontologies_count + %div.card-header-3{data: {controller: 'tooltip'}, title: new_ontologies_created_title} + = t('admin.analytics.ontologies_count', size: @new_ontologies_count.size) + %div.col.card.d-flex.justify-content-center.p-2.mr-2.text-center.container-gradient + %div.card-header-2 + = t('admin.analytics.ontologies_with_errors') + %div.card-header-1 + = @ontologies_problems_count + %div.card-header-3.py-2.mt-1 + %div.col.card.d-flex.justify-content-center.mr-2.p-2.text-center.container-gradient + %div.card-header-2 + = t('admin.analytics.total_visits') + %div.card-header-1 + = format_number_abbreviated(@page_visits[:visits].sum) + %div.card-header-3.py-2.mt-1 - %div.col.card.d-flex.justify-content-center.p-2.text-center.container-gradient - %div.card-header-2 - = t('admin.analytics.active_users') - %div.card-header-1 - = @users_visits[:visits].last - %div.card-header-3 - = t('admin.analytics.visit_users', visits: visits_evolution) - %div.d-flex.flex-wrap.m-2.p-2.justify-content-center.w-100 - %div.col.card.p-2.mr-2 - = chart_component(title: t('admin.analytics.ontology_visits'), type: 'bar', + %div.col.card.d-flex.justify-content-center.p-2.text-center.container-gradient + %div.card-header-2 + = t('admin.analytics.active_users') + %div.card-header-1 + = @users_visits[:visits].last + %div.card-header-3 + = t('admin.analytics.visit_users', visits: visits_evolution) + %div.d-flex.mt-2 + %div.col.card.p-2.mr-2 + = chart_component(title: t('admin.analytics.ontology_visits'), type: 'bar', labels: @ontology_visits[:labels].last(13), datasets: visits_chart_dataset(@ontology_visits[:visits].last(13))) - %div.col.card.p-2 - = chart_component(title: t('admin.analytics.unique_users_visits'), type: 'line', + %div.col.card.p-2 + = chart_component(title: t('admin.analytics.unique_users_visits'), type: 'line', labels: @users_visits[:labels].last(13), datasets: visits_chart_dataset(@users_visits[:visits].last(13))) - %div.col-12.card.p-2.m-2 - = chart_component(title: t('admin.analytics.page_visits'), type: 'bar', + %div.mt-2 + %div.card.p-2 + = chart_component(title: t('admin.analytics.page_visits'), type: 'bar', labels: @page_visits[:labels].last(13).reverse, datasets: visits_chart_dataset(@page_visits[:visits].last(13).reverse), - index_axis: 'y') \ No newline at end of file + index_axis: 'y') diff --git a/app/views/admin/_main.html.haml b/app/views/admin/_main.html.haml index 72a75346c..edb0ed60c 100644 --- a/app/views/admin/_main.html.haml +++ b/app/views/admin/_main.html.haml @@ -1,8 +1,8 @@ -%div.container - %div#site-admin-clear-caches.my-5 +%div + %div#site-admin-clear-caches %div.site-admin-page-header = t('admin.main.cache_management') - %dive.site-admin-page-section + %dive.site-admin-page-section.d-flex = action_button(t('admin.main.clear_cache'), admin_clearcache_path, class_style: 'btn btn-primary mx-1') = action_button(t('admin.main.reset_cache'), admin_resetcache_path, class_style: 'btn btn-primary mx-1') = action_button(t('admin.main.clear_goo_cache'), admin_clear_goo_cache_path, class_style: 'btn btn-primary mx-1') @@ -11,6 +11,6 @@ %div.mb-5 %div.site-admin-page-header = t('admin.main.version_management') - %dive.site-admin-page-section + %dive.site-admin-page-section.p-2 = render TurboFrameComponent.new(id: 'update_check_frame', src: '/admin/update_check_enabled') do |c| - c.loader { t('admin.main.checking_for_updates') } diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index 358a6cd67..9891c88f0 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -1,8 +1,6 @@ - @title = t('admin.index.title') -%div - %div.container - %h3.my-1.py-5= t('admin.index.administration_console') + %div %div.container.my-3 = render_alerts_container @@ -13,20 +11,15 @@ %div %div.mx-1 - - sections = [t('admin.index.analytics'), t('admin.index.site_administration'),t('admin.index.ontology_administration'), t('admin.index.licensing'), t('admin.index.users'), t('admin.index.metadata_administration'), t('admin.index.groups'), t('admin.index.categories'), t('admin.index.persons_and_organizations'), t('admin.index.sparql')] + - sections = [t('admin.index.analytics'), t('admin.index.site_administration'),t('admin.index.ontology_administration'), t('admin.index.licensing'), t('admin.index.users'), t('admin.index.metadata_administration'), t('admin.index.groups'), t('admin.index.categories'), t('admin.index.persons_and_organizations'), t('admin.index.sparql'), t('admin.index.search')] - selected = params[:section] || sections.first.downcase - = render TabsContainerComponent.new(url_parameter: 'section') do |t| - - sections.each do |section_title| - - t.item(title: section_title.humanize, - path: '', - selected: section_title.downcase.eql?(selected.downcase), - page_name: '') + = render Layout::VerticalTabsComponent.new(header: t('admin.index.administration_console'), titles: sections, selected: selected, url_parameter: 'section') do |t| - t.item_content do = render 'analytics' - t.item_content do = render 'main' - t.item_content do - %div.ontologies_list_container.mt-3 + %div %table{:style => "float:left;"} %tr %td{:style => "white-space:nowrap;"} @@ -36,7 +29,7 @@ %a#refresh_report_action{:href => "javascript:;", :class => "link_button ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only", :style => "margin-left:11px;"} %span.ui-button-text{:class => "report_date_generated_button"} %p.tab_description{:style => "clear:both;"} - %table#adminOntologies.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} + %table#adminOntologies.border.rounded.p-1 - t.item_content do %div#renew-license-notice %table.table.table-sm.table-bordered.mt-5#license-table @@ -53,23 +46,16 @@ %div.mb-5#renew-license-form - t.item_content do - %div.ontologies_list_container.mt-3 - %div.mx-auto.w-75 - = render TurboFrameComponent.new(id: 'users-list', src: users_path, loading: 'lazy') + = render TurboFrameComponent.new(id: 'users-list', src: users_path, loading: 'lazy') - t.item_content do = render partial: 'ontologies_metadata_curator/metadata_tab' - t.item_content do - %div.ontologies_list_container.mt-3 - %div.mx-auto.w-75 - = render TurboFrameComponent.new(id: 'groups-list', src: admin_groups_path, loading: 'lazy') + = render TurboFrameComponent.new(id: 'groups-list', src: admin_groups_path, loading: 'lazy') + - t.item_content do + = render TurboFrameComponent.new(id: 'categories-list', src: admin_categories_path, loading: 'lazy') - t.item_content do - %div.ontologies_list_container.mt-3 - %div.mx-auto.w-75 - = render TurboFrameComponent.new(id: 'categories-list', src: admin_categories_path, loading: 'lazy') + = render TurboFrameComponent.new(id: 'agents-list', src: agents_path, loading: 'lazy') - t.item_content do - %div.ontologies_list_container.mt-3 - %div.mx-auto.w-75 - = render TurboFrameComponent.new(id: 'agents-list', src: agents_path, loading: 'lazy') + = sparql_query_container - t.item_content do - %div.container - = sparql_query_container + = render TurboFrameComponent.new(id: 'search-admin', src: '/admin/search', loading: 'lazy') \ No newline at end of file diff --git a/app/views/admin/search/index.html.haml b/app/views/admin/search/index.html.haml new file mode 100644 index 000000000..6ef9728ca --- /dev/null +++ b/app/views/admin/search/index.html.haml @@ -0,0 +1,33 @@ += turbo_frame_tag 'search-admin' do + = render_alerts_container + %div#site-admin-clear-caches.my-5 + %div.site-admin-page-header + = t('admin.search.index.index_data') + %dive.site-admin-page-section + = form_with url: admin_index_batch_path, method: 'post', data:{turbo: true, turbo_frame: '_top'} do + %div.d-flex.p-2 + %div.mx-2{style: 'width: 250px'} + = select_input(name: "model_name", values: [[t('admin.search.index.select_model'), ''],[t('admin.search.index.ontology'), 'ontology'], t('admin.search.index.agent'), [t('admin.search.index.ontology_submission'), 'ontology_submission']], label: '') + = form_save_button + + %div.mb-5 + %div.site-admin-page-header + = t('admin.search.index.collections_management') + %dive.site-admin-page-section + = render TableComponent.new(id: 'search_collections', custom_class: 'border rounded my-2 mx-3') do |t| + - t.header do |h| + - h.th { t('admin.search.index.name') } + - h.th { t('admin.search.index.actions') } + - @collections.each do |c| + - t.row do |r| + - r.td{ c } + - r.td do + .d-flex.align-items-center + %span + = action_button(t('admin.search.index.generate_schema'), "/admin/search/#{c}/init_schema") + %span.mx-1 + | + = link_to_modal(t('admin.search.index.see_schema'), "/admin/search/#{c}/schema") + %span.mx-1 + | + = link_to_modal(t('admin.search.index.see_indexed_data'), "/admin/search/#{c}/data", data: { show_modal_size_value: 'modal-xl' } ) diff --git a/app/views/admin/search/search.html.haml b/app/views/admin/search/search.html.haml new file mode 100644 index 000000000..b4c6d96da --- /dev/null +++ b/app/views/admin/search/search.html.haml @@ -0,0 +1,26 @@ += render_in_modal do + = form_with url: "admin/search/#{params[:collection]}/data", method: 'get', data:{turbo: true} do + %div.mb-2 + = text_input(label: t('admin.search.search.search_query'), name: "query", value: params[:query]) + %div.d-flex.my-1{style: 'gap: 10px'} + = number_input(name: 'page', label: t('admin.search.search.page'), value: params[:page] || 1) + = number_input(name: 'page_size', label: t('admin.search.search.page_size'), value: params[:page_size] || 10) + = form_save_button + + %div.my-1 + = t('admin.search.search.found_documents', count: @count) + = render TableComponent.new(id: 'collection_data', paging: true) do |t| + - t.header do |h| + - h.th { t('admin.search.search.id') } + - h.th { t('admin.search.search.properties') } + - @docs.each do |d| + - t.row do |r| + - r.td {d['id']} + - r.td do + %div{data:{controller:"text-truncate", 'text-truncate-more-text-value': t('admin.search.search.show_more') , 'text-truncate-less-text-value': t('admin.search.search.show_less')}} + %div.overflow-hidden{'data-text-truncate-target': 'content', style: ' display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--read-more-line-clamp, 2);'} + - d.each do |k,v| + %div + #{k}: #{v} + %div.btn.btn-link{data:{'text-truncate-target': 'button', 'action':"click->text-truncate#toggle"}} + = t('admin.search.search.show_more') diff --git a/app/views/admin/search/show.html.haml b/app/views/admin/search/show.html.haml new file mode 100644 index 000000000..b9a270783 --- /dev/null +++ b/app/views/admin/search/show.html.haml @@ -0,0 +1,16 @@ += render_in_modal do + = render TableComponent.new(id: 'collection_schema', paging: true, searching: true) do |t| + - t.header do |h| + - h.th {t('admin.search.show.field_name')} + - h.th {t('admin.search.show.type')} + - h.th {t('admin.search.show.multi_valued')} + - h.th {t('admin.search.show.indexed')} + - h.th {t('admin.search.show.stored')} + + - (@collection["fields"] + @collection["dynamicFields"]).each do |f| + - t.row do |r| + - r.td {f["name"]} + - r.td {f["type"]} + - r.td {f["multiValued"].eql?('true').to_s} + - r.td {f["indexed"].eql?('true').to_s} + - r.td {f["stored"].eql?('true').to_s} diff --git a/app/views/check_resolvability/index.html.haml b/app/views/check_resolvability/index.html.haml index 5fb83dff4..2a190c022 100644 --- a/app/views/check_resolvability/index.html.haml +++ b/app/views/check_resolvability/index.html.haml @@ -1,11 +1,8 @@ .search-page-container .search-page-subcontainer{'data-controller': 'reveal-component'} = form_tag(check_resolvability_path, method: :get, 'data-turbo': true) do - .search-page-input-container{'data-controller': 'reveal'} - .search-page-input - %input{type:"url", placeholder: t("check_resolvability.uri_placeholder"), name: "url", value: @url} - %button.search-page-button{type:'submit'} - = inline_svg_tag 'icons/search.svg' + = search_page_input_component(name: 'url', value: @url, placeholder: t("check_resolvability.uri_placeholder"), type: "url") + = render(Layout::RevealComponent.new(toggle: true, selected: false)) do |c| - c.button do diff --git a/app/views/collections/_collection.html.haml b/app/views/collections/_collection.html.haml index 99ea94cfa..16503de74 100644 --- a/app/views/collections/_collection.html.haml +++ b/app/views/collections/_collection.html.haml @@ -1,11 +1,11 @@ = turbo_frame_tag 'collection' do - = render ConceptDetailsComponent.new(id:'collection-label', acronym: @ontology.acronym, + = render ConceptDetailsComponent.new(id:'collection-label', acronym: @ontology.acronym, concept_id: collection.id, properties: collection.properties, top_keys: %w[created modified comment note], bottom_keys: [], exclude_keys: %w[member]) do |c| - c.header(stripped: true) do |t| - - t.add_row({th: t("collections.id")}, {td: link_to_with_actions(collection["@id"]) }) + - t.add_row({th: t("collections.id")}, {td: link_to_with_actions(collection["@id"], acronym: @ontology.acronym)}) - t.add_row({th: t("collections.preferred_name")}, {td: display_in_multiple_languages(get_collection_label(collection))}) - t.add_row({th: t("collections.members_count")}) do |r| - r.td do diff --git a/app/views/collections/_list_view.html.haml b/app/views/collections/_list_view.html.haml index 42ea1e67a..f05ee7194 100644 --- a/app/views/collections/_list_view.html.haml +++ b/app/views/collections/_list_view.html.haml @@ -1,15 +1,16 @@ -- collections_labels, selected_collection = get_collections_labels(@collections, params[:collectionid]) -- selected_collection_id = selected_collection.nil? ? collections_labels.first["@id"] : selected_collection["@id"] -- if collections_labels.empty? - %div - no collections detected -- else - %div - = render TreeViewComponent.new(id: nil, auto_click: true) do |tree_child| - - sort_collections_label(collections_labels).each do |collection| - - scheme = OpenStruct.new(collection) - - scheme.prefLabel = Array(get_collection_label(collection)).last - - scheme.id = scheme['@id'] - - tree_child.child(child: scheme, href: collection_path(collection['@id'], request_lang), - children_href: '#', selected: scheme.id.eql?(selected_collection_id), - target_frame: 'collection', data: {collectionid: collection['@id']}) += turbo_frame_tag "collections_sorted_list_view-page-1" do + - collections_labels, selected_collection = get_collections_labels(@collections, params[:collectionid]) + - selected_collection_id = selected_collection.nil? ? collections_labels.first["@id"] : selected_collection["@id"] + - if collections_labels.empty? + %div + = no_collections_alert + - else + %div + = render TreeViewComponent.new(id: nil, auto_click: false) do |tree_child| + - sort_collections_label(collections_labels).each do |collection| + - scheme = OpenStruct.new(collection) + - scheme.prefLabel = Array(get_collection_label(collection)).last + - scheme.id = scheme['@id'] + - tree_child.child(child: scheme, href: collection_path(collection['@id'], request_lang), + children_href: '#', selected: scheme.id.eql?(selected_collection_id), + target_frame: 'collection', data: {collectionid: collection['@id']}) diff --git a/app/views/concepts/_details.html.haml b/app/views/concepts/_details.html.haml index 506a70092..f20dd3112 100644 --- a/app/views/concepts/_details.html.haml +++ b/app/views/concepts/_details.html.haml @@ -3,14 +3,14 @@ - label_xl_set = %w[skos-xl#prefLabel skos-xl#altLabel skos-xl#hiddenLabel] - = render ConceptDetailsComponent.new(id:'concept-details', acronym: @ontology.acronym, + = render ConceptDetailsComponent.new(id:'concept-details', acronym: @ontology.acronym, concept_id: @concept.id, properties: @concept.properties, top_keys: %w[description comment], bottom_keys: %w[disjoint subclass is_a has_part], exclude_keys: schemes_keys + label_xl_set + ['inScheme', 'narrower']) do |c| - c.header(stripped: true) do |t| - - t.add_row({th: t('ontology_details.concept.id')}, {td:link_to_with_actions(@concept.id)}) + - t.add_row({th: t('ontology_details.concept.id')}, {td:link_to_with_actions(@concept.id, acronym: @ontology.acronym)}) - t.add_row({th: t('ontology_details.concept.preferred_name')}) do |h| - h.td do = display_in_multiple_languages(@concept.prefLabel) @@ -19,7 +19,7 @@ - t.add_row({th: t('ontology_details.concept.synonyms')}) do |h| - h.td do %div.d-flex - %div + %div.d-flex = display_in_multiple_languages(@concept.synonym) %div.synonym-change-request = add_synonym_button diff --git a/app/views/concepts/_show.html.haml b/app/views/concepts/_show.html.haml index 812131a97..fecb7bea9 100644 --- a/app/views/concepts/_show.html.haml +++ b/app/views/concepts/_show.html.haml @@ -19,7 +19,7 @@ - c.item(id: 'instances', path: '#instances') do = t('concepts.instances') ( - %span#instances_count + %span#concept_instances_sorted_list_count ) - c.item(title: t('concepts.visualization'), path: '#visualization') @@ -30,12 +30,12 @@ ( %span#note_count= @notes.length ) - - c.item(id: 'mappings', path: '#mappings') do - #{t('concepts.mappings')} - ( - %span#mapping_count= 'loading' - ) + .d-flex + #{t('concepts.mappings')} + ( + = concept_mappings_loader(ontology_acronym: @ontology.acronym, concept_id: @concept.id) + ) - if @enable_ontolobridge - c.item(title: t('concepts.new_term_requests'), path: '#request_term') diff --git a/app/views/content_finder/index.html.haml b/app/views/content_finder/index.html.haml new file mode 100644 index 000000000..8cee4d011 --- /dev/null +++ b/app/views/content_finder/index.html.haml @@ -0,0 +1,24 @@ +- @title = "Content Finder" += form_tag('/content_finder', method: :get, 'data-turbo': true, novalidate: true) do + .content-finder-container + .inputs.w-50 + #graph_url + = ontologies_selector(id: 'search-ontologies', name: 'acronym', selected: @acronym || '', placeholder: "Graph URL", multiple: false) + #uri + = render Input::UrlComponent.new(name: "uri", placeholder: "URI", value: params[:uri]) + #output_format + = render Input::SelectComponent.new(name: "output_format", value: %w[html json xml ntriples turtle], selected: params[:output_format], multiple: false) + #submit_button + = render Buttons::RegularButtonComponent.new(id:'regular-button', value: "Fetch", variant: "primary", size: "slim", type:"submit") + +- if !@result&.empty? + %div.p-2.card.content-finder-result.mb-3 + - if params[:output_format] != "html" + = rdf_highlighter_container(@format, @result) + - else + = n_triples_to_table(@result) + +- else + = empty_state("Content not found") + + \ No newline at end of file diff --git a/app/views/errors/internal_server_error.html.haml b/app/views/errors/internal_server_error.html.haml index a585f8c9a..485f4ec04 100644 --- a/app/views/errors/internal_server_error.html.haml +++ b/app/views/errors/internal_server_error.html.haml @@ -9,7 +9,7 @@ %div{style: "width: 216px;"} = render Buttons::RegularButtonComponent.new(id:'Home-button', value: t("errors.go_home_button"), variant: "secondary", href: "/") %div{style: "width: 216px; margin-left: 22px"} - = render Buttons::RegularButtonComponent.new(id:"feedback-button" ,value: t("errors.send_feedback_button"), variant: "primary", href: "/feedback") + = render Buttons::RegularButtonComponent.new(id:"feedback-button" ,value: t("errors.send_feedback_button"), variant: "primary", href: "/feedback?location=#{@referer_url}") diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 4e9417d18..6922c6a21 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -6,7 +6,7 @@ %div{style: "width: 216px;"} = render Buttons::RegularButtonComponent.new(id:'Home-button', value: t("errors.go_home_button"), variant: "secondary", href: "/") %div{style: "width: 216px; margin-left: 22px"} - = render Buttons::RegularButtonComponent.new(id:"feedback-button" ,value: t("errors.send_feedback_button"), variant: "primary", href: "/feedback") + = render Buttons::RegularButtonComponent.new(id:"feedback-button" ,value: t("errors.send_feedback_button"), variant: "primary", href: "/feedback?location=#{@referer_url}") diff --git a/app/views/fair_score/_fair_service_header.html.haml b/app/views/fair_score/_fair_service_header.html.haml index 5a0e04978..1d12810af 100644 --- a/app/views/fair_score/_fair_service_header.html.haml +++ b/app/views/fair_score/_fair_service_header.html.haml @@ -2,6 +2,6 @@ %span = beta_badge %span - = render Display::InfoTooltipComponent.new(text: t("fair_score.view_fair_scores_definitions")) + = info_tooltip(content_tag(:div, t("fair_score.view_fair_scores_definitions"), style: "width: 300px"), interactive: false) %span{style: 'vertical-align: bottom; margin-left: -5px;'} = fairness_link(style: 'vertical-align: middle;', ontology: @ontology&.acronym) \ No newline at end of file diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 6a1e1d9c5..7a010b73d 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -43,7 +43,7 @@ = t('home.index.welcome', site: $SITE) %p = t('home.index.tagline') - = render OntologySearchInputComponent.new(search_icon_type: 'home') + = ontologies_content_autocomplete .home-body-container .home-section diff --git a/app/views/home/tools.html.haml b/app/views/home/tools.html.haml new file mode 100644 index 000000000..941d60ec7 --- /dev/null +++ b/app/views/home/tools.html.haml @@ -0,0 +1,12 @@ +%div.d-flex.px-5.py-3.rounded.tools-container + - @tools.each_value do |tool| + = link_to tool[:link], class: "mx-2 d-block", style: "width: 35%" do + = render Layout::CardComponent.new do |c| + - c.header do |h| + - h.text do + %div.d-flex.flex-column.align-items-center.text-primary.pt-4 + = inline_svg_tag(tool[:icon], height: "50px", width: "50px") + %h3.text-primary.mt-2 + = tool[:title] + %p.px-4.tool-description + = tool[:description] diff --git a/app/views/instances/_details.html.haml b/app/views/instances/_details.html.haml new file mode 100644 index 000000000..f1ddcb015 --- /dev/null +++ b/app/views/instances/_details.html.haml @@ -0,0 +1,27 @@ += render TurboFrameComponent.new(id: params[:modal]&.to_s.eql?('true') ? modal_frame_id : 'instance_show') do + - if @instance && @instance["@id"] + %div + - ontology_acronym = params[:ontology_id] || @ontology.acronym + - filter_properties = ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "http://www.w3.org/2000/01/rdf-schema#label", "http://www.w3.org/2004/02/skos/core#prefLabel"] + + = render ConceptDetailsComponent.new(id:'instance-details', acronym: ontology_acronym, concept_id: @instance["@id"]) do |c| + - c.header(stripped: true) do |t| + - t.add_row({th: t("instances.id")}, {td: link_to_with_actions(@instance["@id"]) }) + + - label = @instance['label'] || @instance['prefLabel'] + - unless label.blank? + - t.add_row({th: t('instances.label') }, {td: label.join(',').html_safe}) + + - types = @instance.types.reject{|x| x['NamedIndividual']} + - unless types.empty? + - t.add_row({th: t('instances.type') }) do |r| + - r.td do + = types.reject{|x| x['NamedIndividual']}.map {|cls| link_to_class(ontology_acronym,cls)}.join(', ').html_safe + - properties = @instance[:properties].to_h.select{|k,v| !filter_properties.include? k.to_s} + - properties.each do |prop| + - if !prop[1].nil? + - t.add_row({th: link_to_property(prop[0], ontology_acronym)}, {td: prop[1].map { |value| instance_property_value(value , ontology_acronym) }.join(', ').html_safe}) + + + + diff --git a/app/views/instances/_instance_details.html.haml b/app/views/instances/_instance_details.html.haml deleted file mode 100644 index 901dc1761..000000000 --- a/app/views/instances/_instance_details.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= render_in_modal do - %div - - ontology_acronym = params[:ontology_id] - - filter_properties = ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] - %table.table - %tr - %td - = link_to 'type', "#" - %td{style:"word-break: break-all"} - - type = @instance.types.reject{|x| x['NamedIndividual']} - #{type.map {|cls| link_to_class(ontology_acronym,cls)}.join(', ').html_safe} - - properties = @instance[:properties].to_h.select{|k,v| !filter_properties.include? k.to_s} - - properties.each do |prop| - - if !prop[1].nil? - %tr - %td - = link_to_property(prop[0] , ontology_acronym) - %td{style:"word-break: break-all"} - = prop[1].map { |value| instance_property_value(value , ontology_acronym) }.join(', ').html_safe \ No newline at end of file diff --git a/app/views/instances/_instances.html.haml b/app/views/instances/_instances.html.haml index d4c4c364d..78b8ce0c2 100644 --- a/app/views/instances/_instances.html.haml +++ b/app/views/instances/_instances.html.haml @@ -1,19 +1,15 @@ = render TurboFrameComponent.new(id: "instances", data: {"turbo-frame-target": "frame"} ) do - %div.ontologies_list_container.mt-3 - %instances-table{id: id, style:"width:100%; position:relative" , 'ontology-acronym': @ontology.acronym ,'class-uri': @instances_concept_id} + %div.ont-properties{data: {controller: ( params[:p].eql?('instances') ? 'container-splitter turbo-frame': ''), 'turbo-frame': { 'url-value': "/ontologies/#{@ontology.acronym}/instances/show"}}} + %div.sidebar.w-100.position-relative.card{data: {'container-splitter-target': 'container', action: 'change->turbo-frame#updateFrame'}} + = tree_container_component(id: "#{params[:p].eql?('instances') ? '' : 'concept_'}instances_sorted_list_view-page-1", + placeholder: t('ontologies.sections.instances_search_placeholder', acronym: @ontology.acronym), + frame_url: "/ontologies/#{@ontology.acronym}/instances#{@concept ? '?type=' + escape(@concept.id) : ''}", + tree_url: "/ontologies/#{@ontology.acronym}/instances?page=1#{@concept ? '&type=' + escape(@concept.id) : ''}&#{request.original_url.split('?')[1]}") + + + - if params[:p].eql?('instances') + %div#prop_contents{data: {'container-splitter-target': 'container'}} + = render partial: 'instances/details' + + - :javascript - $(document).ready(function() { - const inst = document.querySelector("##{id}") - const concept_id = "#{@instances_concept_id}" - //print the instance count append to the tab title - const updateInstancesCount = (count) => { - document.querySelector("#instances_count").textContent = count - } - if(concept_id){ - updateInstancesCount(0) - $(inst).on("init.dt" , (e, setting, json) => { - updateInstancesCount(json["table"]["totalCount"]) - }) - } - }) \ No newline at end of file diff --git a/app/views/layouts/_topnav.html.haml b/app/views/layouts/_topnav.html.haml index 973764cb6..4d0a01374 100644 --- a/app/views/layouts/_topnav.html.haml +++ b/app/views/layouts/_topnav.html.haml @@ -22,7 +22,7 @@ %input - else .nav-search-container - = render OntologySearchInputComponent.new(placeholder: t('layout.header.search_prompt', portal_name: portal_name ), scroll_down: false, search_icon_type: 'nav') + = ontologies_content_autocomplete(search_icon_type: 'nav') - if session[:user].nil? %a.nav-a{:href => "/login"}= t('layout.header.login') diff --git a/app/views/layouts/ontology_viewer/_header.html.haml b/app/views/layouts/ontology_viewer/_header.html.haml index aae361bd1..877e8c408 100644 --- a/app/views/layouts/ontology_viewer/_header.html.haml +++ b/app/views/layouts/ontology_viewer/_header.html.haml @@ -38,8 +38,9 @@ .ontology-details-header-right-container.justify-content-end{style: 'min-width: 20%;'} %span.mx-1 = new_submission_button - %span.mx-1 - = ontology_edit_button + - if @submission_latest + %span.mx-1 + = ontology_edit_button %span = submission_json_button = subscribe_button(@ontology.id) diff --git a/app/views/layouts/tool.html.haml b/app/views/layouts/tool.html.haml index 9c687b6a7..26350b57b 100644 --- a/app/views/layouts/tool.html.haml +++ b/app/views/layouts/tool.html.haml @@ -34,8 +34,8 @@ %div.p-5 %a.d-flex.flex-column.container-gradient.mx-auto.d-flex.justify-content-center.align-items-center{href: "/", style: 'width: 200px; height: 200px; border-radius: 100px;'} %img{:src => asset_path("logo-white.svg"), style: 'width: 100px; height: 100px'} - %p.text-white.h3 - = portal_name + %p.text-white.h3.text-center + = @title || portal_name = yield diff --git a/app/views/login/lost_password.html.haml b/app/views/login/lost_password.html.haml index bb4be2167..8c4ffd668 100644 --- a/app/views/login/lost_password.html.haml +++ b/app/views/login/lost_password.html.haml @@ -11,5 +11,5 @@ %div %p.lost-password-description= t('login.email_address_associated') %p.lost-password-input-title= t('login.email') - = text_field t('login.user'), :email,class: "lost-password-input", placeholder: t('login.email_placeholder') + = text_field :user, :email,class: "lost-password-input", placeholder: t('login.email_placeholder') %input.lost-password-button{:type => "submit", :value => t('login.send_instructions')}/ diff --git a/app/views/mappings/_concept_mappings.html.haml b/app/views/mappings/_concept_mappings.html.haml index 4f948ef7d..c71fe9066 100644 --- a/app/views/mappings/_concept_mappings.html.haml +++ b/app/views/mappings/_concept_mappings.html.haml @@ -1,9 +1,7 @@ --# NOTES on control over mapping deletion: --# deleteMappings() is a callback that is called by "#delete_mappings_button" created below. --# The appearance of that button is controlled by updateMappingDeletePermissions(), which --# relies on @delete_mapping_permission in /app/views/mappings/_mapping_table.html.haml; which, --# in turn, is set by /app/controllers/application_controller.check_delete_mapping_permission() -= turbo_frame_tag 'concept_mappings' do += turbo_frame_tag 'mapping_count' do + = "#{@mappings.size}" + += turbo_frame_tag @type.eql?('modal') ? 'application_modal_content' : 'concept_mappings' do %div{:style => "padding: 1%; width: 98%"} - if session[:user].nil? = link_to "Create New Mapping", "/login?redirect=/ontologies/#{@ontology.acronym}/?p=classes&t=mappings&conceptid=#{escape(@concept.id)}", :method => :get, :class => "btn btn-default mb-3" diff --git a/app/views/mappings/_concept_mappings_selector.html.haml b/app/views/mappings/_concept_mappings_selector.html.haml deleted file mode 100644 index c00a40fb4..000000000 --- a/app/views/mappings/_concept_mappings_selector.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -.card - #headingTwo.card-header - %h2.mb-0 - %button.btn.btn-link.btn-block.text-left.collapsed{"data-target" => "#collapseTwo", "data-toggle" => "collapse", :type => "button"} - = t('mappings.find_mappings') - = link_to(Rails.configuration.settings.links[:mappings], id: "mappings-help") do - %i.fas.fa-question-circle.fa-lg{"aria-hidden": "true"} - #collapseTwo.collapse{"data-parent" => "#accordionExample"} - .card-body - %div - = render partial: 'shared/concept_picker', locals: {name: :concept_mapping_selector, concept_label: '', ontology_acronym: t('mappings.all'), include_definition: true } - %div.mt-1 - = render TurboFrameComponent.new(id:'concept_mappings') -:javascript - const picker_name = 'concept_mapping_selector' - const frame = document.getElementById('concept_mappings') - $('input[name="concept_mapping_selector"]').on('selected', () => { - const ontology_id = $(`input[name="${picker_name}_bioportal_ontology_id"]`).val() - const concept_id = $(`input[name="${picker_name}_bioportal_concept_id"]`).val() - frame.src = `/ajax/mappings/get_concept_table?ontologyid=${ontology_id}&conceptid=${encodeURIComponent(concept_id)}` - }) diff --git a/app/views/mappings/_count.html.haml b/app/views/mappings/_count.html.haml index 66b6d96e5..e3cd5f933 100644 --- a/app/views/mappings/_count.html.haml +++ b/app/views/mappings/_count.html.haml @@ -1,18 +1,35 @@ -= render TableComponent.new(id: 'mapping_count_table') do |t| - - t.header do |h| - - h.th {t("mappings.count.ontology")} - - h.th {t("mappings.count.mappings")} += turbo_frame_tag "mappings_table" do + .summary-mappings-tab + .summary-mappings-tab-table + = render TableComponent.new(id: 'summary-mappings-table', borderless: true, outline: true, searching: true, sort_column: '0', no_init_sort: true) do |t| + - t.header do |h| + - h.th {t("mappings.count.ontology")} + - h.th {t("mappings.count.mappings")} - - if @mapping_counts.blank? - - t.row do |r| - - r.td {t("mappings.count.no_mappings")} - - r.td {' '} - - else - - @mapping_counts.each do |mapping_count| - - t.row do |r| - - r.td do - - title = mapping_count[:target_ontology].name - = link_to_modal title, mappings_show_mappings_path(id: @ontology_acronym ,target: mapping_count[:target_ontology].id), data: { show_modal_title_value: title, show_modal_size_value: 'modal-xl'} - - r.td do - = number_with_delimiter(mapping_count[:count], delimiter: ',') + - if @mapping_counts.blank? + - t.row do |r| + - r.td {t("mappings.count.no_mappings")} + - r.td {' '} + - else + - @mapping_counts.each do |mapping_count| + - t.row do |r| + - r.td do + - title = mapping_count[:target_ontology].name + = link_to_modal title, mappings_show_mappings_path(id: @ontology_acronym ,target: mapping_count[:target_ontology].id), data: { show_modal_title_value: title, show_modal_size_value: 'modal-xl'} + - r.td do + = number_with_delimiter(mapping_count[:count], delimiter: ',') + - if @ontologies_mapping_count + .summary-mappigs-page-container{'data-controller': 'mappings', + 'data-mappings-mappings-list-value': "#{@ontologies_mapping_count.to_h.to_json}", + 'data-mappings-acronym-value': @ontology_acronym, + 'data-mappings-api-url-value': rest_url + } + .mappings-bubble-view-frame{'data-mappings-target': 'frame'} + #mappings-bubbles-view{'data-mappings-target': 'bubbles'} + .mapping-bubbles-loader.d-none{'data-mappings-target': 'loader'} + = render LoaderComponent.new(type: 'pulsing') + .d-none{'data-mappings-target': 'modal'} + = client_filled_modal + .d-flex.justify-content-center + = render Display::InfoTooltipComponent.new(text: mappings_bubble_view_legend) \ No newline at end of file diff --git a/app/views/mappings/_form.html.haml b/app/views/mappings/_form.html.haml index 8bd7d159e..264b680e7 100644 --- a/app/views/mappings/_form.html.haml +++ b/app/views/mappings/_form.html.haml @@ -38,5 +38,5 @@ = select("mapping", "relation", options_for_select(@mapping_relation_options, @selected_relation), {}, class: "form-control") %div.form-group - = submit_tag t("mappings.form.save") , class:'btn btn-success btn-block' + = submit_tag t("mappings.form.save") , class:'regular-button primary-button slim' diff --git a/app/views/mappings/_mapping_table.html.haml b/app/views/mappings/_mapping_table.html.haml index e4600ca17..1d91b3b5b 100644 --- a/app/views/mappings/_mapping_table.html.haml +++ b/app/views/mappings/_mapping_table.html.haml @@ -1,18 +1,7 @@ --# called from mappings_controller in several ways: --# 1. mappings_controller::get_concept_table via /app/views/mappings/_concept_mappings.html.haml --# 2. directly from mappings_controller::get_concept_table --#NOTES on control over mapping deletion: --#deleteMappings() is a callback that is called by "#delete_mappings_button" created below. --#The appearance of that button is controlled by updateMappingDeletePermissions(), which --#relies on @delete_mapping_permission in /app/views/mappings/_mapping_table.html.haml; which, --#in turn, is set by /app/controllers/application_controller.check_delete_mapping_permission() --# --# The delete mappings button display is controlled by JS on page ready (see bp_mappings.js) --# check_box_tag(name, value = "1", checked = false, options = {}) = check_box_tag "delete_mappings_permission", @delete_mapping_permission, @delete_mapping_permission, style: "display: none;" %div#concept_mappings_tables_div = render_alerts_container(MappingsController) - %table#concept_mappings_table.zebra{width: "100%", style:'word-break: break-word'} + %table#concept_mappings_table.table-content-stripped.table-content{width: "100%", style:'word-break: break-word'} %thead %tr %th= t("mappings.mapping_table.mapping_to") diff --git a/app/views/mappings/_ontology_mappings.html.haml b/app/views/mappings/_ontology_mappings.html.haml deleted file mode 100644 index 9890a668c..000000000 --- a/app/views/mappings/_ontology_mappings.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -.card - #headingOne.card-header - %h2.mb-0 - %button.btn.btn-link.btn-block.text-left{"data-target" => "#collapseOne", "data-toggle" => "collapse", :type => "button"} - = t('mappings.intro').html_safe - = link_to(Rails.configuration.settings.links[:mappings], id: "mappings-help", "aria-label": "View mappings help") do - %i.fas.fa-question-circle.fa-lg{"aria-hidden": "true"} - #collapseOne.collapse{"data-parent" => "#accordionExample"} - .card-body - %div#mappings_select - - if @options.empty? - = t('mappings.no_mappings_available') - - else - - @options.unshift(['','']) - %div{onchange: "loadMappings(event.target.value)"} - = select_input(name: 'search[ontologies]', id: 'search_ontologies', label: '', values: @options, placeholder: t('mappings.select_ontologies_list')) - #mapping_load - %img{src: asset_path("jquery.simple.tree/spinner.gif")}/ - = t('mappings.loading_mappings') - #mappingCount{style:'min-height: 300px;'} diff --git a/app/views/mappings/_show.html.haml b/app/views/mappings/_show.html.haml index 62dbcd501..6ca1cffe1 100644 --- a/app/views/mappings/_show.html.haml +++ b/app/views/mappings/_show.html.haml @@ -1,11 +1,12 @@ = render_in_modal do #mappings.paginate_ajax{:style => "overflow: auto; max-height: 600px;"} #mapping_results - = will_paginate @page_results, :update => 'mappings', :params => { :target => params[:target] } + .mappings-table-pagination + = will_paginate @page_results, :update => 'mappings', :params => { :target => params[:target] } - if @mappings.nil? or @mappings.empty? = t("mappings.show.no_mappings_found") - - else - %table.zebra.w-100 + - else + %table.table-content.table-content-stripped %thead %th #{@ontology_name} %th #{@target_ontology_name} @@ -22,7 +23,8 @@ = ajax_to_external_cls(cls) %td #{map.source} #{(map.process || {})[:source_name]} - = will_paginate @page_results, :update => 'mappings', :params => { :target => params[:target] } + .mappings-table-pagination + = will_paginate @page_results, :update => 'mappings', :params => { :target => params[:target] } :javascript jQuery(document).ready(function(){ diff --git a/app/views/mappings/bulk_loader/_loader.html.haml b/app/views/mappings/bulk_loader/_loader.html.haml deleted file mode 100644 index fde314032..000000000 --- a/app/views/mappings/bulk_loader/_loader.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= render_in_modal do - %div.d-flex.flex-column{:style => "overflow: auto; max-height: 600px;"} - = render TurboFrameComponent.new(id: 'file_loader_result') do - %div.my-2 - .card.mb-2 - %div - %h2 - %button.btn.btn-link{"data-target" => "#collapseOne", "data-toggle" => "collapse", :type => "button"} - = t("mappings.bulk_loader.loader.example_of_valid_file") - #collapseOne.collapse - .card-body - %pre - %code - = JSON.pretty_generate @example_code - = form_with url: '/mappings/loader', method: :post, multipart: true, data: { turbo: true} do - %div - = render Input::FileInputComponent.new(name: :file) - %button.btn.btn-secondary.btn-block.mt-2{type:'submit'} - = t("mappings.bulk_loader.loader.save") diff --git a/app/views/mappings/index.html.haml b/app/views/mappings/index.html.haml index 24a5d1dc9..44b509894 100644 --- a/app/views/mappings/index.html.haml +++ b/app/views/mappings/index.html.haml @@ -1,14 +1,23 @@ - @title= t('mappings.title') -%div.container - %div#mappings_container.container-fluid.py-4.flex-grow-1 - %h1.my-1= t('mappings.title') - - %div#mappings_uploader.my-2 - = link_to_modal t('mappings.upload_mappings') , "/mappings/loader", class: "btn btn-primary btn-block", - data: { show_modal_title_value: t('mappings.mappings_bulk_load'), show_modal_size_value: 'modal-xl'} - %hr.my-3.w-100 - #accordionExample.accordion - = render partial: 'ontology_mappings' - = render partial: 'concept_mappings_selector' - +.mappigs-page-container{'data-controller': 'mappings', + 'data-mappings-mappings-list-value': "#{@ontologies_mapping_count.to_h.to_json}", + 'data-mappings-api-url-value': rest_url + } + .mappings-page-subcontainer + .mappings-page-title + .text + = @title + .line + .mappings-page-decription + = t('mappings.description') + = render TabsContainerComponent.new do |c| + - c.item(title: t('mappings.tabs.bubble_view'), selected: true) + - c.item_content do + = render partial: '/mappings/tab_sections/bubble_view' + - c.item(title: t('mappings.tabs.table_view')) + - c.item_content do + = render partial: '/mappings/tab_sections/table_view' + - c.item(title: t('mappings.tabs.upload_mappings')) + - c.item_content do + = render partial: '/mappings/tab_sections/upload_mappings' \ No newline at end of file diff --git a/app/views/mappings/tab_sections/_bubble_view.html.haml b/app/views/mappings/tab_sections/_bubble_view.html.haml new file mode 100644 index 000000000..31a335fa1 --- /dev/null +++ b/app/views/mappings/tab_sections/_bubble_view.html.haml @@ -0,0 +1,26 @@ +.mappings-bubble-view-container + .upload-mappings + .card.upload-mappings-example.ontologies + = render(Layout::RevealComponent.new(toggle: true, selected: false)) do |c| + - c.button do + .title-bar + = inline_svg_tag 'icons/settings.svg' + = t('mappings.filter_ontologies') + - c.container do + .mappings-page-ontologies-selector{'data-mappings-target': 'ontologies'} + = ontologies_selector(id:'mappings_page_ontologies' ,name: 'ontologies[]') + .selector-button{'data-action': 'click->mappings#filterOntologies'} + = regular_button('filter-bubbles', t('mappings.filter_bubbles')) + + .mappings-bubble-view-frame{'data-mappings-target': 'frame'} + #mappings-bubbles-view{'data-mappings-target': 'bubbles'} + .mapping-bubbles-loader.d-none{'data-mappings-target': 'loader'} + = loader_component + = info_tooltip(mappings_bubble_view_legend) + .mappings-zoom-buttons + .in{'data-action': 'click->mappings#zoomIn'} + = inline_svg_tag 'icons/zoom-in.svg' + .out{'data-action': 'click->mappings#zoomOut'} + = inline_svg_tag 'icons/zoom-out.svg' + .d-none{'data-mappings-target': 'modal'} + = client_filled_modal \ No newline at end of file diff --git a/app/views/mappings/tab_sections/_table_view.html.haml b/app/views/mappings/tab_sections/_table_view.html.haml new file mode 100644 index 000000000..5ad3ac571 --- /dev/null +++ b/app/views/mappings/tab_sections/_table_view.html.haml @@ -0,0 +1,8 @@ +.mappings-table-view-container + .mappings-ontologies-select + = form_tag('/mappings/count/fake_id', method: :get, novalidate: true, data: { turbo: true, turbo_frame: 'mappings_table' }) do + .mappings-selector{data: {action: 'change->mappings#submit', 'mappings-target': 'selector'}} + = select_input(name: "ontology", values: @options, placeholder: t('mappings.intro')) + %input.d-none{ type: 'submit', 'data-mappings-target': 'submit'} + %div + = render TurboFrameComponent.new(id:"mappings_table") \ No newline at end of file diff --git a/app/views/mappings/tab_sections/_upload_mappings.html.haml b/app/views/mappings/tab_sections/_upload_mappings.html.haml new file mode 100644 index 000000000..e10cb8064 --- /dev/null +++ b/app/views/mappings/tab_sections/_upload_mappings.html.haml @@ -0,0 +1,16 @@ +.upload-mappings + .card.upload-mappings-example + .title-bar{"data-target" => "#collapseOne", "data-toggle" => "collapse"} + = t("mappings.bulk_loader.loader.example_of_valid_file") + #collapseOne.collapse + .card-body + %pre + %code + = JSON.pretty_generate @example_code + + = form_with url: '/mappings/loader', method: :post, multipart: true, data: { turbo: true, turbo_frame: 'file_loader_result'} do + %div.mb-3 + = render Input::FileInputComponent.new(name: :file) + = render Buttons::RegularButtonComponent.new(id:'upload-mappings-button', value: "Save", variant: "primary", size: 'slim', type:'submit', state: "regular") + .mt-3 + = render TurboFrameComponent.new(id: 'file_loader_result') \ No newline at end of file diff --git a/app/views/metadata_export/index.html.haml b/app/views/metadata_export/index.html.haml index 5f5d95245..726ac235a 100644 --- a/app/views/metadata_export/index.html.haml +++ b/app/views/metadata_export/index.html.haml @@ -6,22 +6,22 @@ - t.item(id: 'triples', title: 'N3') - %w[csv xml json triples].each do |format| - t.item_content do - %div.metadata-exporter{data: {controller: 'metadata-downloader', - 'metadata-downloader-metadata-value': @ontology_metadata.to_json, - 'metadata-downloader-context-value': @submission_latest['@context'].to_json, - 'metadata-downloader-namespaces-value': resolve_namespaces.to_json, - 'metadata-downloader-format-value': format}} + %div.metadata-exporter{data: {controller: 'rdf-highlighter', + 'rdf-highlighter-metadata-value': @ontology_metadata.to_json, + 'rdf-highlighter-context-value': @submission_latest['@context'].to_json, + 'rdf-highlighter-namespaces-value': resolve_namespaces.to_json, + 'rdf-highlighter-format-value': format}} %div.download-btn - = render ChipButtonComponent.new(type: 'clickable', 'data-action': "click->metadata-downloader#download") do + = render ChipButtonComponent.new(type: 'clickable', 'data-action': "click->rdf-highlighter#download") do = inline_svg("summary/download.svg", width: '15px', height: '15px') - if format.eql?('csv') - %div{data: {'metadata-downloader-target': 'content'}} + %div{data: {'rdf-highlighter-target': 'content'}} = render partial: 'ontologies/sections/additional_metadata' - %div.d-none{data: {'metadata-downloader-target': 'loader'}} + %div.d-none{data: {'rdf-highlighter-target': 'loader'}} = render LoaderComponent.new - else %div.p-3.my-2.card %pre - %code.d-block{style: 'text-wrap: pretty; word-break: break-all', data: {'metadata-downloader-target': 'content'}} - %div.d-none{data: {'metadata-downloader-target': 'loader'}} + %code.d-block{style: 'text-wrap: pretty; word-break: break-all', data: {'rdf-highlighter-target': 'content'}} + %div.d-none{data: {'rdf-highlighter-target': 'loader'}} = render LoaderComponent.new \ No newline at end of file diff --git a/app/views/ontologies/browser/_ontologies.html.haml b/app/views/ontologies/browser/_ontologies.html.haml index 3e4d3c130..8e3d8bd30 100644 --- a/app/views/ontologies/browser/_ontologies.html.haml +++ b/app/views/ontologies/browser/_ontologies.html.haml @@ -1,7 +1,11 @@ = render InfiniteScrollComponent.new(id: 'ontologies_list', collection: @ontologies, - next_url: ontologies_filter_url(@filters, page: @page.nextPage), + next_url: ontologies_filter_url(@request_params, page: @page.nextPage), current_page: @page.page, next_page: @page.nextPage) do |c| + + - if @page.page.eql?(1) + = content_tag(:p, class: "browse-desc-text", style: "margin-bottom: 12px !important;") { "#{t("ontologies.showing_ontologies_size", ontologies_size: @count, analytics_size: @total_ontologies)} (#{sprintf("%.2f", @time)}s)" } + - ontologies = c.collection - ontologies.each do |ontology| = render OntologyBrowseCardComponent.new(ontology: ontology) diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index cc2f9c982..83da56b6c 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -30,7 +30,7 @@ }); }); - %div{data: { controller: "turbo-frame history browse-filters" , "turbo-frame-url-value": "/ontologies_filter", action: "change->browse-filters#dispatchFilterEvent changed->history#updateURL changed->turbo-frame#updateFrame"}} + %div{data: { controller: "turbo-frame history browse-filters" , "turbo-frame-url-value": "/ontologies_filter?page=1&#{request.original_url.split('?').last}", action: "change->browse-filters#dispatchFilterEvent changed->history#updateURL changed->turbo-frame#updateFrame"}} .browse-sub-container .browse-first-row{data:{controller: "browse-filters", action: "change->browse-filters#dispatchFilterEvent changed->history#updateURL"}} @@ -61,7 +61,7 @@ .browse-filter-checks-container - values.first.each do |object| - title = (key.eql?(:categories) || key.eql?(:groups)) ? nil : '' - = group_chip_component(name: key, object: object, checked: values[1]&.include?(object["id"]), title: title) do |c| + = group_chip_component(name: key, object: object, checked: values[1]&.include?(object["id"]) || values[1]&.include?(object["value"]) , title: title) do |c| - c.count do %span.badge.badge-light.ml-1 = turbo_frame_tag "count_#{key}_#{object["id"]}", busy: true @@ -76,15 +76,10 @@ %select#format.browse-format-filter{:name => "format"} = options_for_select(@formats, @selected_format) %select#Sort_by.browse-sort-by-filter{:name => "Sort_by"} - = options_for_select(@sorts_options, @selected_sort_by) + = options_for_select(@sorts_options, @sort_by) .browse-ontologies - = render TurboFrameComponent.new(id:"ontologies_list_container", data:{"turbo-frame-target":"frame", "turbo-frame-url-value": "/ontologies_filter"}) do |container| - = turbo_frame_tag "ontologies_filter_count_request" do - = browser_counter_loader - = render TurboFrameComponent.new(id: "ontologies_list_view-page-1" , src: "/ontologies_filter?page=1&#{request.original_url.split('?').last}") do |list| - - list.loader do - - ontologies_browse_skeleton - - container.loader do + = render TurboFrameComponent.new(id: "ontologies_list_view-page-1" , src: "/ontologies_filter?page=1&#{request.original_url.split('?').last}", data:{"turbo-frame-target":"frame", "turbo-frame-url-value": "/ontologies_filter"}) do |list| + - list.loader do = browser_counter_loader - ontologies_browse_skeleton diff --git a/app/views/ontologies/concepts_browsers/_collections_picker.html.haml b/app/views/ontologies/concepts_browsers/_collections_picker.html.haml index bd2f1b935..1191df3ac 100644 --- a/app/views/ontologies/concepts_browsers/_collections_picker.html.haml +++ b/app/views/ontologies/concepts_browsers/_collections_picker.html.haml @@ -9,4 +9,4 @@ %div = select_tag(:collections,options_for_select(collections_labels.map{|s| [s["prefLabel"].last, s["@id"], {'data-color': s['color']}]}, selected_collection.map{|x| x["@id"]}), { multiple: multiple, id: id, class:"form-control", include_blank: true, - data:{controller:'chosen', 'chosen-name-value': :collection_id}.merge(data)}) \ No newline at end of file + data:{controller:'chosen', 'chosen-name-value': :collectionid}.merge(data)}) \ No newline at end of file diff --git a/app/views/ontologies/concepts_browsers/_concepts_browser.html.haml b/app/views/ontologies/concepts_browsers/_concepts_browser.html.haml index 8f74659d2..98aad66d7 100644 --- a/app/views/ontologies/concepts_browsers/_concepts_browser.html.haml +++ b/app/views/ontologies/concepts_browsers/_concepts_browser.html.haml @@ -1,4 +1,4 @@ -%div#concept_browser +%div#concept_browser.tree-container = render TabsContainerComponent.new(type:'outline') do |c| - c.item(id: 'tree-tab', selected: default_sub_menu?) do %span{title: 'Hierarchy view', 'data-controller': "tooltip"} diff --git a/app/views/ontologies/concepts_browsers/_concepts_list.html.haml b/app/views/ontologies/concepts_browsers/_concepts_list.html.haml index bf662d63b..c293620e9 100644 --- a/app/views/ontologies/concepts_browsers/_concepts_list.html.haml +++ b/app/views/ontologies/concepts_browsers/_concepts_list.html.haml @@ -14,7 +14,7 @@ - unless no_collections? %div#sd_content.card.p-1{style: 'overflow-y: auto; height: 60vh;'} - = 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 + = 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}&collectionid=#{params[:concept_collections]}" : '') do .select-collection-placeholder = t("ontologies.concepts_browsers.select_collection") diff --git a/app/views/ontologies/content_serializer.html.haml b/app/views/ontologies/content_serializer.html.haml new file mode 100644 index 000000000..beda31695 --- /dev/null +++ b/app/views/ontologies/content_serializer.html.haml @@ -0,0 +1,2 @@ += render_in_modal do + = rdf_highlighter_container(@format, @result) if @result diff --git a/app/views/ontologies/htaccess.html.haml b/app/views/ontologies/htaccess.html.haml new file mode 100644 index 000000000..bd59fcbe1 --- /dev/null +++ b/app/views/ontologies/htaccess.html.haml @@ -0,0 +1,68 @@ += render_in_modal do + %p + = t("ontologies.htaccess_redirection_description") + + %h5 + %strong + = t("ontologies.instructions_servers", server: "Apache") + %ol + %li + %strong + = t("ontologies.htaccess_redirection.instruction_1") + = t("ontologies.htaccess_redirection.instruction_1_content") + %li + %strong + = t("ontologies.htaccess_redirection.instruction_2") + = t("ontologies.htaccess_redirection.instruction_2_content") + %li + %strong + = t("ontologies.htaccess_redirection.instruction_3") + = t("ontologies.htaccess_redirection.instruction_3_content") + + .htacess-code-container + .clipboard-component-resource-content{style: 'position: absolute; right: 2%; top: 8%;'} + = render ClipboardComponent.new(message: @htaccess_content, show_content: false) + %pre.mb-0 + %code.d-block{style: 'text-wrap: pretty; word-break: break-all; color: white;'} + = @htaccess_content + + %hr + + %h5 + %strong + = t("ontologies.instructions_servers", server: "Nginx") + %ol + %li + %strong + = t("ontologies.nginx_redirection.instruction_1") + = t("ontologies.nginx_redirection.instruction_1_content") + %li + %strong + = t("ontologies.nginx_redirection.instruction_2") + = t("ontologies.nginx_redirection.instruction_2_content") + %li + %strong + = t("ontologies.nginx_redirection.instruction_3") + = t("ontologies.nginx_redirection.instruction_3_content") + %li + %strong + = t("ontologies.nginx_redirection.instruction_4") + = t("ontologies.nginx_redirection.instruction_4_content") + %li + %strong + = t("ontologies.nginx_redirection.instruction_5") + = t("ontologies.nginx_redirection.instruction_5_content") + + .htacess-code-container + .clipboard-component-resource-content{style: 'position: absolute; right: 2%; top: 8%;'} + = render ClipboardComponent.new(message: @nginx_content, show_content: false) + %pre.mb-0 + %code.d-block{style: 'text-wrap: pretty; word-break: break-all; color: white;'} + = @nginx_content + + %h5 + %strong Note + = render Display::AlertComponent.new(message: t("ontologies.redirection_note"), closable: false, type: "warning") + + .contact-support{style: "width: 100%; display: flex; justify-content: end;"} + = render Buttons::RegularButtonComponent.new(id:'regular-button', value: t("ontologies.contact_support"), variant: "primary", href: "/feedback", target: "_blank", state: "regular") \ No newline at end of file diff --git a/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml b/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml index 19b108f29..2aeafa23d 100644 --- a/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml +++ b/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml @@ -2,7 +2,7 @@ .ontologies-selector-results .horizontal-line .results-number - = "Showing #{@ontologies.length} of #{@total_ontologies_number}" + = t("ontologies.showing_ontologies_size", ontologies_size: @ontologies.length, analytics_size: @total_ontologies_number) %span.select-all{'data-action': 'click->ontologies-selector#selectall'} = t('ontologies_selector.select_all') .ontologies diff --git a/app/views/ontologies/sections/_collections.html.haml b/app/views/ontologies/sections/_collections.html.haml index e6bc8fd1b..280c64760 100644 --- a/app/views/ontologies/sections/_collections.html.haml +++ b/app/views/ontologies/sections/_collections.html.haml @@ -3,9 +3,13 @@ = no_collections_alert - else %div.ont-collections{data:{controller: 'container-splitter'}} - %div#collectionsTree.card.sidebar.pt-2{data:{'container-splitter-target': 'container'}} - = render partial: 'collections/list_view' - %div#collection_contents.pl-3{data:{'container-splitter-target': 'container'}} + %div#collectionsTree.card.sidebar{data:{'container-splitter-target': 'container'}} + = tree_container_component(id: "collections_sorted_list_view-page-1", + placeholder: t('ontologies.sections.collections_search_placeholder', acronym: @ontology.acronym), + frame_url: "/ontologies/#{@ontology.acronym}/collections", + tree_url: "/ontologies/#{@ontology.acronym}/collections?#{request.original_url.split('?')[1]}") + + %div#collection_contents{data:{'container-splitter-target': 'container'}} = render TurboFrameComponent.new(id: 'collection') do - if @collection = render partial: 'collections/collection', locals: {collection: @collection} diff --git a/app/views/ontologies/sections/_metadata.html.haml b/app/views/ontologies/sections/_metadata.html.haml index 3f84c40cf..9dd1a07df 100755 --- a/app/views/ontologies/sections/_metadata.html.haml +++ b/app/views/ontologies/sections/_metadata.html.haml @@ -11,7 +11,8 @@ = ontology_depiction_card = properties_card(t('ontologies.sections.identifiers'),t("ontologies.sections.identifiers"), @identifiers) do |values| = horizontal_list_container(values) do |v| - = render LinkFieldComponent.new(value: v, check_resolvability: true) + - generate_htaccess = v.eql?(@identifiers["ontology_portal_uri"]&.first) + = render LinkFieldComponent.new(value: v, acronym: @ontology.acronym, raw: true, check_resolvability: true, generate_link: false, generate_htaccess: generate_htaccess) = properties_dropdown('dates',t("ontologies.sections.dates"),'', @dates_properties) do |values| - Array(values).sort.map do |v| diff --git a/app/views/ontologies/sections/_schemes.html.haml b/app/views/ontologies/sections/_schemes.html.haml index 3ce9277aa..aa01c54aa 100644 --- a/app/views/ontologies/sections/_schemes.html.haml +++ b/app/views/ontologies/sections/_schemes.html.haml @@ -1,11 +1,15 @@ = render TurboFrameComponent.new(id: "schemes", data: {"turbo-frame-target": "frame"} ) do %div.ont-schemes{data:{controller: 'container-splitter'}} - %div#schemesTree.card.sidebar.pt-2{data:{'container-splitter-target': 'container'}} + %div#schemesTree.card.sidebar{data:{'container-splitter-target': 'container'}} - if no_schemes? = no_schemes_alert - else - = render partial: 'schemes/tree_view' - %div#scheme_contents.pl-3{data:{'container-splitter-target': 'container'}} + = tree_container_component(id: "schemes_sorted_list_view-page-1", + placeholder: t('ontologies.sections.schemes_search_placeholder', acronym: @ontology.acronym), + frame_url: "/ontologies/#{@ontology.acronym}/schemes?#{request.original_url.split('?')[1]}", + tree_url: "/ontologies/#{@ontology.acronym}/schemes?#{request.original_url.split('?')[1]}") + + %div#scheme_contents{data:{'container-splitter-target': 'container'}} = render TurboFrameComponent.new(id:'scheme') do = render partial: 'schemes/scheme', locals: {scheme: @scheme} diff --git a/app/views/ontologies/sections/properties.html.haml b/app/views/ontologies/sections/properties.html.haml index f3972c056..99fb1873a 100644 --- a/app/views/ontologies/sections/properties.html.haml +++ b/app/views/ontologies/sections/properties.html.haml @@ -1,8 +1,13 @@ = render TurboFrameComponent.new(id: "properties", data: {"turbo-frame-target": "frame"} ) do - %div.ont-properties{data: {controller: 'container-splitter turbo-frame', 'turbo-frame': { - 'url-value': "/ontologies/#{@ontology.acronym}/properties/show" - }}} - %div#propTree.card.pt-2.sidebar{data: {'container-splitter-target': 'container', action: 'change->turbo-frame#updateFrame'}} - = render partial: 'properties/properties_tree' - %div#prop_contents.pl-3{data: {'container-splitter-target': 'container'}} - = render partial: 'properties/show' + - if no_properties? + = no_properties_alert + - else + %div.ont-properties{data: {controller: 'container-splitter turbo-frame', 'turbo-frame-url-value': "/ontologies/#{@ontology.acronym}/properties/show"}} + %div.sidebar.position-relative.card{data: {'container-splitter-target': 'container', action: 'change->turbo-frame#updateFrame'}} + = tree_container_component(id: 'properties_sorted_list_view-page-1', + placeholder: t('ontologies.sections.properties_search_placeholder', acronym: @ontology.acronym), + frame_url: "/ontologies/#{@ontology.acronym}/properties", + tree_url: "/ontologies/#{@ontology.acronym}/properties?propertyid=#{escape(params[:propertyid])}&ontology=#{@ontology.acronym}&auto_click=false&language=#{request_lang}") + + %div#prop_contents{data: {'container-splitter-target': 'container'}} + = render partial: 'properties/show' diff --git a/app/views/ontologies_metadata_curator/_metadata_tab.html.haml b/app/views/ontologies_metadata_curator/_metadata_tab.html.haml index 9aa15ac3d..2a7a15b59 100644 --- a/app/views/ontologies_metadata_curator/_metadata_tab.html.haml +++ b/app/views/ontologies_metadata_curator/_metadata_tab.html.haml @@ -1,6 +1,5 @@ -%div.mt-5 - - %div.mx-auto.w-75 +%div + %div = form_tag("/ontologies_metadata_curator/result", method: "post", data: { turbo: true, turbo_frame: 'selection_metadata_form'}) do %div %div.mx-1.pt-3 diff --git a/app/views/properties/_properties_tree.html.haml b/app/views/properties/_properties_tree.html.haml deleted file mode 100644 index 2a429ae82..000000000 --- a/app/views/properties/_properties_tree.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%div#propTree - = render TurboFrameComponent.new(id: 'properties_tree_view', - src: "/ajax/properties/treeview?propertyid=#{escape(params[:propertyid])}&ontology=#{@ontology.acronym}&auto_click=false&language=#{request_lang}", - data: {'turbo-frame-target': 'frame'}) diff --git a/app/views/properties/_show.html.haml b/app/views/properties/_show.html.haml index 5544f93be..c03430149 100644 --- a/app/views/properties/_show.html.haml +++ b/app/views/properties/_show.html.haml @@ -3,13 +3,14 @@ - if @property.errors = render Display::AlertComponent.new(type:'info', message: @property.errors.join) - else - = render ConceptDetailsComponent.new(id:'property-details', acronym: @acronym, + = render ConceptDetailsComponent.new(id:'property-details', acronym: @acronym, concept_id: @property.id, properties: OpenStruct.new(LinkedData::Client::Models::Property.properties_to_hash(@property).first), top_keys: [], bottom_keys: [], exclude_keys: []) do |c| - c.header(stripped: true) do |t| - - t.add_row({th: t('properties.id')}, {td: link_to_with_actions(c.concept_properties[:id][:values])}) if c.concept_properties[:id][:values].present? + - t.add_row({th: t('properties.id')}, {td: link_to_with_actions(c.concept_properties[:id][:values], acronym: @acronym)}) if c.concept_properties[:id][:values].present? + - t.add_row({th: t('properties.type')}, {td: c.concept_properties[:type][:values] }) if c.concept_properties[:id][:values].present? - t.add_row({th: t('properties.preferred_name')}, {td: display_in_multiple_languages(c.concept_properties[:label][:values])}) if c.concept_properties[:label][:values].present? - t.add_row({th: t('properties.definitions')}, {td: display_in_multiple_languages(c.concept_properties[:definition][:values])}) if c.concept_properties[:definition][:values].present? - - t.add_row({th: t('properties.parent')}, {td: display_in_multiple_languages(c.concept_properties[:parents][:values])}) if c.concept_properties[:parents][:values].present? + - t.add_row({th: t('properties.parent')}, {td: display_in_multiple_languages(c.concept_properties[:parents][:values])}) if c.concept_properties[:parents][:values].present? \ No newline at end of file diff --git a/app/views/recommender/index.html.haml b/app/views/recommender/index.html.haml index e9a708ccd..10a5c8333 100644 --- a/app/views/recommender/index.html.haml +++ b/app/views/recommender/index.html.haml @@ -73,7 +73,7 @@ .recommender-page-button#get_recommendations_button{class: is_input ? "" : "d-none", 'data-recommender-target': 'button'} = render Buttons::RegularButtonComponent.new(id:'recommender', value: t('recommender.get_recommendations'), variant: "primary", type: 'submit') - .recommender-page-button{class: is_input ? "d-none" : "", 'data-action': 'click->recommender#edit'} + .recommender-page-button{class: is_input ? "d-none" : "", 'data-action': 'click->recommender#edit', id: 'recommender-edit-button'} = render Buttons::RegularButtonComponent.new(id:'edit-recommender', value: t('recommender.edit'), variant: "primary") do |btn| - btn.icon_left do = inline_svg_tag "edit.svg" @@ -94,7 +94,7 @@ - r.td do - ontologies = "" - result[:ontologies].each do |ontology| - %a.recommender-result-ontology{href: ontology[:link]} + %a.recommender-result-ontology{href: ontology_path(id: ontology[:acronym], p: 'summary')} = ontology[:acronym] - r.td do = render Display::ProgressBarComponent.new(progress: result[:final_score]) diff --git a/app/views/schemes/_scheme.html.haml b/app/views/schemes/_scheme.html.haml index d2670af16..5cfac8c31 100644 --- a/app/views/schemes/_scheme.html.haml +++ b/app/views/schemes/_scheme.html.haml @@ -1,12 +1,12 @@ = turbo_frame_tag 'scheme' do - if @scheme && !@scheme.empty? - = render ConceptDetailsComponent.new(id:'scheme-label', acronym: @ontology.acronym, + = render ConceptDetailsComponent.new(id:'scheme-label', acronym: @ontology.acronym, concept_id: @scheme.id, properties: scheme.properties, top_keys: %w[description comment], bottom_keys: %w[disjoint subclass is_a has_part], exclude_keys: []) do |c| - c.header(stripped: true) do |t| - - t.add_row({th: t('schemes.id')} , {td: link_to_with_actions(scheme["@id"])}) + - t.add_row({th: t('schemes.id')} , {td: link_to_with_actions(scheme["@id"], acronym: @ontology.acronym)}) - t.add_row({th: t('schemes.preferred_name')} , {td: display_in_multiple_languages(get_scheme_label(scheme))}) - t.add_row({th: t('schemes.type')} , {td: scheme["@type"]}) diff --git a/app/views/schemes/_tree_view.html.haml b/app/views/schemes/_tree_view.html.haml index 6e57f11be..f036242ae 100644 --- a/app/views/schemes/_tree_view.html.haml +++ b/app/views/schemes/_tree_view.html.haml @@ -1,12 +1,13 @@ -- if no_schemes? - %div - = no_schemes_alert -- else - - schemes_labels, main_scheme_label = get_schemes_labels(@schemes, @submission_latest.URI) - - selected_scheme_id = params[:schemeid].nil? && !main_scheme_label.nil? ? main_scheme_label["@id"] : CGI.unescape(params[:schemeid] || '') - - if main_scheme_label.nil? - = no_main_scheme_alert - %div - = schemes_tree(schemes_labels, main_scheme_label, selected_scheme_id) += turbo_frame_tag "schemes_sorted_list_view-page-1" do + - if no_schemes? + %div + = no_schemes_alert + - else + - schemes_labels, main_scheme_label = get_schemes_labels(@schemes, @submission_latest.URI) + - selected_scheme_id = params[:schemeid].nil? && !main_scheme_label.nil? ? main_scheme_label["@id"] : CGI.unescape(params[:schemeid] || '') + - if main_scheme_label.nil? + = no_main_scheme_alert + %div + = schemes_tree(schemes_labels, main_scheme_label, selected_scheme_id) diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index cc908c769..28ecbf2ee 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -1,15 +1,7 @@ .search-page-container .search-page-subcontainer{'data-controller': 'reveal-component'} = form_tag(search_path, method: :get,data:{turbo: true, controller: 'form-url', action: 'submit->form-url#submit'}) do - .search-page-input-container{'data-controller': 'reveal'} - .search-page-input - %input{type:"text", placeholder: t('search.search_place_holder'), name: "q", value: @search_query} - .search-page-button - = render Buttons::RegularButtonComponent.new(id:'search-page-button', value: "Search", variant: "primary", type: "submit") do |btn| - = btn.icon_right do - = inline_svg_tag "icons/search.svg" - - + = search_page_input_component(placeholder: t('search.search_place_holder'), name: "q", value: @search_query) do .search-page-advanced{'data-reveal-component-target': 'item', class: "#{@advanced_options_open ? '' : 'd-none'}"} .left .filter-container @@ -45,7 +37,7 @@ .search-page-number-of-results = "#{t('search.match_in')} #{@search_results.length} #{t('search.ontologies')}" %div.d-flex - .search-page-json.mx-4 + .search-page-json.mx-4.mt-1 = search_json_link .search-page-advanced-button.show-options{class: "#{@advanced_options_open ? 'd-none' : ''}",'data': {'action': 'click->reveal-component#show', 'reveal-component-target': 'showButton'}} .icon diff --git a/app/views/submissions/_form_content.html.haml b/app/views/submissions/_form_content.html.haml index 93b83c3c9..f7d97aed5 100644 --- a/app/views/submissions/_form_content.html.haml +++ b/app/views/submissions/_form_content.html.haml @@ -2,7 +2,7 @@ = turbo_frame_tag 'test', target: '_top' do = error_message_alert = form_for :submission, url: ontology_submission_path(params["ontology_id"], params["id"]), html: { id: "ontology_submission_form", method: :put, multipart: true, 'data-turbo': true, novalidate: 'true'} do - = render_submission_inputs('') + = render_submission_inputs('', @submission) %hr#edit-ontology-actions-devider .edit-ontology-actions .save-button diff --git a/app/views/users/_form.html.haml b/app/views/users/_form.html.haml index 920b2d2b3..3a2f38b52 100644 --- a/app/views/users/_form.html.haml +++ b/app/views/users/_form.html.haml @@ -50,8 +50,15 @@ - if using_captcha? = recaptcha_tags .d-flex - %input#user_register_mail_list{:checked => "checked", :name => "user[register_mail_list]", :type => "checkbox", :value => "1"}/ + %input.user_register_checkbox{:checked => "checked", :name => "user[register_mail_list]", :type => "checkbox", :value => "1"}/ %p#register-check-text = t('register.mailing_list', site: portal_name) + .d-flex + %input.user_register_checkbox{:name => "user[terms_and_conditions]", :type => "checkbox"}/ + %p#register-check-text + = t('register.accept_terms_and_conditions') + %a{href: $TERMS_AND_CONDITIONS_LINK, target: '_blank'} + = t('register.terms_and_conditions', site: portal_name) + %font{:color => "red"} * .register-button-container = render Buttons::RegularButtonComponent.new(id: 'register-button', value: "Register", type:'submit') diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml index 40f786db9..20a3c0bdb 100644 --- a/app/views/users/index.html.haml +++ b/app/views/users/index.html.haml @@ -1,9 +1,10 @@ = turbo_frame_tag 'users-list' do %div.my-1 = render_alerts_container - .d-flex.justify-content-end + .d-flex.justify-content-end.my-1 = rounded_button_component("#{$REST_URL}/users?display=all&apikey=#{get_apikey}") - = render TableComponent.new(id: 'admin_users', custom_class: 'border rounded p-1') do |t| + + = render TableComponent.new(id: 'admin_users', custom_class: 'border rounded p-1', searching: true, paging: true, sort_column: '7') do |t| - t.header do |h| - h.th {t('users.index.first_name')} - h.th {t('users.index.last_name')} @@ -26,21 +27,14 @@ - r.td {Date.parse(user.created).to_s} - r.td do - count = (user.ontologies&.size || 0) + (user.projects&.size || 0) - %div.d-flex.align-items-center{style: 'width: 250px'} - %span.mx-1 + %div.d-flex.align-items-center{style: 'min-width: 200px'} + %span = link_to t('users.index.detail'), user_path(user.id.split('/').last), {data: {turbo_frame: '_top'}} - %span.mx-1 + %span - if count.zero? = button_to t('users.index.delete'), user_path(user.id.split('/').last), method: :delete, class: 'btn btn-link', form: {data: { turbo: true, turbo_confirm: t('users.index.turbo_confirm'), turbo_frame: '_top'}} - else %span{data: { controller: 'tooltip' }, title:t('users.index.error_delete_message')} = link_to t('users.index.delete'), "", class: 'btn btn-link disabled' - %span.mx-1 - = link_to t('users.index.login_as'), "login_as/#{user.username}", {data: {turbo_frame: '_top'}} - %tr.empty-state - %td.text-center{:colspan => "6"} There are currently no agents. - - :javascript - $.fn.dataTable.ext.errMode = 'none'; - $("#admin_users").dataTable() - + %span + = link_to t('users.index.login_as'), "login_as/#{user.username}", {data: {turbo_frame: '_top'}} \ No newline at end of file diff --git a/app/views/users/new.html.haml b/app/views/users/new.html.haml index e6d6bc8bc..cb42a4ac0 100644 --- a/app/views/users/new.html.haml +++ b/app/views/users/new.html.haml @@ -1,9 +1,10 @@ - @title = t('register.title') = form_for(:user, :url => users_path) do |f| - unless @errors.nil? - .enable-lists{:style => "color: red; padding: 1em;"} - = t('register.account_errors') - %ul - - for error in @errors - %li= error.is_a?(Array) ? error.last : error + .d-flex.justify-content-center.mt-4 + = render Display::AlertComponent.new(type: "danger", closable: false) do + = t('register.account_errors') + %ul.mb-0 + - for error in @errors + %li= error.is_a?(Array) ? error.last : error = render :partial => 'form', :locals => {:f => f} diff --git a/config/bioportal_config_env.rb.sample b/config/bioportal_config_env.rb.sample index cb1ae1ccf..bf07df798 100644 --- a/config/bioportal_config_env.rb.sample +++ b/config/bioportal_config_env.rb.sample @@ -1,23 +1,30 @@ # Organization info $ORG = ENV['ORG'] $ORG_URL = ENV['ORG_URL'] + # Site name (required) $SITE = ENV['SITE'] + # Full string for site, EX: "NCBO BioPortal", do not modify $ORG_SITE = $ORG.nil? || $ORG.empty? ? $SITE : "#{$ORG} #{$SITE}" + # The URL for the BioPortal Rails UI (this application) $UI_URL = ENV['UI_URL'] + # If you are running a PURL server to provide URLs for ontologies in your BioPortal instance, enable this option $PURL_ENABLED = false # The PURL URL is generated using this prefix + the abbreviation for an ontology. # The PURL URL generation algorithm can be altered in app/models/ontology_wrapper.rb $PURL_PREFIX = 'http://purl.bioontology.org/ontology' + + # If your BioPortal installation includes Annotator set this to false $ANNOTATOR_DISABLED = false # Unique string representing the UI's id for use with the BioPortal Core $API_KEY = ENV['API_KEY'] # BioPortal API service address $REST_URL = ENV['API_URL'] + # Annotator REST service address # $ANNOTATOR_URL = "http://services.stageportal.lirmm.fr/annotator" $ANNOTATOR_URL = $PROXY_URL = ENV['ANNOTATOR_URL'] @@ -25,21 +32,25 @@ $ANNOTATOR_URL = $PROXY_URL = ENV['ANNOTATOR_URL'] $NCBO_ANNOTATORPLUS_ENABLED = ENV['NCBO_ANNOTATORPLUS_ENABLED'] $NCBO_ANNOTATOR_URL = ENV['NCBO_ANNOTATOR_URL'] $NCBO_API_KEY = ENV['NCBO_API_KEY'] + # Fairness Assessment. $FAIRNESS_DISABLED = ENV['FAIRNESS_DISABLED'] $FAIRNESS_URL = ENV['FAIRNESS_URL'] -# Announcements sympa mailing list REQUEST address, EX: list-request@lists.example.org -$ANNOUNCE_LIST_SERVICE ||= 'SERVICE_EXAMPLE' -$ANNOUNCE_SERVICE_HOST ||= 'service@test.com' -$ANNOUNCE_LIST ||= 'users-list@test' + + + + # Used to define other bioportal that can be mapped to # Example to map to ncbo bioportal : {"ncbo" => {"api" => "http://data.bioontology.org", "ui" => "http://bioportal.bioontology.org", "apikey" => ""} # Then create the mapping using the following class in JSON : "http://purl.bioontology.org/ontology/MESH/C585345": "ncbo:MESH" # Where "ncbo" is the namespace used as key in the interportal_hash $INTERPORTAL_HASH = {} + $NOT_DOWNLOADABLE = {} + # Bugsnag API key for monitoring exception #$BUGSNAG_API_KEY= + # OAuth2 authentication $OMNIAUTH_PROVIDERS = { github: { @@ -71,29 +82,40 @@ $OMNIAUTH_PROVIDERS = { enable: false } }.freeze + # Don't load and don't display recent mappings if false, in case of too many mappings (take longer to load homepage) $DISPLAY_RECENT = false + # If true then the UI will get available recognize at API_URL/annotators/recognizers $MULTIPLE_RECOGNIZERS = false + # Remove download for these ontologies. Default: # ["CPT","ICD10","ICNP","ICPC2P","MDDB","MEDDRA","MSHFRE","MSHSPA_1","NDDF","NDFRT","NIC","RCD","SCTSPA","SNOMEDCT","WHO-ART"] $RESTRICTED_DOWNLOADS = [] + # Ontolobridge endpoint url $ONTOLOBRIDGE_BASE_URL = 'https://ontolobridge.ccs.miami.edu/api-test/requests' # Ontolobridge authentication token $ONTOLOBRIDGE_AUTHENTICATION_TOKEN = 'Token Uq2pae73ktMtmgjUgtnhEOuHxr9sZeuK' + # Ontologies for which to enable the new term request (Ontolobridge) tab $NEW_TERM_REQUEST_ONTOLOGIES = [] + # Legacy REST core service address (BioPortal v3.x and lower) $LEGACY_REST_URL = 'http://example.org:8080/bioportal' + # Release version text (appears in footer of all pages, except 404 and 500 errors) $RELEASE_VERSION = ENV['RELEASE_VERSION'] + # Enable Slices, filtering of ontologies based on subdomain and ontology groups $ENABLE_SLICES = false + # Google Analytics ID (optional) $ANALYTICS_ID = ENV['ANALYTICS_ID'] + # Enable client request caching $CLIENT_REQUEST_CACHING = true + # Email settings ActionMailer::Base.smtp_settings = { address: '', # smtp server address, ex: smtp.example.org @@ -108,8 +130,8 @@ $SUPPORT_EMAIL = ENV['SUPPORT_EMAIL'] # Email used to send notifications $NOTIFICATION_EMAIL = ENV['SUPPORT_EMAIL'] -# Bugsnag is a tool for exceptions tracking https://www.bugsnag.com/ -$BUGSNAG_APIKEY + + # reCAPTCHA # In order to use reCAPTCHA on the account creation and feedback submission pages: # 1. Obtain a reCAPTCHA v2 key from: https://www.google.com/recaptcha/admin @@ -123,10 +145,12 @@ $BUGSNAG_APIKEY ENV['USE_RECAPTCHA'] = 'false' # Custom BioPortal logging require 'log' + # URL where BioMixer GWT app is located # $BIOMIXER_URL = "http://bioportal-integration.bio-mixer.appspot.com" $BIOMIXER_URL = ENV['BIOMIXER_URL'] $BIOMIXER_APIKEY = ENV['BIOMIXER_APIKEY'] + ## # Custom Ontology Details # Custom details can be added on a per ontology basis using a key/value pair as columns of the details table @@ -135,11 +159,20 @@ $BIOMIXER_APIKEY = ENV['BIOMIXER_APIKEY'] # $ADDITIONAL_ONTOLOGY_DETAILS = { "STY" => { "Additional Detail" => "Text to be shown in the right-hand column." } } ## $ADDITIONAL_ONTOLOGY_DETAILS = {} + # Front notice appears on the front page only and is closable by the user. It remains closed for seven days (stored in cookie) $FRONT_NOTICE = '' # Site notice appears on all pages and remains closed indefinitely. Stored below as a hash with a unique key and a string message # EX: $SITE_NOTICE = { :unique_key => 'Put your message here (can include html if you use single quotes).' } $SITE_NOTICE = {} + +$TERMS_AND_CONDITIONS_LINK = 'https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/terms-conditions-naDsDo2Zxq' +$CITE_ANNOTATOR = 'https://hal.science/hal-00492024' +$ANNOTATOR_API_DOC = 'https://data.agroportal.lirmm.fr/documentation#nav_annotator' +$CITE_RECOMMENDER = 'https://doi.org/10.1186/s13326-017-0128-y' +# Resource term +$RESOURCE_TERM = ENV['RESOURCE_TERM'] || 'ontology' + $HOME_PAGE_LOGOS = [ { img_src: 'logos/supports/numev.png', @@ -239,6 +272,7 @@ $FOOTER_LINKS = { products: { release_notes: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/release-notes-btKjZk5tU2", api: "https://data.agroportal.lirmm.fr/", + tools: "/tools", sparql: "https://sparql.agroportal.lirmm.fr/test/", ontoportal: "https://ontoportal.org/" }, @@ -248,8 +282,9 @@ $FOOTER_LINKS = { agro_documentation: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/public-documentation-QMpsC9aVBb" }, agreements: { - terms: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/terms-and-conditions-naDsDo2Zxq", - privacy_policy: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/privacy-policy-wsJtxnwHmo", + terms: $TERMS_AND_CONDITIONS_LINK, + privacy_policy: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/terms-conditions-naDsDo2Zxq#h-privacy-policy", + legal_notices: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/terms-conditions-naDsDo2Zxq#h-legal-notice" }, about: { about_us: "https://github.com/agroportal/project-management", @@ -260,10 +295,11 @@ $FOOTER_LINKS = { } } -$NOT_DOWNLOADABLE = {} + $UI_THEME = ENV['UI_THEME'] || 'ontoportal' $HOSTNAME = ENV['API_URL'] + if $HOSTNAME $HOSTNAME = ENV['API_URL'].split('data.').last # add custom stage server configuration if needed (e.g bioportal_config_development_stageportal.lirmm.fr) @@ -271,10 +307,3 @@ if $HOSTNAME require_relative "bioportal_config_development_#{$HOSTNAME}" end end -$CITE_ANNOTATOR = 'https://hal.science/hal-00492024' -$ANNOTATOR_API_DOC = 'https://data.agroportal.lirmm.fr/documentation#nav_annotator' - -$CITE_RECOMMENDER = 'https://doi.org/10.1186/s13326-017-0128-y' - -# Resource term -$RESOURCE_TERM = ENV['RESOURCE_TERM'] || 'ontology' diff --git a/config/bioportal_config_test.rb b/config/bioportal_config_test.rb index 3487a3628..44169096b 100644 --- a/config/bioportal_config_test.rb +++ b/config/bioportal_config_test.rb @@ -55,7 +55,7 @@ # If your main UI is hosted at example.org and you add custom.example.org pointing to the same Rails installation # you could filter the ontologies visible at custom.example.org by adding this to the hash: "custom" => { :name => "Custom Slice", :ontologies => [1032, 1054, 1099] } # Any number of slices can be added. Groups are added automatically using the group acronym as the subdomain. -$ENABLE_SLICES = true +$ENABLE_SLICES = false $ONTOLOGY_SLICES = {} # Cube metrics reporting diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml new file mode 100644 index 000000000..6495aad7c --- /dev/null +++ b/config/i18n-tasks.yml @@ -0,0 +1,8 @@ +# config/i18n-tasks.yml +<% require 'i18n-tasks-csv' %> + +csv: + export: + - "tmp/i18n-export/main.csv" + import: + - tmp/i18n-export/main.csv \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index bae0d3589..2916588c7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -66,7 +66,7 @@ en: provide_ontology_or_concept: Please provide an ontology id or concept id with an ontology id. search_for_class: Please search for a class using the Jump To field above missing_roots_for_ontology: Missing @roots for %{acronym} - missing_roots: Missing roots %{id} + missing_roots: Missing roots missing_class: Missing class %{root_child} missing_class_ontology: Missing class %{acronym} / %{concept_id} error_simplify_class: "Failure to simplify class: %{cls}" @@ -114,7 +114,7 @@ en: ontologies_with_errors: Ontologies with errors total_visits: Total visits active_users: Active users - visit_users: "%{ visits } users since last month" + visit_users: "%{visits} users since last month" ontology_visits: Ontology visits unique_users_visits: Unique users visits page_visits: Page visits of this month @@ -144,6 +144,7 @@ en: categories: Categories persons_and_organizations: Persons & Organizations sparql: SPARQL + search: Search & Indexation report_generated_on: "Report generated on:" licensed_to: Licensed to appliance_id: Appliance ID @@ -238,6 +239,35 @@ en: license_from_being_saved: "prohibited this license from being saved:" paste_your_license: Paste your license key into the text area submit: Submit + search: + index: + index_data: "INDEX DATA" + collections_management: "COLLECTIONS MANAGEMENT" + name: "Name" + actions: "Actions" + ontology: "Ontology" + agent: "Agent" + ontology_submission: "Ontology submission" + select_model: "Select a model to index" + generate_schema: "Generate schema" + see_schema: "See schema" + see_indexed_data: "See indexed data" + show: + field_name: "Field name" + type: "Type" + multi_valued: "Multi valued" + indexed: "Indexed" + stored: "Stored" + search: + search_query: "Search query" + page: "Page" + page_size: "Page size" + found_documents: "Found %{count} documents" + id: "ID" + properties: "Properties" + show_more: "+ Show more ..." + show_less: "- Show less ..." + annotator: title: Annotator description: Get annotations for text with ontology classes @@ -268,7 +298,10 @@ en: score: Score api_documentation: "Annotator API documentation" insert_sample_text: Insert sample text - sample_text: Melanoma is a malignant tumor of melanocytes found mainly in the skin but also in the intestine and the eye. + sample_text: There has been many recent studies on the use of microbial antagonists to control diseases incited by soilborne and airborne plant pathogenic bacteria + and fungi, in an attempt to replace existing methods of chemical control and avoid extensive use of fungicides, which often lead to resistance in plant pathogens. + In agriculture, plant growth-promoting and biocontrol microorganisms have emerged as safe alternatives to chemical pesticides. Streptomyces spp. and their + metabolites may have great potential as excellent agents for controlling various fungal and bacterial phytopathogens. concepts: error_valid_concept: "Error: You must provide a valid concept id" missing_roots: Missing roots @@ -468,6 +501,7 @@ en: ontoportal: OntoPortal release_notes: Release Notes api: API + tools: Tools sparql: SPARQL support: Support contact_us: Contact Us @@ -477,6 +511,7 @@ en: agreements: Legal terms: Terms and Conditions privacy_policy: Privacy Policy + legal_notices: Legal Notices cite_us: Cite Us acknowledgments: Acknowledgments about: About @@ -526,6 +561,13 @@ en: ontology_visits: Ontology visits mappings: all: All + description: Dive into an overview of the mappings in the bubble view, efficiently locate a specific ontology in the table view or upload your own mappings. + tabs: + bubble_view: Bubbles view + table_view: Table view + upload_mappings: Upload mappings + filter_ontologies: Filter ontologies in the bubble view + filter_bubbles: Filter bubbles external_mappings: "External Mappings (%{number_with_delimiter})" interportal_mappings: "Interportal Mappings - %{acronym} (%{number_with_delimiter})" test_bulk_load: This is the mappings produced to test the bulk load @@ -538,11 +580,19 @@ en: find_mappings: Find mappings of a class/concept intro: Find all the mappings of an ontology loading_mappings: Loading mappings... - mappings_bulk_load: Upload mappings in bulk from a source file no_mappings_available: No mappings available title: Mappings upload_mappings: Upload mappings select_ontologies_list: Select ontologies + bubble_view_legend: + bubble_size: "Bubble size:" + bubble_size_desc: "The global number of mappings with all other ontologies." + color_degree: "Color degree:" + color_degree_desc: "The number of mappings with the selected ontology." + yellow_bubble: "Yellow bubble: " + selected_bubble: "The selected bubble." + less_mappings: "Less mappings" + more_mappings: "More mappings" count: ontology: Ontology mappings: Mappings @@ -750,6 +800,8 @@ en: first_name: First name last_name: Last name mailing_list: Register to the %{site}'s mailing list + accept_terms_and_conditions: I acknowledge and accept + terms_and_conditions: "%{site}'s terms and conditions." optional: "(Optional)" password: Password title: Register @@ -771,6 +823,7 @@ en: validate_orcid: Please enter a valid ORCID. validate_username: please enter a valid username valid_email_adresse: Please enter a valid email adresse + validate_terms_and_conditions: Accepting the terms and conditions is required, please check the box to proceed. first_name_required: First name field is required last_name_required: Last name field is required index: @@ -972,17 +1025,17 @@ en: and top concept assertion. %{link} ontology_obo_language_link: the OBOinOWL parser. ontology_obo_language_help: > - OBO ontologies submitted to %{portal_name} will be parsed by the OWL-API which integrates %{link} - The resulting RDF triples will then be loaded in %{portal_name} triple-store. + OBO ontologies submitted to %{portal_name} will be parsed by the OWL-API which integrates %{link} + The resulting RDF triples will then be loaded in %{portal_name} triple-store. ontology_owl_language_link: the Protégé ontology_owl_language_help: > - OWL ontologies submitted to %{portal_name} will be parsed by the OWL-API. An easy way to verify if your ontology will parse is to open it with - %{link} - software which does use the same component. + OWL ontologies submitted to %{portal_name} will be parsed by the OWL-API. An easy way to verify if your ontology will parse is to open it with + %{link} + software which does use the same component. ontology_umls_language_link: by the UMLS2RDF tool. ontology_umls_language_help: > - UMLS-RRF resources are usually produced %{link} + UMLS-RRF resources are usually produced %{link} groups: Groups visibility: Visibility @@ -994,7 +1047,7 @@ en: help_text: Help text contact_name: "%{name} Name" contact_email: "%{name} Email" - edit_metadata_instruction: "Edit the metadata of your ontology here. Some of these values are used by %{portal_name} functionalities, including for FAIRness assessment. %{link)" + edit_metadata_instruction: "Edit the metadata of your ontology here. Some of these values are used by %{portal_name} functionalities, including for FAIRness assessment. %{link}" edit_metadata_instruction_link: "See guidelines and recommendations for metadata here." license_help: "%{portal_name} requires an URI for the license. If you do not find your choice here, %{link}" license_help_link: "Please pick up an URI from here." @@ -1004,6 +1057,10 @@ en: help_creator: "The following properties take for value an 'agent' in %{portal_name} (either a person or an organization). These agents are shared over all the ontologies and suggested with autocompletion if they already exist. Editing an agent here will change it to all the ontologies that agent is involved in." version_help: "For more information on how to encode versioning information in an ontology, see %{link}" version_helper_link: "guidelines and recommendations." + instances: + id: ID + type: Type + label: Label collections: error_valid_collection: "Error: You must provide a valid collection id" no_collections_alert: "%{acronym} does not contain collections (skos:Collection)" @@ -1110,7 +1167,8 @@ en: ontology_processing_failed: "The ontology processing failed, with the current statuses: %{status}" ontology_parsing_succeeded: "The ontology parsing succeeded, but some processing steps failed, here are the current statuses: %{status}" upload_an_ontology: Upload an ontology. Sections such as %{ontology} will be available once done. - ontology_is_processing: The ontology is processing. Sections such as %{ontology} will be available once processing is complete. + new_ontology_is_processing: The ontology is processing. Sections such as %{ontology} will be available once processing is complete. + ontology_is_processing: The ontology is processing. Sections such as %{ontology} will be updated once processing is complete. contact_support: Contact support edit_natural_languages: Edit natural languages of %{acronym} edit_available_languages: Click here to edit available languages @@ -1263,6 +1321,10 @@ en: ncbo_widget_wiki: NCBO Widget Wiki file: file. no_class_concept_found: No class/concept found + instances_search_placeholder: Search an Instance in %{acronym} + properties_search_placeholder: Search a property in %{acronym} + schemes_search_placeholder: Search a scheme in %{acronym} + collections_search_placeholder: Search a collection in %{acronym} metadata: fair_score_title: FAIR score @@ -1286,6 +1348,35 @@ en: export_metadata: Export all metadata abstract: Abstract description: Description + + htaccess_modal_title: "Rewrite rules for %{acronym} ontology" + instructions_servers: Instructions for %{server} servers + htaccess_redirection_description: "We offer a seamless solution to make your ontology URIs resolvable and content negotiable by allowing URL redirection for your ontology URIs to our Agroportal URIs. To facilitate this process, we've provided you with a set of .htaccess rewrite rules. By following the simple instructions below, you'll be able to implement these rules swiftly and efficiently, ensuring smooth redirection" + + redirection_note: | + This redirection works only for uri with the form of: url/path/resource_id + This will not work for uris in the form of: url/path/to/something#resource_id OR url/path/to/something:resource_id + htaccess_redirection: + instruction_1: "Access .htaccess File:" + instruction_1_content: "Locate the .htaccess file in the root directory of your website, if it doesn't exist create one. This file controls how your web server behaves and is often used for URL rewriting and redirection." + instruction_2: "Copy and Paste Redirect Rules in the .htaccess file:" + instruction_2_content: "Copy the rewrite rules provided in the black rectangle. Open the .htaccess file and paste the copied redirect rules into the file" + instruction_3: "Enable rewrite module (Linux):" + instruction_3_content: "you'll need to ensure that the Apache module called 'rewrite' is enabled by running the command `sudo a2enmod rewrite`" + + + nginx_redirection: + instruction_1: "Access Nginx configuration file:" + instruction_1_content: "The location of this file can vary depending on your system, but common locations include /etc/nginx/nginx.conf, /etc/nginx/sites-available/default or /usr/local/etc/nginx/nginx.conf." + instruction_2: "Locate the server block or location block where you want to enable URL rewriting:" + instruction_2_content: "This is typically within the server { ... } block." + instruction_3: "Copy and Paste Redirect Rules :" + instruction_3_content: "Copy the rewrite rules provided in the black rectangle. Inside the appropriate block, add the copied rules to enable URL rewriting" + instruction_4: "Test the configuration for syntax errors:" + instruction_4_content: "Run the following command to test the redirection configuration `sudo nginx -t`" + instruction_5: "Reload Nginx to apply the changes:" + instruction_5_content: "Restart the nginx server to apply the redirection using this command `sudo systemctl reload nginx`" + components: check_resolvability: checking resolvability... error_block: child_data_generator block did not provide all the child arguements @@ -1324,11 +1415,16 @@ en: join_the_count: Join the %{count} users, watching this resource and be notified of all its updates see_more: See more... see_less: See less... + tree_view_empty: No result found + copy_original_uri: Copy original URI + copy_portal_uri: Copy %{portal_name} URI properties: id: ID + type: Type preferred_name: Preferred name definitions: Definitions parent: Parent + no_properties_alert: "%{acronym} does not contain properties" visits: ontology_visits: Ontology visits name: name @@ -1349,4 +1445,23 @@ en: select_all: Select all cancel: Cancel apply: Apply - unselect_all: Unselect all \ No newline at end of file + unselect_all: Unselect all + tools: + search: + title: "Search content (coming)" + description: > + This tool/service enables users to search for any Resource Description Framework (RDF) element within the portal. + Users can search by either the unique identifier (URI) associated with the RDF element or by its label. + It facilitates efficient navigation and retrieval of specific RDF elements, enhancing user experience and productivity within the portal. + converter: + title: "Content converter (coming)" + description: > + The Content Converter tool/service offers the capability to convert any RDF element into various formats such as RDF/XML, Turtle, Ntriples, or JSON. + This functionality allows users to transform RDF data into formats suitable for different purposes or compatible with various systems and applications. + It promotes interoperability and flexibility in handling RDF data within the portal ecosystem. + url_checker: + title: "URI resolvability checker" + description: > + This tool/service verifies the resolvability of Uniform Resource Identifiers (URIs) and their content negotiability. + It checks whether a given URI is accessible and whether the content associated with it can be negotiated based on the client's preferences. + This functionality ensures the reliability and accessibility of linked resources within the RDF ecosystem, aiding in maintaining data integrity and facilitating seamless integration with external resources. \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index c1e0782be..2ac75c679 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -41,7 +41,7 @@ fr: formats: year_month_day_concise: "%Y-%m-%d" # 2017-03-01 month_day_year: "%b %-d, %Y" # Mar 1, 2017 - monthfull_day_year: "%B %-d, %Y" # Mars 1, 2017 + monthfull_day_year: "%-d %B %Y" # Mars 1, 2017 language: translation_not_available: "Traduction en %{language} non disponible" @@ -71,7 +71,7 @@ fr: provide_ontology_or_concept: Veuillez fournir un identifiant d'ontologie ou un identifiant de concept avec un identifiant d'ontologie. search_for_class: Veuillez rechercher une classe à l'aide du champ Jump To ci-dessus missing_roots_for_ontology: Racines manquantes pour %{acronym} - missing_roots: Racines manquantes %{id} + missing_roots: Racines manquantes missing_class: Classe manquante %{root_child} missing_class_ontology: Classe manquante %{acronym} / %{concept_id} error_simplify_class: "Échec de la simplification de la classe : %{cls}" @@ -79,7 +79,7 @@ fr: incomplete_simple_ontology: "Ontologie simple incomplète : %{id}, %{ont}" label_xl: error_valid_label_xl: "Erreur : Vous devez fournir un identifiant de label_xl valide" - + admin: query_not_permitted: Requête non autorisée update_info_successfully: Informations de mise à jour récupérées avec succès @@ -119,7 +119,7 @@ fr: ontologies_with_errors: Ontologies avec erreurs total_visits: Visites totales active_users: Utilisateurs actifs - visit_users: "%{ visits } utilisateurs depuis le mois dernier" + visit_users: "%{visits} utilisateurs depuis le mois dernier" ontology_visits: Visites d'ontologies unique_users_visits: Visites d'utilisateurs uniques page_visits: Visites de page de ce mois @@ -147,8 +147,9 @@ fr: metadata_administration: Administration des métadonnées groups: Groupes categories: Catégories - persons_and_organizations: Personnes & Organisations + persons_and_organizations: Personnes et Organisations sparql: SPARQL + search: Recherche et Indexation report_generated_on: "Rapport généré le :" licensed_to: Licencié à appliance_id: ID de l'appareil @@ -240,6 +241,34 @@ fr: license_from_being_saved: "a empêché cette licence d'être sauvegardée :" paste_your_license: Collez votre clé de licence dans la zone de texte submit: Soumettre + search: + index: + index_data: "DONNÉES INDEXES" + collections_management: "GESTION DES COLLECTIONS" + name: "Nom" + actions: "Actions" + select_model: "Sélectionnez un modèle à indexer" + ontology: "Ontologie" + agent: "Agent" + ontology_submission: "Soumission" + generate_schema: "Générer le schéma" + see_schema: "Voir le schéma" + see_indexed_data: "Voir les données indexées" + show: + field_name: "Nom du champ" + type: "Type" + multi_valued: "Multivalué" + indexed: "Indexé" + stored: "Stocké" + search: + search_query: "Requête de recherche" + page: "Page" + page_size: "Taille de la page" + found_documents: "Trouvé %{count} documents" + id: "ID" + properties: "Propriétés" + show_more: "+ Afficher plus ..." + show_less: "- Afficher moins ..." annotator: title: Annotateur @@ -271,7 +300,10 @@ fr: get_annotation: Obtenir des annotations total_results: Résultats totaux insert_sample_text: Insérer un texte d'exemple - sample_text: Le mélanome est une tumeur maligne des mélanocytes trouvée principalement dans la peau mais aussi dans l'intestin et l'œil. + sample_text: Il y a eu de nombreuses études récentes sur l'utilisation d'antagonistes microbiens pour contrôler les maladies causées par des bactéries phytopathogènes telluriques + et aériennes, dans le but de remplacer les méthodes actuelles de contrôle chimique et d'éviter l'utilisation extensive de fongicides, qui conduisent souvent à une résistance chez les pathogènes des plantes. + En agriculture, les microorganismes promoteurs de croissance des plantes et de biocontrôle ont émergé comme des alternatives sûres aux pesticides chimiques. Les espèces de Streptomyces et leurs + métabolites peuvent avoir un grand potentiel en tant qu'agents excellents pour contrôler divers phytopathogènes fongiques et bactériens. concepts: error_valid_concept: "Erreur : Vous devez fournir un identifiant de concept valide" @@ -313,7 +345,7 @@ fr:

Justification (optionnel)
Fournissez ici toute information supplémentaire concernant le terme demandé.

- + home: ontoportal_instances: "Autres installations d’OntoPortal" bug: Bug @@ -475,6 +507,7 @@ fr: ontoportal: OntoPortal release_notes: Notes de version api: API + tools: Outils sparql: SPARQL support: Aides contact_us: Contactez-nous @@ -484,6 +517,7 @@ fr: agreements: Légal terms: Termes et conditions privacy_policy: Politique de confidentialité + legal_notices: Mentions légales cite_us: Citez-nous acknowledgments: Remerciements about: À propos @@ -535,69 +569,84 @@ fr: ontology_visits: Visites d'ontologies mappings: - select_ontologies_list: "Sélectionner des ontologies" - external_mappings: "Alignements Externes (%{number_with_delimiter})" - interportal_mappings: "Alignements Interportail - %{acronym} (%{number_with_delimiter})" - test_bulk_load: Ceci est les alignements produits pour tester le chargement en masse - mapping_created: Alignement créé - mapping_updated: Alignement mis à jour + all: Tous + description: Plongez dans une vue d'ensemble des mappings dans la vue en bulles, localisez efficacement une ontologie spécifique dans la vue en tableau ou téléchargez vos propres mappings. + tabs: + bubble_view: Vue en bulles + table_view: Vue en tableau + upload_mappings: Télécharger des mappings + filter_ontologies: Filtrer les ontologies dans la vue en bulles + filter_bubbles: Filtrer les bulles + external_mappings: "Mappings externes (%{number_with_delimiter})" + interportal_mappings: "Mappings interportails - %{acronym} (%{number_with_delimiter})" + test_bulk_load: Ceci sont les mappings créés pour tester le chargement en masse + mapping_created: Mapping créé + mapping_updated: Mapping mis à jour mapping_deleted: "%{map_id} supprimé avec succès" - mapping_not_found: "Alignement %{id} non trouvé" + mapping_not_found: "Mapping %{id} non trouvé" error_of_source_and_target: Les concepts source et cible doivent être spécifiés - mapping_issue: "Problème d'alignement avec '%{mapping}' : %{message}" - find_mappings: Trouver les alignements d'une classe/concept - intro: Trouver tous les alignements d'une ontologie - loading_mappings: Chargement des alignements... - mappings_bulk_load: Télécharger des alignements en masse à partir d'un fichier source - no_mappings_available: Aucun alignement disponible - title: Alignements - upload_mappings: Télécharger des alignements - all: Tout + mapping_issue: "Problème de mapping avec '%{mapping}' : %{message}" + find_mappings: Trouver tous les mappings d'une classe/concept + intro: Trouver tous les mappings d'une ontologie + loading_mappings: Chargement des mappings... + no_mappings_available: Aucun mapping disponible + title: Mappings + upload_mappings: Télécharger des mappings + select_ontologies_list: Sélectionner les ontologies + bubble_view_legend: + bubble_size: "Taille de la bulle :" + bubble_size_desc: "Le nombre total de correspondances avec toutes les autres ontologies." + color_degree: "Degré de couleur :" + color_degree_desc: "Le nombre de correspondances avec l'ontologie sélectionnée." + yellow_bubble: "Bulle jaune : " + selected_bubble: "La bulle sélectionnée." + less_mappings: "Moins de correspondances" + more_mappings: "Plus de correspondances" count: ontology: Ontologie - mappings: Alignements - no_mappings: Il n'y a aucun alignement vers ou depuis cette ontologie + mappings: Mappings + no_mappings: Il n'y a aucun mapping vers ou depuis cette ontologie form: source_class: Classe source - mapping_name: Description du alignement (nom) + mapping_name: Description du mapping (nom) contact_info: Informations de contact - mapping_source_name: Nom de la source (ID de l'ensemble de alignement) + mapping_source_name: Nom source (ID de l'ensemble de mappings) mapping_comment: Commentaire - mapping_relation: Type de relation de alignement - save: Sauvegarder + mapping_relation: Type de relation de mapping + save: Enregistrer mapping_table: - mapping_to: Alignement vers + mapping_to: Mapping vers relations: Relations source: Source type: Type actions: Actions - no_mappings: Il n'y a actuellement aucun alignement pour cette classe. + no_mappings: Il n'y a actuellement aucun mapping pour cette classe. mapping_type_selector: - mapping_type: Type de alignement + mapping_type: Type de mapping internal: Interne - interportal: InterPortail + interportal: Interportail external: Externe target_class: Classe cible details: Détails ontology_acronym: Ontologie (acronyme) class: Classe - ontology_acronym_placeholder: Entrez l'acronyme de l'ontologie + ontology_acronym_placeholder: Entrez l'ACRONYM de l'ontologie class_uri_placeholder: Entrez l'URI de la classe ontology_uri_placeholder: Entrez l'URI de l'ontologie show_line: - edit_modal: Éditer + edit_modal: Modifier delete_button: Supprimer - turbo_confirm: Êtes-vous sûr ? - edit_mapping: Éditer l'alignement pour %{preflabel} + turbo_confirm: Êtes-vous sûr(e) ? + edit_mapping: Modifier le mapping pour %{preflabel} show: - no_mappings_found: Aucun alignement trouvé + no_mappings_found: Aucun mapping trouvé bulk_loader: loader: example_of_valid_file: Voir un exemple de fichier valide - save: Sauvegarder + save: Enregistrer loaded_mappings: - mappings_created: "%{size} alignements créés avec succès" - id: Id + mappings_created: "%{size} mappings créés avec succès" + id: ID source: Source target: Cible relation: Relation @@ -605,6 +654,7 @@ fr: actions: Actions see_other_properties: Voir d'autres propriétés + agents: not_found_agent: Agent avec id %{id} add_agent: Nouvel agent ajouté avec succès @@ -664,7 +714,7 @@ fr: metadata: additional_metadata: Métadonnées supplémentaires header: - last_submission_date: Date de la dernière soumission + last_submission_date: Date de la dernière soumission le projects: project_not_found: "Projet non trouvé : %{id}" @@ -765,6 +815,8 @@ fr: first_name: Prénom last_name: Nom mailing_list: S'inscrire à la liste de diffusion de %{site} + accept_terms_and_conditions: Je reconnais et j'accepte + terms_and_conditions: les termes et conditions de %{site}. optional: "(Optionnel)" password: Mot de passe title: S'inscrire @@ -787,6 +839,7 @@ fr: validate_orcid: Veuillez entrer un ORCID valide. validate_username: veuillez entrer un nom d'utilisateur valide valid_email_adresse: Veuillez entrer une adresse email valide + validate_terms_and_conditions: L'acceptation des termes et conditions est requise, veuillez cocher la case pour continuer. first_name_required: Le champ du prénom est requis last_name_required: Le champ du nom est requis index: @@ -1024,6 +1077,10 @@ fr: help_creator: "Les propriétés suivantes prennent pour valeur un 'agent' dans %{portal_name} (soit une personne, soit une organisation). Ces agents sont partagés sur toutes les ontologies et sont suggérés avec l'autocomplétion s'ils existent déjà. Modifier un agent ici le modifiera pour toutes les ontologies auxquelles cet agent est impliqué." version_help: "Pour plus d'informations sur la manière d'encoder les informations de version dans une ontologie, consultez %{link}" version_helper_link: "les lignes directrices et recommandations." + instances: + id: ID + type: Type + label: Label collections: error_valid_collection: "Erreur : Vous devez fournir un identifiant de collection valide" no_collections_alert: "%{acronym} ne contient pas de collections (skos:Collection)" @@ -1136,7 +1193,8 @@ fr: ontology_processing_failed: "Le traitement de l'ontologie a échoué, avec les statuts actuels : %{status}" ontology_parsing_succeeded: "L'analyse de l'ontologie a réussi, mais certaines étapes de traitement ont échoué, voici les statuts actuels : %{status}" upload_an_ontology: Téléchargez une ontologie. Des sections comme %{ontology} seront disponibles une fois terminées. - ontology_is_processing: L'ontologie est en cours de traitement. Des sections comme %{ontology} seront disponibles une fois le traitement terminé. + new_ontology_is_processing: L'ontologie est en cours de traitement. Des sections comme %{ontology} seront disponibles une fois le traitement terminé. + ontology_is_processing: L'ontologie est en cours de traitement. Des sections comme %{ontology} seront mis a jours une fois le traitement terminé. contact_support: Contacter le support edit_natural_languages: Modifier les langues naturelles de %{acronym} edit_available_languages: Cliquez ici pour modifier les langues disponibles @@ -1288,6 +1346,11 @@ fr: ncbo_widget_wiki: Wiki des Widgets NCBO file: fichier. no_class_concept_found: Aucune classe/concept trouvé + instances_search_placeholder: Rechercher une instance dans %{acronym} + properties_search_placeholder: Rechercher une propriété dans %{acronym} + schemes_search_placeholder: Rechercher un schéma dans %{acronym} + collections_search_placeholder: Rechercher une collection dans %{acronym} + metadata: fair_score_title: Score FAIR total_score: "Score total : %{score} ( %{normalized_score}%)" @@ -1315,6 +1378,35 @@ fr: abstract: Résumé description: Description + htaccess_modal_title: "Règles de redirection pour l'ontologie %{acronym}" + instructions_servers: Instructions pour les servers %{server} + htaccess_redirection_description: "Nous proposons une solution transparente pour rendre les URI de votre ontologie résolubles et le contenu négociable en autorisant la redirection d'URL de vos URI d'ontologie vers nos URI d'Agroportal. Pour faciliter ce processus, nous vous avons fourni un ensemble de règles de réécriture .htaccess. En suivant les instructions simples ci-dessous, vous serez en mesure de mettre en œuvre ces règles rapidement et efficacement, garantissant ainsi une redirection fluide" + + redirection_note: > + Cette redirection fonctionne uniquement pour les uri sous la forme: url/path/resource_id. + Cela ne fonctionnera pas pour les URIS sous la forme: url/path/to/something#resource_id OR url/path/to/something:resource_id + htaccess_redirection: + instruction_1: "Accéder au fichier .htaccess:" + instruction_1_content: "Localisez le fichier .htaccess dans le répertoire racine de votre site Web, s'il n'existe pas, créez-en un. Ce fichier contrôle le comportement de votre serveur Web et est souvent utilisé pour la réécriture et la redirection d'URL" + instruction_2: "Copiez et collez les règles de redirection dans le fichier .htaccess:" + instruction_2_content: "Copiez les règles de redirection fournies dans le rectangle noir. Ouvrez le fichier .htaccess et collez les règles de redirection copiées dans le fichier" + instruction_3: "Activer le module de réécriture (Linux):" + instruction_3_content: "vous devrez vous assurer que le module Apache appelé 'rewrite' est activé en exécutant la commande `sudo a2enmod rewrite`" + + nginx_redirection: + instruction_1: "Accéder au fichier de configuration Nginx:" + instruction_1_content: "L'emplacement de ce fichier peut varier en fonction de votre système, mais les emplacements courants incluent /etc/nginx/nginx.conf, /etc/nginx/sites-available/default ou /usr/local/etc/nginx/nginx. conf." + instruction_2: "Localisez le bloc de serveur ou le bloc d'emplacement dans lequel vous souhaitez activer la réécriture d'URL:" + instruction_2_content: "C'est généralement dans le bloc serveur { ... }." + instruction_3: "Copier et coller les règles de redirection:" + instruction_3_content: "Copiez les règles de redirection fournies dans le rectangle noir. Dans le bloc approprié, ajoutez les règles copiées pour activer la réécriture d'URL" + instruction_4: "Testez la configuration pour les erreurs de syntaxe:" + instruction_4_content: "Exécutez la commande suivante pour tester la configuration de redirection `sudo nginx -t`" + instruction_5: "Redémarrez Nginx pour appliquer les modifications:" + instruction_5_content: "Redémarrez le serveur nginx pour appliquer la redirection à l'aide de cette commande `sudo systemctl reload nginx`" + + + components: check_resolvability: vérification de la résolvabilité... @@ -1354,12 +1446,17 @@ fr: join_the_count: Rejoignez les %{count} utilisateurs, surveillant cette ressource et soyez informé de toutes ses mises à jour see_more: Voir plus... see_less: Voir moins... + tree_view_empty: Pas de résultat + copy_original_uri: Copier l'URI d'origine + copy_portal_uri: Copier l'URI de %{portal_name} properties: id: ID + type: Type preferred_name: Nom préféré definitions: Définitions parent: Parent + no_properties_alert: "%{acronym} ne contient pas de propriétés" visits: ontology_visits: Visites de l'ontologie @@ -1383,3 +1480,27 @@ fr: cancel: Annuler apply: Appliquer unselect_all: Tout désélectionner + + tools: + search: + title: "Rechercher du contenu (à venir)" + description: > + Cet outil/service permet aux utilisateurs de rechercher n'importe quel élément du Cadre de Description des Ressources (RDF) dans le portail. + Les utilisateurs peuvent rechercher soit par l'identifiant unique (URI) associé à l'élément RDF, soit par son libellé. + Il facilite la navigation efficace et la récupération d'éléments RDF spécifiques, améliorant ainsi l'expérience utilisateur et la productivité au sein du portail. + + converter: + title: "Convertisseur de contenu (à venir)" + description: > + L'outil/service de conversion de contenu offre la possibilité de convertir n'importe quel élément RDF en divers formats tels que RDF/XML, Turtle, Ntriples ou JSON. + Cette fonctionnalité permet aux utilisateurs de transformer les données RDF en formats adaptés à différents usages ou compatibles avec divers systèmes et applications. + Il favorise l'interopérabilité et la flexibilité dans la manipulation des données RDF au sein de l'écosystème du portail. + + url_checker: + title: "Vérificateur de résolvabilité d'URI" + description: > + Cet outil/service vérifie la résolvabilité des Identifiants Uniformes de Ressource (URI) et leur négociabilité de contenu. + Il vérifie si un URI donné est accessible et si le contenu associé peut être négocié en fonction des préférences du client. + Cette fonctionnalité garantit la fiabilité et l'accessibilité des ressources liées dans l'écosystème RDF, aidant ainsi à maintenir l'intégrité des données et facilitant l'intégration transparente avec des ressources externes. + + diff --git a/config/routes.rb b/config/routes.rb index cf2c1cb3f..de09142b5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,7 @@ root to: 'home#index' mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? + get'/tools', to: 'home#tools' get 'auth/:provider/callback', to: 'login#create_omniauth' get 'locale/:language', to: 'language#set_locale_language' get 'metadata_export/index' @@ -17,6 +18,7 @@ post 'agents/:id/usages', to: 'agents#update_agent_usages', constraints: { id: /.+/ } resources :agents, constraints: { id: /.+/ } post 'agents/:id', to: 'agents#update', constraints: { id: /.+/ } + resources :ontolobridge do post :save_new_term_instructions, on: :collection end @@ -28,7 +30,6 @@ get '/users/subscribe/:username', to: 'users#subscribe' get '/users/un-subscribe/:email', to: 'users#un_subscribe' - get '/mappings/loader', to: 'mappings#loader' post '/mappings/loader', to: 'mappings#loader_process' get 'mappings/count/:id', to: 'mappings#count', constraints: { id: /.+/ } get 'mappings/show_mappings', to: 'mappings#show_mappings' @@ -41,20 +42,38 @@ resources :concepts - get 'ontologies/:ontology_id/concepts', to: 'concepts#show_concept' + + scope :ontologies do + get ':ontology/concepts' => 'concepts#index' + get ':ontology/concepts/show', to: 'concepts#show' + + + get ':ontology/instances', to: 'instances#index' + get ':ontology/instances/show', to: 'instances#show' + + get ':ontology/properties', to: 'properties#index' + get ':ontology/properties/show', to: 'properties#show' + + get ':ontology/schemes', to: 'schemes#index' + get ':ontology/schemes/show', to: 'schemes#show' + + get ':ontology/collections', to: 'collections#index' + get ':ontology/collections/show', to: 'collections#show' + end + + resources :ontologies do resources :submissions do get 'edit_properties' end - get 'instances/:instance_id', to: 'instances#show', constraints: { instance_id: /[^\/?]+/ } - get 'schemes/show_scheme', to: 'schemes#show' - get 'collections/show' get 'metrics' get 'metrics_evolution' get 'subscriptions' end + + resources :login resources :admin, only: [:index] @@ -64,6 +83,14 @@ match 'groups/synchronize_groups' => 'groups#synchronize_groups', via: [:post] resources :groups, only: [:index, :create, :new, :edit, :update, :destroy] resources :categories, only: [:index, :create, :new, :edit, :update, :destroy] + scope :search do + get '/', to: 'search#index' + post 'index_batch', to: 'search#index_batch' + post ':collection/init_schema', to: 'search#init_schema' + get ':collection/schema', to: 'search#show' + get ':collection/data', to: 'search#search' + end + end post 'admin/clearcache', to: 'admin#clearcache' @@ -73,6 +100,7 @@ get 'admin/ontologies_report', to: 'admin#ontologies_report' post 'admin/refresh_ontologies_report', to: 'admin#refresh_ontologies_report' delete 'admin/ontologies', to: 'admin#delete_ontologies' + delete 'admin/ontologies/:acronym/submissions/:id', to: 'admin#delete_submission' put 'admin/ontologies', to: 'admin#process_ontologies' get 'admin/update_check_enabled', to: 'admin#update_check_enabled' get 'admin/ontologies/:acronym/log', to: 'admin#parse_log' @@ -122,6 +150,9 @@ # Ontologies get '/ontologies/view/edit/:id' => 'ontologies#edit_view', :constraints => { id: /[^\/?]+/ } get '/ontologies/view/new/:id' => 'ontologies#new_view' + get '/ontologies/:acronym/download' => 'ontologies_redirection#redirect_ontology' + get '/ontologies/:acronym/:id/serialize/:output_format' => 'ontologies#content_serializer', :id => /.+/ + get '/ontologies/:acronym/htaccess' => 'ontologies_redirection#generate_htaccess' get '/ontologies/virtual/:ontology' => 'ontologies#virtual', :as => :ontology_virtual get '/ontologies/success/:id' => 'ontologies#submit_success' @@ -135,16 +166,18 @@ match '/ontologies/:acronym/submissions/:id/edit_metadata' => 'submissions#edit_metadata', via: [:get, :post] get '/ontologies_filter', to: 'ontologies#ontologies_filter' - get '/ontologies/:acronym/properties/show', to: 'properties#show' + get 'ontologies_selector', to: 'ontologies#ontologies_selector' get 'ontologies_selector/results', to: 'ontologies#ontologies_selector_results' # Notes get 'ontologies/:ontology/notes/:noteid', to: 'notes#virtual_show', as: :note_virtual, noteid: /.+/ get 'ontologies/:ontology/notes', to: 'notes#virtual_show' + get '/ontologies/:acronym/:id' => 'ontologies_redirection#redirect', :id => /.+/ + + # Ajax get '/ajax/' => 'ajax_proxy#get', :as => :ajax - get '/ajax_concepts/:ontology/' => 'concepts#show', :constraints => { id: /[^\/?]+/ } get '/ajax/class_details' => 'concepts#details' get '/ajax/mappings/get_concept_table' => 'mappings#get_concept_table' get '/ajax/json_ontology' => 'ajax_proxy#json_ontology' @@ -157,7 +190,6 @@ get '/ajax/classes/treeview' => 'concepts#show_tree' get '/ajax/classes/list' => 'collections#show_members' get '/ajax/classes/date_sorted_list' => 'concepts#show_date_sorted_list' - get '/ajax/properties/treeview' => 'properties#show_tree' get '/ajax/properties/children' => 'properties#show_children' get '/ajax/properties/tree' => 'concepts#property_tree' get 'ajax/schemes/label', to: "schemes#show_label" @@ -168,8 +200,6 @@ get '/ajax/fair_score/html' => 'fair_score#details_html' get '/ajax/submission/show_licenses/:id' => 'ontologies#show_licenses' get '/ajax/fair_score/json' => 'fair_score#details_json' - get '/ajax/:ontology/instances' => 'instances#index_by_ontology' - get '/ajax/:ontology/classes/:conceptid/instances' => 'instances#index_by_class', :constraints => { conceptid: /[^\/?]+/ } get '/ajax/ontologies', to: "ontologies#ajax_ontologies" get '/ajax/agents', to: "agents#ajax_agents" get '/ajax/images/show' => 'application#show_image_modal' @@ -184,6 +214,7 @@ # Search get 'search', to: 'search#index' + get 'ajax/search/ontologies/content', to: 'search#json_ontology_content_search' get 'check_resolvability' => 'check_resolvability#index' get 'check_url_resolvability' => 'check_resolvability#check_resolvability' @@ -214,6 +245,7 @@ get '/exhibit/:ontology/:id' => 'concepts#exhibit' + mount Lookbook::Engine, at: "/lookbook" end diff --git a/package.json b/package.json index 3e4673d33..392e650aa 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@hotwired/turbo-rails": "^7.1.1", "@triply/yasgui": "^4.2.28", "chart.js": "^4.4.1", + "d3": "^7.8.5", "datatables.net-dt": "^1.13.8", "debounce": "^1.2.1", "esbuild": "^0.14.41", diff --git a/test/controllers/ontologies_controller_test.rb b/test/controllers/ontologies_controller_test.rb index eeefd4b72..054b4eb90 100644 --- a/test/controllers/ontologies_controller_test.rb +++ b/test/controllers/ontologies_controller_test.rb @@ -26,7 +26,7 @@ class OntologiesControllerTest < ActionDispatch::IntegrationTest test "should open the tree views of #{ont.acronym} ontology" do paths = [ ajax_classes_treeview_path(ontology: ont.acronym), - ajax_properties_treeview_path(ontology: ont.acronym) + "/ontologies/#{ont.acronym}/properties" ] paths.each do |path| begin diff --git a/test/fixtures/recommender.yml b/test/fixtures/recommender.yml new file mode 100644 index 000000000..6c03bf8c2 --- /dev/null +++ b/test/fixtures/recommender.yml @@ -0,0 +1,698 @@ +yaml_structure: regular +sample_response: + - evaluationScore: 0.64 + ontologies: + - acronym: INRAETHES + "@id": https://data.stageportal.lirmm.fr/ontologies/INRAETHES + "@type": http://data.bioontology.org/metadata/Ontology + links: + submissions: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/submissions + properties: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/properties + classes: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes + single_class: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/{class_id} + roots: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/roots + schemes: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/schemes + collections: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/collections + xl_labels: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/skos_xl_labels + instances: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/instances + metrics: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/metrics + reviews: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/reviews + notes: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/notes + groups: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/groups + categories: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/categories + latest_submission: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/latest_submission + projects: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/projects + download: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/download + views: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/views + analytics: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/analytics + ui: http://stageportal.lirmm.fr/ontologies/INRAETHES + "@context": + submissions: http://data.bioontology.org/metadata/OntologySubmission + properties: http://data.bioontology.org/metadata/Property + classes: http://www.w3.org/2002/07/owl#Class + single_class: http://www.w3.org/2002/07/owl#Class + roots: http://www.w3.org/2002/07/owl#Class + schemes: http://www.w3.org/2004/02/skos/core#ConceptScheme + collections: http://www.w3.org/2004/02/skos/core#Collection + xl_labels: http://www.w3.org/2008/05/skos-xl#Label + instances: http://data.bioontology.org/metadata/Instance + metrics: http://data.bioontology.org/metadata/Metrics + reviews: http://data.bioontology.org/metadata/Review + notes: http://data.bioontology.org/metadata/Note + groups: http://data.bioontology.org/metadata/Group + categories: http://data.bioontology.org/metadata/Category + latest_submission: http://data.bioontology.org/metadata/OntologySubmission + projects: http://data.bioontology.org/metadata/Project + download: http://data.bioontology.org/metadata/Ontology + views: http://data.bioontology.org/metadata/Ontology + analytics: http://data.bioontology.org/metadata/Analytics + ui: http://data.bioontology.org/metadata/Ontology + "@context": + "@vocab": http://data.bioontology.org/metadata/ + acronym: http://omv.ontoware.org/2005/05/ontology#acronym + "@language": en + coverageResult: + score: 50 + normalizedScore: 0.909 + numberTermsCovered: 5 + numberWordsCovered: 5 + annotations: + - from: 1 + to: 8 + matchType: PREF + text: MELANOMA + annotatedClass: + "@id": http://opendata.inrae.fr/thesaurusINRAE/c_11970 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970 + ontology: https://data.stageportal.lirmm.fr/ontologies/INRAETHES + children: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970/children + parents: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970/instances + tree: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970/tree + notes: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970/mappings + ui: http://stageportal.lirmm.fr/ontologies/INRAETHES?p=classes&conceptid=http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11970 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + - from: 25 + to: 29 + matchType: PREF + text: TUMOR + annotatedClass: + "@id": http://opendata.inrae.fr/thesaurusINRAE/c_11887 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887 + ontology: https://data.stageportal.lirmm.fr/ontologies/INRAETHES + children: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887/children + parents: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887/instances + tree: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887/tree + notes: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887/mappings + ui: http://stageportal.lirmm.fr/ontologies/INRAETHES?p=classes&conceptid=http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_11887 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + - from: 66 + to: 69 + matchType: PREF + text: SKIN + annotatedClass: + "@id": http://opendata.inrae.fr/thesaurusINRAE/c_8606 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606 + ontology: https://data.stageportal.lirmm.fr/ontologies/INRAETHES + children: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606/children + parents: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606/instances + tree: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606/tree + notes: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606/mappings + ui: http://stageportal.lirmm.fr/ontologies/INRAETHES?p=classes&conceptid=http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8606 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + - from: 87 + to: 95 + matchType: PREF + text: INTESTINE + annotatedClass: + "@id": http://opendata.inrae.fr/thesaurusINRAE/c_8817 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817 + ontology: https://data.stageportal.lirmm.fr/ontologies/INRAETHES + children: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817/children + parents: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817/instances + tree: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817/tree + notes: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817/mappings + ui: http://stageportal.lirmm.fr/ontologies/INRAETHES?p=classes&conceptid=http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_8817 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + - from: 105 + to: 107 + matchType: PREF + text: EYE + annotatedClass: + "@id": http://opendata.inrae.fr/thesaurusINRAE/c_9169 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169 + ontology: https://data.stageportal.lirmm.fr/ontologies/INRAETHES + children: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169/children + parents: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169/instances + tree: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169/tree + notes: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/INRAETHES/classes/http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169/mappings + ui: http://stageportal.lirmm.fr/ontologies/INRAETHES?p=classes&conceptid=http%3A%2F%2Fopendata.inrae.fr%2FthesaurusINRAE%2Fc_9169 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + specializationResult: + score: 11.832 + normalizedScore: 0.932 + acceptanceResult: + normalizedScore: 0 + bioportalScore: 0 + umlsScore: 0 + detailResult: + normalizedScore: 0 + definitionsScore: 0 + synonymsScore: 0 + propertiesScore: 0 + - evaluationScore: 0.4 + ontologies: + - acronym: PATO + "@id": https://data.stageportal.lirmm.fr/ontologies/PATO + "@type": http://data.bioontology.org/metadata/Ontology + links: + submissions: https://data.stageportal.lirmm.fr/ontologies/PATO/submissions + properties: https://data.stageportal.lirmm.fr/ontologies/PATO/properties + classes: https://data.stageportal.lirmm.fr/ontologies/PATO/classes + single_class: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/{class_id} + roots: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/roots + schemes: https://data.stageportal.lirmm.fr/ontologies/PATO/schemes + collections: https://data.stageportal.lirmm.fr/ontologies/PATO/collections + xl_labels: https://data.stageportal.lirmm.fr/ontologies/PATO/skos_xl_labels + instances: https://data.stageportal.lirmm.fr/ontologies/PATO/instances + metrics: https://data.stageportal.lirmm.fr/ontologies/PATO/metrics + reviews: https://data.stageportal.lirmm.fr/ontologies/PATO/reviews + notes: https://data.stageportal.lirmm.fr/ontologies/PATO/notes + groups: https://data.stageportal.lirmm.fr/ontologies/PATO/groups + categories: https://data.stageportal.lirmm.fr/ontologies/PATO/categories + latest_submission: https://data.stageportal.lirmm.fr/ontologies/PATO/latest_submission + projects: https://data.stageportal.lirmm.fr/ontologies/PATO/projects + download: https://data.stageportal.lirmm.fr/ontologies/PATO/download + views: https://data.stageportal.lirmm.fr/ontologies/PATO/views + analytics: https://data.stageportal.lirmm.fr/ontologies/PATO/analytics + ui: http://stageportal.lirmm.fr/ontologies/PATO + "@context": + submissions: http://data.bioontology.org/metadata/OntologySubmission + properties: http://data.bioontology.org/metadata/Property + classes: http://www.w3.org/2002/07/owl#Class + single_class: http://www.w3.org/2002/07/owl#Class + roots: http://www.w3.org/2002/07/owl#Class + schemes: http://www.w3.org/2004/02/skos/core#ConceptScheme + collections: http://www.w3.org/2004/02/skos/core#Collection + xl_labels: http://www.w3.org/2008/05/skos-xl#Label + instances: http://data.bioontology.org/metadata/Instance + metrics: http://data.bioontology.org/metadata/Metrics + reviews: http://data.bioontology.org/metadata/Review + notes: http://data.bioontology.org/metadata/Note + groups: http://data.bioontology.org/metadata/Group + categories: http://data.bioontology.org/metadata/Category + latest_submission: http://data.bioontology.org/metadata/OntologySubmission + projects: http://data.bioontology.org/metadata/Project + download: http://data.bioontology.org/metadata/Ontology + views: http://data.bioontology.org/metadata/Ontology + analytics: http://data.bioontology.org/metadata/Analytics + ui: http://data.bioontology.org/metadata/Ontology + "@context": + "@vocab": http://data.bioontology.org/metadata/ + acronym: http://omv.ontoware.org/2005/05/ontology#acronym + "@language": en + coverageResult: + score: 25 + normalizedScore: 0.455 + numberTermsCovered: 3 + numberWordsCovered: 3 + annotations: + - from: 66 + to: 69 + matchType: SYN + text: SKIN + annotatedClass: + "@id": http://purl.obolibrary.org/obo/UBERON_0002097 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097 + ontology: https://data.stageportal.lirmm.fr/ontologies/PATO + children: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097/children + parents: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097/instances + tree: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097/tree + notes: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097/mappings + ui: http://stageportal.lirmm.fr/ontologies/PATO?p=classes&conceptid=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0002097 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 8 + - from: 87 + to: 95 + matchType: PREF + text: INTESTINE + annotatedClass: + "@id": http://purl.obolibrary.org/obo/UBERON_0000160 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160 + ontology: https://data.stageportal.lirmm.fr/ontologies/PATO + children: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160/children + parents: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160/instances + tree: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160/tree + notes: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160/mappings + ui: http://stageportal.lirmm.fr/ontologies/PATO?p=classes&conceptid=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000160 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + - from: 105 + to: 107 + matchType: PREF + text: EYE + annotatedClass: + "@id": http://purl.obolibrary.org/obo/UBERON_0000970 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970 + ontology: https://data.stageportal.lirmm.fr/ontologies/PATO + children: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970/children + parents: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970/instances + tree: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970/tree + notes: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/PATO/classes/http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970/mappings + ui: http://stageportal.lirmm.fr/ontologies/PATO?p=classes&conceptid=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FUBERON_0000970 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 5 + specializationResult: + score: 12.689 + normalizedScore: 1 + acceptanceResult: + normalizedScore: 0 + bioportalScore: 0 + umlsScore: 0 + detailResult: + normalizedScore: 0 + definitionsScore: 0 + synonymsScore: 0 + propertiesScore: 0 + - evaluationScore: 0.269 + ontologies: + - acronym: EUROSCIVOC + "@id": https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC + "@type": http://data.bioontology.org/metadata/Ontology + links: + submissions: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/submissions + properties: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/properties + classes: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes + single_class: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/{class_id} + roots: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/roots + schemes: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/schemes + collections: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/collections + xl_labels: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/skos_xl_labels + instances: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/instances + metrics: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/metrics + reviews: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/reviews + notes: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/notes + groups: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/groups + categories: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/categories + latest_submission: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/latest_submission + projects: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/projects + download: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/download + views: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/views + analytics: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/analytics + ui: http://stageportal.lirmm.fr/ontologies/EUROSCIVOC + "@context": + submissions: http://data.bioontology.org/metadata/OntologySubmission + properties: http://data.bioontology.org/metadata/Property + classes: http://www.w3.org/2002/07/owl#Class + single_class: http://www.w3.org/2002/07/owl#Class + roots: http://www.w3.org/2002/07/owl#Class + schemes: http://www.w3.org/2004/02/skos/core#ConceptScheme + collections: http://www.w3.org/2004/02/skos/core#Collection + xl_labels: http://www.w3.org/2008/05/skos-xl#Label + instances: http://data.bioontology.org/metadata/Instance + metrics: http://data.bioontology.org/metadata/Metrics + reviews: http://data.bioontology.org/metadata/Review + notes: http://data.bioontology.org/metadata/Note + groups: http://data.bioontology.org/metadata/Group + categories: http://data.bioontology.org/metadata/Category + latest_submission: http://data.bioontology.org/metadata/OntologySubmission + projects: http://data.bioontology.org/metadata/Project + download: http://data.bioontology.org/metadata/Ontology + views: http://data.bioontology.org/metadata/Ontology + analytics: http://data.bioontology.org/metadata/Analytics + ui: http://data.bioontology.org/metadata/Ontology + "@context": + "@vocab": http://data.bioontology.org/metadata/ + acronym: http://omv.ontoware.org/2005/05/ontology#acronym + "@language": en + coverageResult: + score: 15 + normalizedScore: 0.273 + numberTermsCovered: 2 + numberWordsCovered: 2 + annotations: + - from: 1 + to: 8 + matchType: PREF + text: MELANOMA + annotatedClass: + "@id": http://data.europa.eu/8mn/euroscivoc/276b8c99-a318-48df-aa31-1f9f3e0ba910 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910 + ontology: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC + children: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/children + parents: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/instances + tree: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/tree + notes: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/mappings + ui: http://stageportal.lirmm.fr/ontologies/EUROSCIVOC?p=classes&conceptid=http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + - from: 34 + to: 44 + matchType: SYN + text: MELANOCYTES + annotatedClass: + "@id": http://data.europa.eu/8mn/euroscivoc/276b8c99-a318-48df-aa31-1f9f3e0ba910 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910 + ontology: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC + children: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/children + parents: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/instances + tree: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/tree + notes: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/EUROSCIVOC/classes/http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910/mappings + ui: http://stageportal.lirmm.fr/ontologies/EUROSCIVOC?p=classes&conceptid=http%3A%2F%2Fdata.europa.eu%2F8mn%2Feuroscivoc%2F276b8c99-a318-48df-aa31-1f9f3e0ba910 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + specializationResult: + score: 3.732 + normalizedScore: 0.294 + acceptanceResult: + normalizedScore: 0.5 + bioportalScore: 0 + umlsScore: 1 + detailResult: + normalizedScore: 0 + definitionsScore: 0 + synonymsScore: 0 + propertiesScore: 0 + - evaluationScore: 0.239 + ontologies: + - acronym: AGROVOC + "@id": https://data.stageportal.lirmm.fr/ontologies/AGROVOC + "@type": http://data.bioontology.org/metadata/Ontology + links: + submissions: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/submissions + properties: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/properties + classes: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes + single_class: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/{class_id} + roots: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/roots + schemes: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/schemes + collections: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/collections + xl_labels: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/skos_xl_labels + instances: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/instances + metrics: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/metrics + reviews: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/reviews + notes: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/notes + groups: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/groups + categories: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/categories + latest_submission: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/latest_submission + projects: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/projects + download: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/download + views: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/views + analytics: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/analytics + ui: http://stageportal.lirmm.fr/ontologies/AGROVOC + "@context": + submissions: http://data.bioontology.org/metadata/OntologySubmission + properties: http://data.bioontology.org/metadata/Property + classes: http://www.w3.org/2002/07/owl#Class + single_class: http://www.w3.org/2002/07/owl#Class + roots: http://www.w3.org/2002/07/owl#Class + schemes: http://www.w3.org/2004/02/skos/core#ConceptScheme + collections: http://www.w3.org/2004/02/skos/core#Collection + xl_labels: http://www.w3.org/2008/05/skos-xl#Label + instances: http://data.bioontology.org/metadata/Instance + metrics: http://data.bioontology.org/metadata/Metrics + reviews: http://data.bioontology.org/metadata/Review + notes: http://data.bioontology.org/metadata/Note + groups: http://data.bioontology.org/metadata/Group + categories: http://data.bioontology.org/metadata/Category + latest_submission: http://data.bioontology.org/metadata/OntologySubmission + projects: http://data.bioontology.org/metadata/Project + download: http://data.bioontology.org/metadata/Ontology + views: http://data.bioontology.org/metadata/Ontology + analytics: http://data.bioontology.org/metadata/Analytics + ui: http://data.bioontology.org/metadata/Ontology + "@context": + "@vocab": http://data.bioontology.org/metadata/ + acronym: http://omv.ontoware.org/2005/05/ontology#acronym + "@language": en + coverageResult: + score: 20 + normalizedScore: 0.364 + numberTermsCovered: 2 + numberWordsCovered: 2 + annotations: + - from: 1 + to: 8 + matchType: PREF + text: MELANOMA + annotatedClass: + "@id": http://aims.fao.org/aos/agrovoc/c_4713 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713 + ontology: https://data.stageportal.lirmm.fr/ontologies/AGROVOC + children: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713/children + parents: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713/instances + tree: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713/tree + notes: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713/mappings + ui: http://stageportal.lirmm.fr/ontologies/AGROVOC?p=classes&conceptid=http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_4713 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + - from: 66 + to: 69 + matchType: PREF + text: SKIN + annotatedClass: + "@id": http://aims.fao.org/aos/agrovoc/c_7097 + "@type": http://www.w3.org/2002/07/owl#Class + links: + self: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097 + ontology: https://data.stageportal.lirmm.fr/ontologies/AGROVOC + children: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097/children + parents: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097/parents + descendants: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097/descendants + ancestors: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097/ancestors + instances: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097/instances + tree: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097/tree + notes: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097/notes + mappings: https://data.stageportal.lirmm.fr/ontologies/AGROVOC/classes/http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097/mappings + ui: http://stageportal.lirmm.fr/ontologies/AGROVOC?p=classes&conceptid=http%3A%2F%2Faims.fao.org%2Faos%2Fagrovoc%2Fc_7097 + "@context": + self: http://www.w3.org/2002/07/owl#Class + ontology: http://data.bioontology.org/metadata/Ontology + children: http://www.w3.org/2002/07/owl#Class + parents: http://www.w3.org/2002/07/owl#Class + descendants: http://www.w3.org/2002/07/owl#Class + ancestors: http://www.w3.org/2002/07/owl#Class + instances: http://data.bioontology.org/metadata/Instance + tree: http://www.w3.org/2002/07/owl#Class + notes: http://data.bioontology.org/metadata/Note + mappings: http://data.bioontology.org/metadata/Mapping + ui: http://www.w3.org/2002/07/owl#Class + "@context": + "@vocab": http://data.bioontology.org/metadata/ + "@language": en + hierarchySize: 0 + specializationResult: + score: 3.315 + normalizedScore: 0.261 + acceptanceResult: + normalizedScore: 0 + bioportalScore: 0 + umlsScore: 0 + detailResult: + normalizedScore: 0 + definitionsScore: 0 + synonymsScore: 0 + propertiesScore: 0 diff --git a/test/integration/login_flows_test.rb b/test/integration/login_flows_test.rb index 6772e31ca..d446a6d6a 100644 --- a/test/integration/login_flows_test.rb +++ b/test/integration/login_flows_test.rb @@ -40,7 +40,8 @@ class LoginFlowsTest < ActionDispatch::IntegrationTest githubId: new_user.githubId, email: new_user.email, password: new_user.password, - password_confirmation: new_user.password + password_confirmation: new_user.password, + terms_and_conditions: true } } diff --git a/test/system/agent_flows_test.rb b/test/system/agent_flows_test.rb index aafa9c701..c4272b71f 100644 --- a/test/system/agent_flows_test.rb +++ b/test/system/agent_flows_test.rb @@ -4,6 +4,7 @@ class AgentFlowsTest < ApplicationSystemTestCase include AgentHelper setup do + WebMock.disable! teardown @logged_user = fixtures(:users)[:john] @new_person = fixtures(:agents)[:agent1] diff --git a/test/system/login_flows_test.rb b/test/system/login_flows_test.rb index 0c4a152f1..5ed014b2c 100644 --- a/test/system/login_flows_test.rb +++ b/test/system/login_flows_test.rb @@ -3,6 +3,7 @@ class LoginFlowsTest < ApplicationSystemTestCase setup do + WebMock.disable! @user_john = fixtures(:users)[:john] @user_bob = create_user(fixtures(:users)[:bob]) end @@ -31,6 +32,7 @@ class LoginFlowsTest < ApplicationSystemTestCase fill_in 'user_email', with: new_user.email fill_in 'user_password', with: new_user.password fill_in 'user_password_confirmation', with: new_user.password + find("input[name='user[terms_and_conditions]']").set(true) # Click the save button click_button 'Register' diff --git a/test/system/recommender_page_test.rb b/test/system/recommender_page_test.rb new file mode 100644 index 000000000..1c0f98043 --- /dev/null +++ b/test/system/recommender_page_test.rb @@ -0,0 +1,118 @@ +require "application_system_test_case" +require 'webmock/minitest' + +class RecommenderPageTest < ApplicationSystemTestCase + def setup + WebMock.disable! + @apikey = LinkedData::Client.settings.apikey + @host = LinkedData::Client.settings.rest_url + @recommender_api = "#{@host}/recommender" + @ontologies_url = "#{@host}/ontologies?include=acronym,name" + + ## Remove http:// from the host url + uri = URI.parse(@host) + @host = "#{uri.host}:#{uri.port}" + + @recommender_submit_button = "div.recommender-page-button" + @recommender_text_area = "textarea#recommender-text-area" + @sample_response = fixtures(:recommender)["sample_response"] + end + + + test "go to recommender page and check if all filters and inputs are there" do + visit root_url + click_link(href: '/recommender') + assert_selector @recommender_text_area + assert_selector "div.text-choice" + assert_selector "div.keywords-choice" + assert_selector "div.ontologies-choice" + assert_selector "div.ontology-sets-choice" + + find("div.advanced-options-button").click + + assert_selector "div.weights-configuration" + assert_selector ".ontologies.input" + + find("div.ontology-sets-choice").click + + assert_selector ".maxsets.input" + assert_selector "div.insert-sample-text-button" + assert_selector @recommender_submit_button + end + + test "go to recommender page insert sample text and get recommendations" do + visit root_url + click_link(href: '/recommender') + find(@recommender_text_area).fill_in(with: 'Melanoma is a malignant tumor of melanocytes found mainly in the skin but also in the intestine and the eye.') + # Mock the response of the API calling /recommender with sample text + WebMock.enable! + WebMock.stub_request(:post, @recommender_api) + .with( + body: "{\"input\":\"Melanoma is a malignant tumor of melanocytes found mainly in the skin but also in the intestine and the eye.\",\"input_type\":\"1\",\"output_type\":\"1\",\"wc\":\"0.55\",\"wa\":\"0.15\",\"wd\":\"0.15\",\"ws\":\"0.15\",\"max_elements_set\":\"3\",\"controller\":\"recommender\",\"action\":\"index\"}", + headers: { + 'Accept'=>'application/json', + 'Authorization'=>"apikey token=#{@apikey}", + 'Content-Type'=>'application/json', + 'Host'=>@host, + 'User-Agent'=>'NCBO API Ruby Client v0.1.0' + }) + .to_return(status: 200, body: @sample_response.to_json, headers: {}) + + # Mock the response of the API calling /ontologies + WebMock.stub_request(:get, @ontologies_url) + .with( + headers: { + 'Accept'=>'application/json', + 'Authorization'=>"apikey token=#{@apikey}", + 'Host'=>@host, + 'User-Agent'=>'NCBO API Ruby Client v0.1.0' + }) + .to_return(status: 200, body: ([]).to_json, headers: {}) + + + find(@recommender_submit_button).click + + # Recommendations table exists + assert_selector "div#recommender-table_wrapper" + + # The number of results is 4 + assert_equal 4, page.all('.recommender-result-ontology').count + + # The number of highlighted annotations are 5 + assert_selector ".recommender-page-text-area-results a", count: 5 + + # Json button exists + assert_selector "div.json-button" + + # Cite us button exists + assert_selector "div.cite-us-button" + + # Go to annotator button exists + assert_selector "div.go-to-annotator" + + find("div#recommender-edit-button").click + + + find(@recommender_text_area).native.clear + find(@recommender_text_area).fill_in(with: 'input to show no result') + + # Mock the response of the API calling /recommender using for an input that returns no results + WebMock.stub_request(:post, "http://localhost:9393/recommender") + .with( + body: "{\"input\":\"input to show no result\",\"input_type\":\"1\",\"output_type\":\"1\",\"wc\":\"0.55\",\"wa\":\"0.15\",\"wd\":\"0.15\",\"ws\":\"0.15\",\"max_elements_set\":\"3\",\"controller\":\"recommender\",\"action\":\"index\"}", + headers: { + 'Accept'=>'application/json', + 'Authorization'=>"apikey token=#{@apikey}", + 'Content-Type'=>'application/json', + 'Host'=>@host, + 'User-Agent'=>'NCBO API Ruby Client v0.1.0' + }) + .to_return(status: 200, body: ([]).to_json, headers: {}) + + find(@recommender_submit_button).click + + assert_selector "div.browse-empty-illustration" + + + end +end diff --git a/test/system/submission_flows_test.rb b/test/system/submission_flows_test.rb index 55b26ee68..4a11bd1b8 100644 --- a/test/system/submission_flows_test.rb +++ b/test/system/submission_flows_test.rb @@ -3,6 +3,7 @@ class SubmissionFlowsTest < ApplicationSystemTestCase setup do + WebMock.disable! @logged_user = fixtures(:users)[:john] @user_bob = fixtures(:users)[:bob] @new_ontology = fixtures(:ontologies)[:ontology1] diff --git a/test/test_helper.rb b/test/test_helper.rb index 1083b2287..e8f7cf84a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -37,7 +37,11 @@ def load_all_fixtures fixture_files.each do |fixture_file| fixture_name = File.basename(fixture_file, '.yml') data = YAML.load_file(fixture_file) - fixtures_data[fixture_name] = OpenStruct.new(Array(data).map{|key, hash| [key , OpenStruct.new(hash)]}.to_h) + if data["yaml_structure"].eql?("regular") + fixtures_data[fixture_name] = data + else + fixtures_data[fixture_name] = OpenStruct.new(Array(data).map{|key, hash| [key , OpenStruct.new(hash)]}.to_h) + end end fixtures_data diff --git a/yarn.lock b/yarn.lock index 5cb3f6d1c..131990647 100644 --- a/yarn.lock +++ b/yarn.lock @@ -265,6 +265,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@7: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^6.1.0: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" @@ -280,6 +285,250 @@ cookiejar@^2.1.2: resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + +"d3-color@1 - 3", d3-color@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" + integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.4" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" + integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + +"d3-format@1 - 3", d3-format@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +d3-geo@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.0.tgz#74fd54e1f4cebd5185ac2039217a98d39b0a4c0e" + integrity sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-scale-chromatic@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" + integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@^7.8.5: + version "7.8.5" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.5.tgz#fde4b760d4486cdb6f0cc8e2cbff318af844635c" + integrity sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + data-uri-to-buffer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" @@ -338,6 +587,13 @@ define-data-property@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +delaunator@5: + version "5.0.1" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" + integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== + dependencies: + robust-predicates "^3.0.2" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -590,6 +846,13 @@ highlight.js@^11.9.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== +iconv-lite@0.6: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -600,6 +863,11 @@ inherits@^2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + jquery@>=1.7, jquery@^3.5.0: version "3.7.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" @@ -794,11 +1062,26 @@ remove-accents@^0.4.2: resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.4.tgz#73704abf7dae3764295d475d2b6afac4ea23e4d9" integrity sha512-EpFcOa/ISetVHEXqu+VwI96KZBmq+a8LJnGkaeFw45epGlxIZz5dhEEnNZMsQXgORu3qaMoLX4qJCzOik6ytAg== +robust-predicates@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" + integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== + +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + semver@^7.3.2: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" From 0dfe3da3980e2f0e6a3b7fd9348aadbbe765c073 Mon Sep 17 00:00:00 2001 From: OntoPortal Bot Date: Fri, 24 May 2024 09:44:17 +0200 Subject: [PATCH 02/10] [ontoportal-bot] Gemfile.lock update --- Gemfile.lock | 66 +++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ea693d64b..223c6a7d4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 674193220f93d99587e6d3a969adcb384caf0a61 + revision: fff54539bbdcf98ff4cd01d188440cda66fa3a01 branch: development specs: ontologies_api_client (2.2.0) @@ -85,14 +85,14 @@ GEM addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) - airbrussh (1.5.1) + airbrussh (1.5.2) sshkit (>= 1.6.1, != 1.7.0) ast (2.4.2) autoprefixer-rails (10.4.16.0) execjs (~> 2) base64 (0.2.0) - bcrypt_pbkdf (1.1.0) - bigdecimal (3.1.6) + bcrypt_pbkdf (1.1.1) + bigdecimal (3.1.8) bindata (2.5.0) bindex (0.8.1) bootsnap (1.18.3) @@ -102,7 +102,7 @@ GEM popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) brakeman (5.4.1) - bugsnag (6.26.4) + bugsnag (6.27.0) concurrent-ruby (~> 1.0) builder (3.2.4) capistrano (3.18.1) @@ -139,7 +139,7 @@ GEM bigdecimal rexml crass (1.0.6) - css_parser (1.16.0) + css_parser (1.17.1) addressable cube-ruby (0.0.3) daemons (1.4.1) @@ -175,9 +175,9 @@ GEM flamegraph (0.9.5) globalid (1.2.1) activesupport (>= 6.1) - graphql (2.3.0) + graphql (2.3.4) base64 - graphql-client (0.21.0) + graphql-client (0.22.0) activesupport (>= 3.0) graphql (>= 1.13.0) haml (5.2.2) @@ -201,7 +201,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) - i18n (1.14.4) + i18n (1.14.5) concurrent-ruby (~> 1.0) i18n-tasks (0.9.37) activesupport (>= 4.0.2) @@ -224,8 +224,8 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.7.2) - irb (1.12.0) - rdoc + irb (1.13.1) + rdoc (>= 4.0.0) reline (>= 0.4.2) iso-639 (0.3.6) jquery-rails (4.6.0) @@ -247,7 +247,7 @@ GEM jwt (2.8.1) base64 language_server-protocol (3.17.0.3) - launchy (3.0.0) + launchy (3.0.1) addressable (~> 2.8) childprocess (~> 5.0) letter_opener (1.10.0) @@ -284,16 +284,16 @@ GEM net-smtp marcel (1.0.4) matrix (0.4.2) - method_source (1.0.0) + method_source (1.1.0) mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2024.0305) + mime-types-data (3.2024.0507) mini_mime (1.1.5) - minitest (5.22.3) + minitest (5.23.1) msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) - multipart-post (2.4.0) + multipart-post (2.4.1) mutex_m (0.2.0) mysql2 (0.5.6) net-ftp (0.2.1) @@ -301,7 +301,7 @@ GEM time net-http (0.3.2) uri - net-imap (0.4.10) + net-imap (0.4.11) date net-protocol net-pop (0.1.2) @@ -316,8 +316,8 @@ GEM net-protocol net-ssh (7.2.3) netrc (0.11.0) - newrelic_rpm (9.8.0) - nio4r (2.7.1) + newrelic_rpm (9.9.0) + nio4r (2.7.3) nokogiri (1.15.6-x86_64-linux) racc (~> 1.4) oauth2 (2.0.9) @@ -352,12 +352,12 @@ GEM omniauth-orcid (2.1.1) omniauth-oauth2 (~> 1.3) ruby_dig (~> 0.0.2) - omniauth-rails_csrf_protection (1.0.1) + omniauth-rails_csrf_protection (1.0.2) actionpack (>= 4.2) omniauth (~> 2.0) open_uri_redirections (0.2.1) parallel (1.24.0) - parser (3.3.0.5) + parser (3.3.1.0) ast (~> 2.4.1) racc popper_js (1.16.1) @@ -368,7 +368,7 @@ GEM public_suffix (5.0.5) puma (5.6.8) nio4r (~> 2.0) - racc (1.7.3) + racc (1.8.0) rack (2.2.9) rack-accept (0.4.5) rack (>= 0.4) @@ -417,28 +417,29 @@ GEM rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) - rb-inotify (0.10.1) + rb-inotify (0.11.1) ffi (~> 1.0) rdoc (6.3.4.1) recaptcha (5.9.0) json redcarpet (3.6.0) - regexp_parser (2.9.0) - reline (0.5.0) + regexp_parser (2.9.2) + reline (0.5.7) io-console (~> 0.5) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.2.6) + rexml (3.2.8) + strscan (>= 3.0.9) rouge (4.2.1) rspec-core (3.13.0) rspec-support (~> 3.13.0) rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-rails (6.1.2) @@ -450,7 +451,7 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.1) - rubocop (1.62.1) + rubocop (1.64.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -461,8 +462,8 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.2) - parser (>= 3.3.0.4) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) ruby_dig (0.0.2) @@ -505,7 +506,7 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sshkit (1.22.1) + sshkit (1.22.2) base64 mutex_m net-scp (>= 1.1.2) @@ -514,6 +515,7 @@ GEM stackprof (0.2.26) stimulus-rails (1.3.3) railties (>= 6.0.0) + strscan (3.1.0) temple (0.10.3) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -563,7 +565,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yard (0.9.36) - zeitwerk (2.6.13) + zeitwerk (2.6.14) PLATFORMS x86_64-linux From ef3c6c14eac81cb1699431fac6822eb233e98f67 Mon Sep 17 00:00:00 2001 From: OntoPortal Bot Date: Sat, 25 May 2024 08:34:31 +0200 Subject: [PATCH 03/10] [ontoportal-bot] Gemfile.lock update --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 223c6a7d4..5546308ad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: fff54539bbdcf98ff4cd01d188440cda66fa3a01 + revision: f91c32d532e6109b7eaccd5fd2ef51fef2192d71 branch: development specs: ontologies_api_client (2.2.0) @@ -553,7 +553,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.23.0) + webmock (3.23.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) From 4a7fb7a5de1d777f497f53df02e1de4c75572aaa Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Sat, 25 May 2024 08:54:10 +0200 Subject: [PATCH 04/10] Merge to master: Release 2.8.0.1 fix not found issues, OFAIRE cache and Annotator issues(#632) * [ontoportal-bot] Gemfile.lock update * implement caching for the ofaire service * fix the browse page popularity sort --------- Co-authored-by: OntoPortal Bot --- app/controllers/concerns/submission_filter.rb | 2 +- app/helpers/fair_score_helper.rb | 61 +++++++++++++++++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index c8677e601..44d4f6e52 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -236,7 +236,7 @@ def ontology_hash(ont, submissions) o[:individual_count_formatted] = number_with_delimiter(o[:individual_count], delimiter: ',') o[:note_count] = ont.notes&.length || 0 - o[:project_count] = ont.projects&.length || + o[:project_count] = ont.projects&.length || 0 o[:popularity] = @analytics[ont.acronym] || 0 o[:rank] = sub&[:rank] || 0 diff --git a/app/helpers/fair_score_helper.rb b/app/helpers/fair_score_helper.rb index f3fc4f39c..7abb76eb6 100644 --- a/app/helpers/fair_score_helper.rb +++ b/app/helpers/fair_score_helper.rb @@ -13,23 +13,26 @@ def get_fairness_service_url(apikey = user_apikey) end def get_fairness_json(ontologies_acronyms, apikey = user_apikey) - Rails.cache.fetch("fairness-#{ontologies_acronyms.gsub(',', '-')}-#{apikey}", expires: 24.hours) do + if Rails.cache.exist?("fairness-#{ontologies_acronyms.gsub(',', '-')}-#{apikey}") + out = read_large_data("fairness-#{ontologies_acronyms.gsub(',', '-')}-#{apikey}") + else + out = "{}" begin - out = {} time = Benchmark.realtime do conn = Faraday.new do |conn| conn.options.timeout = 30 end response = conn.get(get_fairness_service_url(apikey) + "&ontologies=#{ontologies_acronyms}&combined") - out = MultiJson.load(response.body.force_encoding('ISO-8859-1').encode('UTF-8')) + out = response.body.force_encoding('ISO-8859-1').encode('UTF-8') + cache_large_data("fairness-#{ontologies_acronyms.gsub(',', '-')}-#{apikey}", out) end puts "Call fairness service for: #{ontologies_acronyms} (#{time}s)" rescue Rails.logger.warn t('fair_score.fairness_unreachable_warning') end - - out end + MultiJson.use :oj + MultiJson.load(out) rescue {} end def get_fair_score(ontologies_acronyms, apikey = user_apikey) @@ -139,5 +142,53 @@ def fairness_link(style: '', ontology: nil) ontology = ontology || 'all' render IconWithTooltipComponent.new(icon: "json.svg",link: "#{get_fairness_service_url}&ontologies=#{ontology}&combined=true", target: '_blank', title: t('fair_score.go_to_api'), size:'small', style: custom_style) end + + private + require 'zlib' + + def cache_large_data(key, data, chunk_size = 1.megabyte) + compressed_data = Zlib::Deflate.deflate(data) + total_size = compressed_data.bytesize + Rails.logger.info "Total compressed data size: #{total_size} bytes" + + # Determine the number of chunks + chunk_count = (total_size.to_f / chunk_size).ceil + + chunk_count.times do |index| + chunk_key = "#{key}_chunk_#{index}" + start_byte = index * chunk_size + end_byte = start_byte + chunk_size - 1 + chunk = compressed_data.byteslice(start_byte..end_byte) + + unless Rails.cache.write(chunk_key, chunk, expires_in: 24.hours) + Rails.logger.error "Failed to write chunk #{index} for key: #{key}" + return false + end + end + + # Store metadata about the chunks + metadata = { chunk_count: chunk_count } + Rails.cache.write("#{key}_metadata", metadata, expires_in: 24.hours) + Rails.cache.write(key, true, expires_in: 24.hours) + end + + def read_large_data(key) + metadata = Rails.cache.read("#{key}_metadata") + return nil unless metadata + + chunk_count = metadata[:chunk_count] + data = '' + + chunk_count.times do |index| + chunk_key = "#{key}_chunk_#{index}" + chunk = Rails.cache.read(chunk_key) + return nil unless chunk + data << chunk + end + + # Decompress data + Zlib::Inflate.inflate(data) + end + end From af3839fd78a4596723a145233e6040d1b8bc425a Mon Sep 17 00:00:00 2001 From: Bilel Kihal <61744974+Bilelkihal@users.noreply.github.com> Date: Wed, 29 May 2024 09:31:01 +0200 Subject: [PATCH 05/10] Fix: feedback email not filling the sender information (name, email, ...) (#634) * fix feedback email sender missing infos * add internationalization to feedback email --- app/views/notifier/feedback.html.haml | 10 +++++----- config/locales/en.yml | 8 +++++++- config/locales/fr.yml | 7 ++++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/views/notifier/feedback.html.haml b/app/views/notifier/feedback.html.haml index 5ce9196e3..306d92ee4 100644 --- a/app/views/notifier/feedback.html.haml +++ b/app/views/notifier/feedback.html.haml @@ -5,17 +5,17 @@ %meta{content: "no-cache", name: "turbo-cache-control"}/ %body %p - = t("ontologies.notifier.name", name: @name) + = t("feedback_mail.name", name: @name) %p - = t("ontologies.notifier.email", email: @email) + = t("feedback_mail.email", email: @email) %p - = t("ontologies.notifier.location", location: @location) - %p= t("ontologies.notifier.tags") + = t("feedback_mail.location", location: @location) + %p= t("feedback_mail.tags") %ul - for tag in @tags %li = tag %p %br/ - %strong= t("ontologies.notifier.feedback") + %strong= t("feedback_mail.feedback") = simple_format(@comment) \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 2916588c7..e406c3c3f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1464,4 +1464,10 @@ en: description: > This tool/service verifies the resolvability of Uniform Resource Identifiers (URIs) and their content negotiability. It checks whether a given URI is accessible and whether the content associated with it can be negotiated based on the client's preferences. - This functionality ensures the reliability and accessibility of linked resources within the RDF ecosystem, aiding in maintaining data integrity and facilitating seamless integration with external resources. \ No newline at end of file + This functionality ensures the reliability and accessibility of linked resources within the RDF ecosystem, aiding in maintaining data integrity and facilitating seamless integration with external resources. + feedback_mail: + name: "Name: %{name}" + email: "Email: %{email}" + location: "Location: %{location}" + tags: Tags + feedback: Feedback \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 2ac75c679..2385828fe 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1503,4 +1503,9 @@ fr: Il vérifie si un URI donné est accessible et si le contenu associé peut être négocié en fonction des préférences du client. Cette fonctionnalité garantit la fiabilité et l'accessibilité des ressources liées dans l'écosystème RDF, aidant ainsi à maintenir l'intégrité des données et facilitant l'intégration transparente avec des ressources externes. - + feedback_mail: + name: "Nom: %{name}" + email: "E-mail: %{email}" + location: "Emplacement: %{location}" + tags: Tags + feedback: Feedback From 4f59b47ba21a5cf66b0500baa3737cf388187f48 Mon Sep 17 00:00:00 2001 From: OntoPortal Bot Date: Mon, 3 Jun 2024 19:04:45 +0200 Subject: [PATCH 06/10] [ontoportal-bot] Gemfile.lock update --- Gemfile.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5546308ad..0abda7ec7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: f91c32d532e6109b7eaccd5fd2ef51fef2192d71 + revision: abea6c217c0ee93bcfd80c47c2af52747365d491 branch: development specs: ontologies_api_client (2.2.0) @@ -134,7 +134,7 @@ GEM railties (> 3.1) childprocess (5.0.0) coderay (1.1.3) - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.1) crack (1.0.0) bigdecimal rexml @@ -169,7 +169,7 @@ GEM faraday-multipart (1.0.4) multipart-post (~> 2) faraday-net_http (2.1.0) - ffi (1.16.3) + ffi (1.17.0-x86_64-linux-gnu) flag-icons-rails (3.4.6.1) sass-rails flamegraph (0.9.5) @@ -199,7 +199,7 @@ GEM htmlbeautifier (1.4.3) htmlentities (4.3.4) http-accept (1.7.0) - http-cookie (1.0.5) + http-cookie (1.0.6) domain_name (~> 0.5) i18n (1.14.5) concurrent-ruby (~> 1.0) @@ -301,7 +301,7 @@ GEM time net-http (0.3.2) uri - net-imap (0.4.11) + net-imap (0.4.12) date net-protocol net-pop (0.1.2) @@ -316,7 +316,7 @@ GEM net-protocol net-ssh (7.2.3) netrc (0.11.0) - newrelic_rpm (9.9.0) + newrelic_rpm (9.10.0) nio4r (2.7.3) nokogiri (1.15.6-x86_64-linux) racc (~> 1.4) @@ -357,7 +357,7 @@ GEM omniauth (~> 2.0) open_uri_redirections (0.2.1) parallel (1.24.0) - parser (3.3.1.0) + parser (3.3.2.0) ast (~> 2.4.1) racc popper_js (1.16.1) @@ -424,7 +424,7 @@ GEM json redcarpet (3.6.0) regexp_parser (2.9.2) - reline (0.5.7) + reline (0.5.8) io-console (~> 0.5) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) @@ -451,7 +451,7 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.1) - rubocop (1.64.0) + rubocop (1.64.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -565,7 +565,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yard (0.9.36) - zeitwerk (2.6.14) + zeitwerk (2.6.15) PLATFORMS x86_64-linux From 163760657c127442b06fac834ead7a3bdea3ad23 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Mon, 3 Jun 2024 19:08:22 +0200 Subject: [PATCH 07/10] fix: display of agents with no acronym --- Gemfile | 2 +- Gemfile.lock | 23 +++++++++++++++-------- app/helpers/agent_helper.rb | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 810a8c853..a249f451e 100644 --- a/Gemfile +++ b/Gemfile @@ -87,7 +87,7 @@ gem 'omniauth-keycloak' group :staging, :production, :appliance do # application monitoring - gem 'newrelic_rpm' + gem 'newrelic_rpm', '~> 9.9' # logs in json format, useful for shipping logs to logstash # gem 'rackstash', git: 'https://github.com/planio-gmbh/rackstash.git' # gem 'logstash-logger' diff --git a/Gemfile.lock b/Gemfile.lock index 0abda7ec7..cf78df08d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,6 +92,7 @@ GEM execjs (~> 2) base64 (0.2.0) bcrypt_pbkdf (1.1.1) + bcrypt_pbkdf (1.1.1-x86_64-darwin) bigdecimal (3.1.8) bindata (2.5.0) bindex (0.8.1) @@ -101,7 +102,8 @@ GEM autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) - brakeman (5.4.1) + brakeman (6.1.2) + racc bugsnag (6.27.0) concurrent-ruby (~> 1.0) builder (3.2.4) @@ -121,11 +123,11 @@ GEM capistrano-bundler (>= 1.1, < 3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.39.2) + capybara (3.40.0) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.8) + nokogiri (~> 1.11) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) @@ -169,6 +171,7 @@ GEM faraday-multipart (1.0.4) multipart-post (~> 2) faraday-net_http (2.1.0) + ffi (1.17.0-x86_64-darwin) ffi (1.17.0-x86_64-linux-gnu) flag-icons-rails (3.4.6.1) sass-rails @@ -190,7 +193,7 @@ GEM railties (>= 5.1) hashdiff (1.1.0) hashie (5.0.0) - highline (2.1.0) + highline (3.0.1) html2haml (2.3.0) erubis (~> 2.7.0) haml (>= 4.0) @@ -318,7 +321,9 @@ GEM netrc (0.11.0) newrelic_rpm (9.10.0) nio4r (2.7.3) - nokogiri (1.15.6-x86_64-linux) + nokogiri (1.16.5-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.5-x86_64-linux) racc (~> 1.4) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) @@ -481,7 +486,8 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (4.9.0) + selenium-webdriver (4.21.1) + base64 (~> 0.2) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -568,6 +574,7 @@ GEM zeitwerk (2.6.15) PLATFORMS + x86_64-darwin-23 x86_64-linux DEPENDENCIES @@ -612,7 +619,7 @@ DEPENDENCIES mysql2 net-ftp (~> 0.2.0) net-http (~> 0.3.2) - newrelic_rpm + newrelic_rpm (~> 9.9) oj omniauth omniauth-github @@ -652,4 +659,4 @@ DEPENDENCIES will_paginate (~> 3.0) BUNDLED WITH - 2.3.23 + 2.5.10 diff --git a/app/helpers/agent_helper.rb b/app/helpers/agent_helper.rb index 00caebe47..f1a5240e2 100644 --- a/app/helpers/agent_helper.rb +++ b/app/helpers/agent_helper.rb @@ -169,7 +169,7 @@ def agent_tooltip(agent) if agent.affiliations && agent.affiliations != [] affiliations = "" agent.affiliations.each do |affiliation| - affiliations = affiliations + affiliation.acronym + " " + affiliations = "#{affiliations} #{affiliation.acronym || affiliation.name}" end end person_icon = inline_svg_tag 'icons/person.svg' , class: 'agent-type-icon' From 107b7b4318e3b6b72cb3082d21275955ec242211 Mon Sep 17 00:00:00 2001 From: OntoPortal Bot Date: Mon, 3 Jun 2024 19:46:10 +0200 Subject: [PATCH 08/10] [ontoportal-bot] Gemfile.lock update --- Gemfile | 2 +- Gemfile.lock | 25 +++++++++---------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index a249f451e..a9bcf5714 100644 --- a/Gemfile +++ b/Gemfile @@ -87,7 +87,7 @@ gem 'omniauth-keycloak' group :staging, :production, :appliance do # application monitoring - gem 'newrelic_rpm', '~> 9.9' + gem 'newrelic_rpm', '< 9.10.0' # logs in json format, useful for shipping logs to logstash # gem 'rackstash', git: 'https://github.com/planio-gmbh/rackstash.git' # gem 'logstash-logger' diff --git a/Gemfile.lock b/Gemfile.lock index cf78df08d..94fb95943 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,7 +92,6 @@ GEM execjs (~> 2) base64 (0.2.0) bcrypt_pbkdf (1.1.1) - bcrypt_pbkdf (1.1.1-x86_64-darwin) bigdecimal (3.1.8) bindata (2.5.0) bindex (0.8.1) @@ -102,8 +101,7 @@ GEM autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) - brakeman (6.1.2) - racc + brakeman (5.4.1) bugsnag (6.27.0) concurrent-ruby (~> 1.0) builder (3.2.4) @@ -123,11 +121,11 @@ GEM capistrano-bundler (>= 1.1, < 3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.40.0) + capybara (3.39.2) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.11) + nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) @@ -171,7 +169,6 @@ GEM faraday-multipart (1.0.4) multipart-post (~> 2) faraday-net_http (2.1.0) - ffi (1.17.0-x86_64-darwin) ffi (1.17.0-x86_64-linux-gnu) flag-icons-rails (3.4.6.1) sass-rails @@ -193,7 +190,7 @@ GEM railties (>= 5.1) hashdiff (1.1.0) hashie (5.0.0) - highline (3.0.1) + highline (2.1.0) html2haml (2.3.0) erubis (~> 2.7.0) haml (>= 4.0) @@ -319,11 +316,9 @@ GEM net-protocol net-ssh (7.2.3) netrc (0.11.0) - newrelic_rpm (9.10.0) + newrelic_rpm (9.9.0) nio4r (2.7.3) - nokogiri (1.16.5-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.16.5-x86_64-linux) + nokogiri (1.15.6-x86_64-linux) racc (~> 1.4) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) @@ -486,8 +481,7 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (4.21.1) - base64 (~> 0.2) + selenium-webdriver (4.9.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -574,7 +568,6 @@ GEM zeitwerk (2.6.15) PLATFORMS - x86_64-darwin-23 x86_64-linux DEPENDENCIES @@ -619,7 +612,7 @@ DEPENDENCIES mysql2 net-ftp (~> 0.2.0) net-http (~> 0.3.2) - newrelic_rpm (~> 9.9) + newrelic_rpm (< 9.10.0) oj omniauth omniauth-github @@ -659,4 +652,4 @@ DEPENDENCIES will_paginate (~> 3.0) BUNDLED WITH - 2.5.10 + 2.3.23 From 97c12e44e456089b59347fc140160d5176561dd8 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Sun, 9 Jun 2024 05:53:27 +0200 Subject: [PATCH 09/10] Feature: update docker compose to have a production service (#666) * update Dockerfile to precompile assets and create config files * disable graphql intializer as not used and raise issues with docker * docker ignore local credentials files so that will be created when run * update docker compose file to have two services for dev and production --- .dockerignore | 3 + .github/workflows/docker-image.yml | 3 +- Dockerfile | 6 +- Gemfile | 52 ++++----- Gemfile.lock | 156 +++++++++++++++----------- bin/ontoportal | 6 +- config/database.yml.sample | 1 - config/environment.rb | 13 +++ config/environments/production.rb | 15 +-- config/initializers/graphql_client.rb | 32 +++--- docker-compose.yml | 68 +++++++---- 11 files changed, 214 insertions(+), 141 deletions(-) diff --git a/.dockerignore b/.dockerignore index f4216be09..8cdea307d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,3 +20,6 @@ docker-compose.override.yml yarn-error.log yarn-debug.log* .yarn-integrity + +config/credentials/* +config/credentials.yml.enc \ No newline at end of file diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 5d2de1123..77ce165f9 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -3,11 +3,12 @@ name: Docker branch Images build on: push: branches: + - master - development - stage - test release: - types: [ published ] + types: [published] jobs: push_to_registry: name: Push Docker branch image to Docker Hub diff --git a/Dockerfile b/Dockerfile index 3d5c45f1b..2eb0ea3ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,10 +38,14 @@ RUN yarn install && yarn build +RUN cp config/bioportal_config_env.rb.sample config/bioportal_config_production.rb +RUN cp config/bioportal_config_env.rb.sample config/bioportal_config_development.rb +RUN cp config/database.yml.sample config/database.yml + # Precompile bootsnap code for faster boot times RUN bundle exec bootsnap precompile --gemfile app/ lib/ -# RUN SECRET_KEY_BASE_DUMMY="1" ./bin/rails assets:precompile +RUN SECRET_KEY_BASE_DUMMY="1" ./bin/rails assets:precompile ENV BINDING="0.0.0.0" EXPOSE 3000 diff --git a/Gemfile b/Gemfile index a9bcf5714..34aad240b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,12 @@ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '7.0.7' +gem 'rails', '7.0.3' -gem 'jsbundling-rails' gem 'chart-js-rails' -gem 'sassc-rails' #sass-rails replacent -gem 'terser' #ugilifer replacent +gem 'jsbundling-rails' +gem 'sassc-rails' # sass-rails replacent +gem 'terser' # ugilifer replacent # See https://github.com/rails/execjs#readme for more supported runtimes # gem 'therubyracer', platforms: :ruby @@ -16,12 +16,9 @@ gem 'bootstrap', '~> 4.2.0' gem 'jquery-rails' gem 'jquery-ui-rails' - - # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] gem 'sprockets-rails' - # Use the Puma web server [https://github.com/puma/puma] gem 'puma', '~> 5.0' @@ -35,10 +32,10 @@ gem 'turbo-rails' gem 'stimulus-rails' # Build JSON APIs with ease [https://github.com/rails/jbuilder] -#gem "jbuilder" +# gem "jbuilder" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem 'tzinfo-data', platforms: %i[ mingw mswin x64_mingw jruby ] +gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] # To use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0' @@ -49,41 +46,43 @@ gem 'bootsnap', require: false gem 'cube-ruby', require: 'cube' gem 'dalli' +gem 'ffi', '~> 1.16.3' +gem 'flag-icons-rails', '~> 3.4' gem 'flamegraph' gem 'graphql-client' gem 'haml', '~> 5.1' gem 'i18n' -gem 'rails-i18n', '~> 7.0.0' gem 'iconv' +gem 'inline_svg' +gem 'iso-639', '~> 0.3.6' +gem 'lookbook', '~> 1.5.5' gem 'multi_json' gem 'mysql2' gem 'oj' +gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', + branch: 'development' gem 'open_uri_redirections' gem 'pry' gem 'psych', '< 4' gem 'rack-mini-profiler' gem 'rails_autolink' +gem 'rails-i18n', '~> 7.0.0' gem 'rdoc' gem 'recaptcha', '~> 5.9.0' gem 'rest-client' gem 'stackprof', require: false gem 'thin' -gem 'view_component', '~> 2.72' gem 'turnout' +gem 'view_component', '~> 2.72' gem 'will_paginate', '~> 3.0' -gem 'inline_svg' -gem "lookbook", '~> 1.5.5' -gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'development' -gem "flag-icons-rails", "~> 3.4" -gem "iso-639", "~> 0.3.6" # Multi-Provider Authentication gem 'omniauth' -gem "omniauth-rails_csrf_protection" gem 'omniauth-github' gem 'omniauth-google-oauth2' -gem 'omniauth-orcid' gem 'omniauth-keycloak' +gem 'omniauth-orcid' +gem 'omniauth-rails_csrf_protection' group :staging, :production, :appliance do # application monitoring @@ -110,16 +109,16 @@ group :development do gem 'rubocop', require: false # gem 'i18n-debug' # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem - gem 'debug', platforms: %i[ mri mingw x64_mingw ] + gem 'debug', platforms: %i[mri mingw x64_mingw] # Use console on exceptions pages [https://github.com/rails/web-console] - gem 'web-console' gem 'i18n-tasks' gem 'i18n-tasks-csv', '~> 1.1' + gem 'web-console' gem 'deepl-rb' - gem 'letter_opener_web', '~> 2.0' gem 'haml-rails' + gem 'letter_opener_web', '~> 2.0' end group :test, :development do @@ -132,14 +131,11 @@ group :test do gem 'selenium-webdriver' gem 'simplecov', require: false gem 'simplecov-cobertura' # for codecov.io - #gem 'webdrivers' + # gem 'webdrivers' gem 'webmock' end +gem 'net-ftp', '~> 0.2.0', require: false +gem 'net-http', '~> 0.3.2' -gem "net-ftp", "~> 0.2.0", require: false -gem "net-http", "~> 0.3.2" - - - -gem "bugsnag", "~> 6.26" +gem 'bugsnag', '~> 6.26' diff --git a/Gemfile.lock b/Gemfile.lock index 94fb95943..5c78134fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,67 +17,67 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.7) - actionpack (= 7.0.7) - activesupport (= 7.0.7) + actioncable (7.0.3) + actionpack (= 7.0.3) + activesupport (= 7.0.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.7) - actionpack (= 7.0.7) - activejob (= 7.0.7) - activerecord (= 7.0.7) - activestorage (= 7.0.7) - activesupport (= 7.0.7) + actionmailbox (7.0.3) + actionpack (= 7.0.3) + activejob (= 7.0.3) + activerecord (= 7.0.3) + activestorage (= 7.0.3) + activesupport (= 7.0.3) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.7) - actionpack (= 7.0.7) - actionview (= 7.0.7) - activejob (= 7.0.7) - activesupport (= 7.0.7) + actionmailer (7.0.3) + actionpack (= 7.0.3) + actionview (= 7.0.3) + activejob (= 7.0.3) + activesupport (= 7.0.3) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.7) - actionview (= 7.0.7) - activesupport (= 7.0.7) - rack (~> 2.0, >= 2.2.4) + actionpack (7.0.3) + actionview (= 7.0.3) + activesupport (= 7.0.3) + rack (~> 2.0, >= 2.2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.7) - actionpack (= 7.0.7) - activerecord (= 7.0.7) - activestorage (= 7.0.7) - activesupport (= 7.0.7) + actiontext (7.0.3) + actionpack (= 7.0.3) + activerecord (= 7.0.3) + activestorage (= 7.0.3) + activesupport (= 7.0.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.7) - activesupport (= 7.0.7) + actionview (7.0.3) + activesupport (= 7.0.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.7) - activesupport (= 7.0.7) + activejob (7.0.3) + activesupport (= 7.0.3) globalid (>= 0.3.6) - activemodel (7.0.7) - activesupport (= 7.0.7) - activerecord (7.0.7) - activemodel (= 7.0.7) - activesupport (= 7.0.7) - activestorage (7.0.7) - actionpack (= 7.0.7) - activejob (= 7.0.7) - activerecord (= 7.0.7) - activesupport (= 7.0.7) + activemodel (7.0.3) + activesupport (= 7.0.3) + activerecord (7.0.3) + activemodel (= 7.0.3) + activesupport (= 7.0.3) + activestorage (7.0.3) + actionpack (= 7.0.3) + activejob (= 7.0.3) + activerecord (= 7.0.3) + activesupport (= 7.0.3) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.7) + activesupport (7.0.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -92,6 +92,8 @@ GEM execjs (~> 2) base64 (0.2.0) bcrypt_pbkdf (1.1.1) + bcrypt_pbkdf (1.1.1-arm64-darwin) + bcrypt_pbkdf (1.1.1-x86_64-darwin) bigdecimal (3.1.8) bindata (2.5.0) bindex (0.8.1) @@ -104,7 +106,7 @@ GEM brakeman (5.4.1) bugsnag (6.27.0) concurrent-ruby (~> 1.0) - builder (3.2.4) + builder (3.3.0) capistrano (3.18.1) airbrussh (>= 1.0.0) i18n @@ -134,7 +136,7 @@ GEM railties (> 3.1) childprocess (5.0.0) coderay (1.1.3) - concurrent-ruby (1.3.1) + concurrent-ruby (1.3.2) crack (1.0.0) bigdecimal rexml @@ -169,7 +171,7 @@ GEM faraday-multipart (1.0.4) multipart-post (~> 2) faraday-net_http (2.1.0) - ffi (1.17.0-x86_64-linux-gnu) + ffi (1.16.3) flag-icons-rails (3.4.6.1) sass-rails flamegraph (0.9.5) @@ -287,7 +289,7 @@ GEM method_source (1.1.0) mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2024.0507) + mime-types-data (3.2024.0604) mini_mime (1.1.5) minitest (5.23.1) msgpack (1.7.2) @@ -318,6 +320,16 @@ GEM netrc (0.11.0) newrelic_rpm (9.9.0) nio4r (2.7.3) + nokogiri (1.15.6-aarch64-linux) + racc (~> 1.4) + nokogiri (1.15.6-arm-linux) + racc (~> 1.4) + nokogiri (1.15.6-arm64-darwin) + racc (~> 1.4) + nokogiri (1.15.6-x86-linux) + racc (~> 1.4) + nokogiri (1.15.6-x86_64-darwin) + racc (~> 1.4) nokogiri (1.15.6-x86_64-linux) racc (~> 1.4) oauth2 (2.0.9) @@ -327,7 +339,7 @@ GEM rack (>= 1.2, < 4) snaky_hash (~> 2.0) version_gem (~> 1.1) - oj (3.16.3) + oj (3.16.4) bigdecimal (>= 3.0) omniauth (2.1.2) hashie (>= 3.4.6) @@ -356,7 +368,7 @@ GEM actionpack (>= 4.2) omniauth (~> 2.0) open_uri_redirections (0.2.1) - parallel (1.24.0) + parallel (1.25.1) parser (3.3.2.0) ast (~> 2.4.1) racc @@ -379,20 +391,20 @@ GEM rack (~> 2.2, >= 2.2.4) rack-test (2.1.0) rack (>= 1.3) - rails (7.0.7) - actioncable (= 7.0.7) - actionmailbox (= 7.0.7) - actionmailer (= 7.0.7) - actionpack (= 7.0.7) - actiontext (= 7.0.7) - actionview (= 7.0.7) - activejob (= 7.0.7) - activemodel (= 7.0.7) - activerecord (= 7.0.7) - activestorage (= 7.0.7) - activesupport (= 7.0.7) + rails (7.0.3) + actioncable (= 7.0.3) + actionmailbox (= 7.0.3) + actionmailer (= 7.0.3) + actionpack (= 7.0.3) + actiontext (= 7.0.3) + actionview (= 7.0.3) + activejob (= 7.0.3) + activemodel (= 7.0.3) + activerecord (= 7.0.3) + activestorage (= 7.0.3) + activesupport (= 7.0.3) bundler (>= 1.15.0) - railties (= 7.0.7) + railties (= 7.0.3) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -407,9 +419,9 @@ GEM actionview (> 3.1) activesupport (> 3.1) railties (> 3.1) - railties (7.0.7) - actionpack (= 7.0.7) - activesupport (= 7.0.7) + railties (7.0.3) + actionpack (= 7.0.3) + activesupport (= 7.0.3) method_source rake (>= 12.2) thor (~> 1.0) @@ -502,9 +514,9 @@ GEM sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) + sprockets-rails (3.5.1) + actionpack (>= 6.1) + activesupport (>= 6.1) sprockets (>= 3.0.0) sshkit (1.22.2) base64 @@ -568,7 +580,20 @@ GEM zeitwerk (2.6.15) PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86-linux + x86-linux-gnu + x86-linux-musl + x86_64-darwin x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl DEPENDENCIES bcrypt_pbkdf (>= 1.0, < 2.0) @@ -589,6 +614,7 @@ DEPENDENCIES debug deepl-rb ed25519 (>= 1.2, < 2.0) + ffi (~> 1.16.3) flag-icons-rails (~> 3.4) flamegraph graphql-client @@ -626,7 +652,7 @@ DEPENDENCIES psych (< 4) puma (~> 5.0) rack-mini-profiler - rails (= 7.0.7) + rails (= 7.0.3) rails-i18n (~> 7.0.0) rails_autolink rdoc @@ -652,4 +678,4 @@ DEPENDENCIES will_paginate (~> 3.0) BUNDLED WITH - 2.3.23 + 2.4.22 diff --git a/bin/ontoportal b/bin/ontoportal index 80f288cef..a2c030b23 100755 --- a/bin/ontoportal +++ b/bin/ontoportal @@ -135,8 +135,8 @@ dev() { bash_cmd+=" (bundle config unset local.ontologies_api_client) &&" fi done - bash_cmd+=" (bundle check || bundle install || bundle update) && bin/rails db:prepare && bundle exec rails s -b 0.0.0.0 -p 3000" - docker_run_cmd+=" --service-ports rails bash -c \"$bash_cmd\"" + bash_cmd+=" (bundle check || bundle install) && bin/rails db:prepare && bundle exec rails s -b 0.0.0.0 -p 3000" + docker_run_cmd+=" --service-ports dev bash -c \"$bash_cmd\"" echo "$docker_run_cmd" echo "Run: bundle exec rails s -b 0.0.0.0 -p 3000" eval "$docker_run_cmd" @@ -188,7 +188,7 @@ test() { # Function to handle the "run" option run() { echo "Run: $*" - docker compose run --rm -it rails bash -c "$*" + docker compose run --rm -it dev bash -c "$*" } create_config_files diff --git a/config/database.yml.sample b/config/database.yml.sample index 6edf6ed31..2787d5f25 100644 --- a/config/database.yml.sample +++ b/config/database.yml.sample @@ -50,7 +50,6 @@ test: production: <<: *default database: bioportal_ui_production - username: bp_user password: <%= ENV["BIOPORTAL_WEB_UI_DATABASE_PASSWORD"] %> appliance: diff --git a/config/environment.rb b/config/environment.rb index cac531577..99be33ce5 100755 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,18 @@ # Load the Rails application. require_relative "application" +# Remove this after migrating to Rails 7.1 (https://github.com/rails/rails/issues/32947#issuecomment-1356391185) +class Rails::Application + def secret_key_base + if Rails.env.development? || Rails.env.test? || ENV["SECRET_KEY_BASE_DUMMY"] + secrets.secret_key_base ||= generate_development_secret + else + validate_secret_key_base( + ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base + ) + end + end +end + # Initialize the Rails application. Rails.application.initialize! diff --git a/config/environments/production.rb b/config/environments/production.rb index 3eb3961f0..432744722 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,4 @@ -require "active_support/core_ext/integer/time" +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -18,11 +18,11 @@ # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). - config.require_master_key = true + config.require_master_key = ENV['REQUIRE_MASTER_KEY'].present? # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. - config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Compress JavaScripts config.assets.js_compressor = :terser @@ -56,7 +56,7 @@ config.log_level = :info # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -97,12 +97,13 @@ end # Use a different cache store in production. - config.cache_store = :mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211", { namespace: 'bioportal_web_ui', expires_in: 1.day } + config.cache_store = :mem_cache_store, ENV['MEMCACHE_SERVERS'] || 'localhost:11211', + { namespace: 'bioportal_web_ui', expires_in: 1.day } # Add custom data attributes to sanitize allowed list - config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style', 'data-cls', 'data-ont'] + config.action_view.sanitized_allowed_attributes = %w[id class style data-cls data-ont] - if ENV["RAILS_LOG_TO_STDOUT"].present? + if ENV['RAILS_LOG_TO_STDOUT'].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) diff --git a/config/initializers/graphql_client.rb b/config/initializers/graphql_client.rb index 57760a168..9cf786411 100644 --- a/config/initializers/graphql_client.rb +++ b/config/initializers/graphql_client.rb @@ -1,17 +1,17 @@ # frozen_string_literal: true - -require 'graphql/client' -require 'graphql/client/http' - -module GitHub - HTTPAdapter = GraphQL::Client::HTTP.new('https://api.github.com/graphql') do - def headers(_context) - { 'Authorization': "Bearer #{Rails.application.credentials.dig(:kgcl, :github_access_token)}" } - end - end - - Client = GraphQL::Client.new( - schema: File.join(Rails.root, 'data', 'schema.json').to_s, - execute: HTTPAdapter - ) -end +# Disable as no used in ontoportal-lirmm branch and causing problems with docker image build +# require 'graphql/client' +# require 'graphql/client/http' +# +# module GitHub +# HTTPAdapter = GraphQL::Client::HTTP.new('https://api.github.com/graphql') do +# def headers(_context) +# { 'Authorization': "Bearer #{Rails.application.credentials.dig(:kgcl, :github_access_token)}" } +# end +# end +# +# Client = GraphQL::Client.new( +# schema: File.join(Rails.root, 'data', 'schema.json').to_s, +# execute: HTTPAdapter +# ) +# end diff --git a/docker-compose.yml b/docker-compose.yml index 1eb928b19..644e1ca6e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,32 @@ x-app: &default-app - image: agroportal/ontoportal_web_ui:development + image: agroportal/ontoportal_web_ui:master env_file: - ".env" tty: true volumes: - - .:/app - - bundle:/usr/local/bundle + - bundle:/srv/ontoportal/bundle - node:/node_modules - rails_cache:/app/tmp/cache - assets:/app/public/assets - /var/run/docker.sock:/var/run/docker.sock + - .:/app + depends_on: + db: + condition: service_healthy + cache: + condition: service_started + node: + condition: service_started + links: + - db + - cache + environment: &env + BUNDLE_WITHOUT: "" + BUNDLE_PATH: /srv/ontoportal/bundle + DB_HOST: db + CACHE_HOST: cache + ports: + - "3000:3000" tmpfs: - /tmp - /app/tmp/pids @@ -28,12 +45,12 @@ services: healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 5s - retries: 3 + retries: 3 cache: image: memcached:latest restart: unless-stopped - command: [ "-m", "1024" ] + command: ["-m", "1024"] networks: - default ports: @@ -41,36 +58,48 @@ services: node: <<: *default-app command: "yarn build --watch" + depends_on: + - cache + - db + + dev: + <<: *default-app - rails: + production: <<: *default-app + command: "bundle exec puma -C config/puma.rb" + environment: + <<: *env + RAILS_ENV: "production" + BUNDLE_WITHOUT: "development test" + BUNDLE_PATH: "/usr/local/bundle" + #SECRET_KEY_BASE: TODO + #RAILS_MASTER_KEY: TODO + BIOPORTAL_WEB_UI_DATABASE_PASSWORD: root + MEMCACHE_SERVERS: "cache:11211" depends_on: db: condition: service_healthy cache: condition: service_started - node: - condition: service_started - links: - - db - - cache - environment: - BUNDLE_WITHOUT: '' - DB_HOST: db - CACHE_HOST: cache - ports: - - "3000:3000" + volumes: + - node:/node_modules + - rails_cache:/app/tmp/cache + - assets:/app/public/assets + - app_ui:/app + test: <<: *default-app depends_on: - db - cache - chrome-server - network_mode: 'host' + network_mode: "host" environment: - BUNDLE_WITHOUT: '' + BUNDLE_WITHOUT: "" DB_HOST: 127.0.0.1 CACHE_HOST: 127.0.0.1 + chrome-server: image: selenium/standalone-chrome:112.0-chromedriver-112.0-grid-4.9.0-20230421 shm_size: 2g @@ -84,3 +113,4 @@ volumes: rails_cache: assets: node: + app_ui: From 88c68554af34a712e68e2d65f915687107d268a8 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Mon, 10 Jun 2024 05:32:28 +0200 Subject: [PATCH 10/10] feature: add deploy config for agroportal --- .github/workflows/deploy.yml | 97 ++++++++++++++++++------------------ config/deploy/agroportal.rb | 17 +++++++ 2 files changed, 66 insertions(+), 48 deletions(-) create mode 100644 config/deploy/agroportal.rb diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 916bd6cc4..b0874453c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,27 +18,28 @@ name: Capistrano Deployment # Controls when the action will run. on: push: - branches: [ stage test] + branches: + - stage + - test # Allows running this workflow manually from the Actions tab workflow_dispatch: inputs: BRANCH: - description: 'Branch/tag to deploy' + description: "Branch/tag to deploy" options: - stage - test - master - default: test + default: stage required: true environment: - description: 'target environment to deploy to' + description: "target environment to deploy to" type: choice options: - staging - - production - - appliance + - agroportal - test - default: test + default: stage jobs: deploy: runs-on: ubuntu-latest @@ -47,45 +48,45 @@ jobs: PRIVATE_CONFIG_REPO: ${{ format('git@github.com:{0}.git', secrets.CONFIG_REPO) }} # Steps represent a sequence of tasks that will be executed as part of the job steps: - - name: set branch/tag and environment to deploy from inputs - run: | - # workflow_dispatch default input doesn't get set on push so we need to set defaults - # via shell parameter expansion - # https://dev.to/mrmike/github-action-handling-input-default-value-5f2g - USER_INPUT_BRANCH=${{ inputs.branch }} - echo "BRANCH=${USER_INPUT_BRANCH:github.head_ref:-master}" >> $GITHUB_ENV + - name: set branch/tag and environment to deploy from inputs + run: | + # workflow_dispatch default input doesn't get set on push so we need to set defaults + # via shell parameter expansion + # https://dev.to/mrmike/github-action-handling-input-default-value-5f2g + USER_INPUT_BRANCH=${{ inputs.branch }} + echo "BRANCH=${USER_INPUT_BRANCH:github.head_ref:-master}" >> $GITHUB_ENV - USER_INPUT_ENVIRONMENT=${{ inputs.environment }} - echo "TARGET=${USER_INPUT_ENVIRONMENT:-staging}" >> $GITHUB_ENV - - CONFIG_REPO=${{ secrets.CONFIG_REPO }} - GH_PAT=${{ secrets.GH_PAT }} - echo "PRIVATE_CONFIG_REPO=https://${GH_PAT}@github.com/${CONFIG_REPO}" >> $GITHUB_ENV - - echo "SSH_JUMPHOST=${{ secrets.SSH_JUMPHOST }}" >> $GITHUB_ENV - echo "SSH_JUMPHOST_USER=${{ secrets.SSH_JUMPHOST_USER }}" >> $GITHUB_ENV - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.7.6 # Not needed with a .ruby-version file - bundler-cache: true # runs 'bundle install' and caches installed gems automatically - - name: get-deployment-config - uses: actions/checkout@v3 - with: - repository: ${{ secrets.CONFIG_REPO }} # repository containing deployment settings - token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT - path: deploy_config - - name: copy-deployment-config - run: cp -r deploy_config/ontoportal_web_ui/${{ inputs.environment }}/* . - # add ssh hostkey so that capistrano doesn't complain - - name: Add jumphost's hostkey to Known Hosts - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_JUMPHOST }}" - ssh-keyscan -H ${{ secrets.SSH_JUMPHOST }} > ~/.ssh/known_hosts - shell: bash - - uses: miloserdow/capistrano-deploy@master - with: - target: ${{ env.TARGET }} # which environment to deploy - deploy_key: ${{ secrets.DEPLOY_ENC_KEY }} # Name of the variable configured in Settings/Secrets of your github project + USER_INPUT_ENVIRONMENT=${{ inputs.environment }} + echo "TARGET=${USER_INPUT_ENVIRONMENT:-staging}" >> $GITHUB_ENV + + CONFIG_REPO=${{ secrets.CONFIG_REPO }} + GH_PAT=${{ secrets.GH_PAT }} + echo "PRIVATE_CONFIG_REPO=https://${GH_PAT}@github.com/${CONFIG_REPO}" >> $GITHUB_ENV + + echo "SSH_JUMPHOST=${{ secrets.SSH_JUMPHOST }}" >> $GITHUB_ENV + echo "SSH_JUMPHOST_USER=${{ secrets.SSH_JUMPHOST_USER }}" >> $GITHUB_ENV + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.6 # Not needed with a .ruby-version file + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: get-deployment-config + uses: actions/checkout@v3 + with: + repository: ${{ secrets.CONFIG_REPO }} # repository containing deployment settings + token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT + path: deploy_config + - name: copy-deployment-config + run: cp -r deploy_config/ontoportal_web_ui/${{ inputs.environment }}/* . + # add ssh hostkey so that capistrano doesn't complain + - name: Add jumphost's hostkey to Known Hosts + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_JUMPHOST }}" + ssh-keyscan -H ${{ secrets.SSH_JUMPHOST }} > ~/.ssh/known_hosts + shell: bash + - uses: miloserdow/capistrano-deploy@master + with: + target: ${{ env.TARGET }} # which environment to deploy + deploy_key: ${{ secrets.DEPLOY_ENC_KEY }} # Name of the variable configured in Settings/Secrets of your github project diff --git a/config/deploy/agroportal.rb b/config/deploy/agroportal.rb new file mode 100644 index 000000000..c01f3fb90 --- /dev/null +++ b/config/deploy/agroportal.rb @@ -0,0 +1,17 @@ +# Simple Role Syntax +# ================== +# Supports bulk-adding hosts to roles, the primary +# server in each group is considered to be the first +# unless any hosts have the primary property set. +# Don't declare `role :all`, it's a meta role +role :app, %w[agroportal.lirmm.fr] +role :db, %w[agroportal.lirmm.fr] # sufficient to run db:migrate only on one system +set :branch, ENV.include?('BRANCH') ? ENV['BRANCH'] : 'master' +# Extended Server Syntax +# ====================== +# This can be used to drop a more detailed server +# definition into the server list. The second argument +# something that quacks like a hash can be used to set +# extended properties on the server. +# server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value +set :log_level, :error