diff --git a/.env.sample b/.env.sample index 9d4cb5a54..aeafa8126 100644 --- a/.env.sample +++ b/.env.sample @@ -1,137 +1,43 @@ -# Default values are optimized for production to avoid having to configure -# much in production. -# -# However it should be easy to get going in development too. If you see an -# uncommented option that means it's either mandatory to set or it's being -# overwritten in development to make your life easier. +##################### UI configuration #################### +RAILS_ENV=development +SITE=Testportal +ORG=LIRMM +ORG_URL=http://www.lirmm.fr -# Enable BuildKit by default: -# https://docs.docker.com/develop/develop-images/build_enhancements -export DOCKER_BUILDKIT=1 +UI_URL=http://localhost:3000 +API_URL=http://localhost:9393 +API_KEY= -# Rather than use the directory name, let's control the name of the project. -export COMPOSE_PROJECT_NAME=ontoportal-docker +UI_THEME=ontoportal -# In development we want all services to start but in production you don't -# need the asset watchers to run since assets get built into the image. -# -# You can even choose not to run postgres and redis in prod if you plan to use -# managed cloud services. Everything "just works", even optional depends_on! -#export COMPOSE_PROFILES=postgres,redis,web,worker,cable -export COMPOSE_PROFILES=db,cache,assets,web +BIOMIXER_URL= +BIOMIXER_APIKEY= -# If you're running native Linux and your uid:gid isn't 1000:1000 you can set -# these to match your values before you build your image. You can check what -# your uid:gid is by running `id` from your terminal. -#export UID=1000 -#export GID=1000 +ANNOTATOR_URL= -# You can generate a more secure secret by running: ./run rails secret -export SECRET_KEY_BASE=insecure_key_for_dev +FAIRNESS_DISABLED=false +FAIRNESS_URL= -# Which environment is running? These should be "development" or "production". -#export RAILS_ENV=production -#export NODE_ENV=production -export RAILS_ENV=development -export NODE_ENV=development +NCBO_ANNOTATORPLUS_ENABLED=false +NCBO_ANNOTATOR_URL= +NCBO_API_KEY= -# The bind port for puma. -# -# Be warned that if you change this value you'll need to change 8000 in both -# your Dockerfile and in a few spots in docker-compose.yml due to the nature of -# how this value can be set (Docker Compose doesn't support nested ENV vars). -#export PORT=8000 +SUPPORT_EMAIL=sifrportal-support@lirmm.fr +RELEASE_VERSION="OntoPortal Appliance 3.0.1" -# How many workers and threads should your app use? WEB_CONCURRENCY defaults -# to the server's CPU count * 2. That is a good starting point. In development -# it's a good idea to use 1 to avoid race conditions when debugging. -#export WEB_CONCURRENCY= -#export RAILS_MAX_THREADS=5 -#export WEB_CONCURRENCY=1 -#export RAILS_MAX_THREADS=1 +ANALYTICS_ID= +USE_RECAPTCHA=false +#################### Buidling the API for running tests #################### +## An ontology that will be imported in the starting of the API server +STARTER_ONTOLOGY=STY +## API key of a remote API used to download the starter ontology +OP_API_KEY=8b5b7825-538d-40e0-9e9e-5ab9274a9aeb +## API url of the remote API used to download the starter ontology +OP_API_URL="https://data.bioontology.org" -# You'll always want to set POSTGRES_USER and POSTGRES_PASSWORD since the -# postgres Docker image uses them for its default database user and password. -export POSTGRES_USER=hello -export POSTGRES_PASSWORD=password -#export POSTGRES_DB=hello -#export POSTGRES_HOST=postgres -#export POSTGRES_PORT=5432 +## Image repositroy from which the ontoportal api will be built +API_IMAGE_REPOSITORY=agroportal +## Image tag/version from which the ontoportal api will be built +API_IMAGE_TAG=development -# What's your full Redis connection URL? This will be used for caching, Sidekiq, -# and Action Cable. You can always split them up later. -#export REDIS_URL=redis://redis:6379/1 -# The bind port for puma but for Action Cable. -# -# Be warned that if you change this value you'll need to change 28080 in a few -# spots in docker-compose.yml due to the nature of how this value can be set -# (Docker Compose doesn't support nested ENV vars). -#export CABLE_PORT=28080 - -# The Action Cable address that will be accessible over HTTP. In production -# you would typically have this reverse proxied to a sub-domain with nginx, in -# which case you would set something like: ws://cable.example.com -# -# This is one case where it defaults to a development value because it's not -# possible for me to know what domain name you'll be using. -#export ACTION_CABLE_FRONTEND_URL=ws://localhost:28080 - -# Comma separated list of RegExp origins to allow connections from for Action -# Cable. The values will be converted into a proper RegExp, so omit the / /. -# -# Examples: -# http:\/\/localhost* -# http:\/\/example.*,https:\/\/example.* -#export ACTION_CABLE_ALLOWED_REQUEST_ORIGINS=http:\/\/localhost* - -# If this is set then Rails will serve files from public/ in production. You -# probably don't want this behavior unless you're testing prod mode locally, -# because nginx would typically serve static files. -#export RAILS_SERVE_STATIC_FILES= - -# Should Docker restart your containers if they go down in unexpected ways? -#export DOCKER_RESTART_POLICY=unless-stopped -export DOCKER_RESTART_POLICY=no - -# What health check test command do you want to run? In development, having it -# curl your web server will result in a lot of log spam, so setting it to -# /bin/true is an easy way to make the health check do basically nothing. -#export DOCKER_WEB_HEALTHCHECK_TEST=curl localhost:8000/up -export DOCKER_WEB_HEALTHCHECK_TEST=/bin/true - -# What ip:port should be published back to the Docker host for the app server? -# If you're using Docker Toolbox or a custom VM you can't use 127.0.0.1. This -# is being overwritten in dev to be compatible with more dev environments. -# -# If you have a port conflict because something else is using 8000 then you -# can either stop that process or change 8000 to be something else. -# -# Use the default in production to avoid having puma directly accessible to -# the internet since it'll very likely be behind nginx or a load balancer. -#export DOCKER_WEB_PORT_FORWARD=127.0.0.1:8000 -export DOCKER_WEB_PORT_FORWARD=127.0.0.1:3000 - -# This is the same as above except for Action Cable. -#export DOCKER_CABLE_PORT_FORWARD=127.0.0.1:28080 -export DOCKER_CABLE_PORT_FORWARD=28080 - -# What volume path should be used? In dev we want to volume mount everything -# so that we can develop our code without rebuilding our Docker images. -#export DOCKER_WEB_VOLUME=./public:/app/public - - -export DOCKER_WEB_VOLUME=.:/app - -# What CPU and memory constraints will be added to your services? When left at -# 0 they will happily use as much as needed. -# export DOCKER_POSTGRES_CPUS=0 -# export DOCKER_POSTGRES_MEMORY=0 -# export DOCKER_REDIS_CPUS=0 -# export DOCKER_REDIS_MEMORY=0 -# export DOCKER_WEB_CPUS=0 -# export DOCKER_WEB_MEMORY=0 -# export DOCKER_WORKER_CPUS=0 -# export DOCKER_WORKER_MEMORY=0 -# export DOCKER_CABLE_CPUS=0 -# export DOCKER_CABLE_MEMORY=0 diff --git a/.github/workflows/docker-image-arm.yml b/.github/workflows/docker-image-arm.yml new file mode 100644 index 000000000..0de876c29 --- /dev/null +++ b/.github/workflows/docker-image-arm.yml @@ -0,0 +1,56 @@ +name: Docker branch Images build - ARM version + +on: + push: + branches: + - development + - stage + - test + release: + types: [ published ] +jobs: + push_to_registry: + name: Push Docker branch image to Docker Hub + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to Docker Hub + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: | + agroportal/ontoportal_web_ui-arm + ghcr.io/${{ github.repository }}-arm + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/arm64 + build-args: | + RUBY_VERSION=2.7.8 + push: true + tags: ${{ steps.meta.outputs.tags }}-${{ env.BRANCH_NAME }}-arm64 + labels: ${{ steps.meta.outputs.labels }} + diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 000000000..5d2de1123 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,55 @@ +name: Docker branch Images build + +on: + push: + branches: + - development + - stage + - test + release: + types: [ published ] +jobs: + push_to_registry: + name: Push Docker branch image to Docker Hub + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to Docker Hub + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: | + agroportal/ontoportal_web_ui + ghcr.io/${{ github.repository }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64 + build-args: | + RUBY_VERSION=2.7.8 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/tests-real-data-stageportal.yml b/.github/workflows/tests-real-data-stageportal.yml new file mode 100644 index 000000000..53a9d112d --- /dev/null +++ b/.github/workflows/tests-real-data-stageportal.yml @@ -0,0 +1,52 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby +name: "Run remote API (stageportal) tests CI" + +on: + push: + pull_request: + types: [ opened, reopened ] +env: + API_URL: https://data.stageportal.lirmm.fr/ # or ${{ secrets.API_URL }} + API_KEY: 1de0a270-29c5-4dda-b043-7c3580628cd5 # public apikey or ${{ secrets.API_KEY }} + BIOMIXER_URL: ${{ secrets.BIOMIXER_URL }} + FAIRNESS_URL: ${{ secrets.FAIRNESS_URL }} + ANNOTATOR_URL: ${{ secrets.ANNOTATOR_URL }} + DB_HOST: 127.0.0.1 +jobs: + test: + runs-on: ubuntu-latest + services: + mysql: + image: mysql:8.0 + ports: + - "3306:3306" + env: + MYSQL_ROOT_PASSWORD: root + memcached: + image: memcached:1.6 + ports: + - 11211:11211 + steps: + - name: Checkout code + uses: actions/checkout@v3 + # Add or replace dependency steps here + - name: Install Ruby and gems + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.8 + bundler-cache: true + # Add or replace database setup steps here + - name: set up config file + run: cp config/database.yml.sample config/database.yml + - name: Set up database schema + run: RAILS_ENV=test bin/rails db:setup + - name: Set up yarn + run: yarn install + # Add or replace test runners here + - name: Run tests + run: RAILS_ENV=test bin/rails test test/controllers/* -v \ No newline at end of file diff --git a/.github/workflows/tests-real-data-testportal.yml b/.github/workflows/tests-real-data-testportal.yml new file mode 100644 index 000000000..baa017f4e --- /dev/null +++ b/.github/workflows/tests-real-data-testportal.yml @@ -0,0 +1,52 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby +name: "Run remote API (testportal) tests CI" + +on: + push: + pull_request: + types: [ opened, reopened ] +env: + API_URL: https://data.testportal.lirmm.fr/ # or ${{ secrets.API_URL }} + API_KEY: 1de0a270-29c5-4dda-b043-7c3580628cd5 # public apikey or ${{ secrets.API_KEY }} + BIOMIXER_URL: ${{ secrets.BIOMIXER_URL }} + FAIRNESS_URL: ${{ secrets.FAIRNESS_URL }} + ANNOTATOR_URL: ${{ secrets.ANNOTATOR_URL }} + DB_HOST: 127.0.0.1 +jobs: + test: + runs-on: ubuntu-latest + services: + mysql: + image: mysql:8.0 + ports: + - "3306:3306" + env: + MYSQL_ROOT_PASSWORD: root + memcached: + image: memcached:1.6 + ports: + - 11211:11211 + steps: + - name: Checkout code + uses: actions/checkout@v3 + # Add or replace dependency steps here + - name: Install Ruby and gems + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.8 + bundler-cache: true + # Add or replace database setup steps here + - name: set up config file + run: cp config/database.yml.sample config/database.yml + - name: Set up database schema + run: RAILS_ENV=test bin/rails db:setup + - name: Set up yarn + run: yarn install + # Add or replace test runners here + - name: Run tests + run: RAILS_ENV=test bin/rails test test/controllers/* -v \ No newline at end of file diff --git a/.github/workflows/tests-system.yml b/.github/workflows/tests-system.yml new file mode 100644 index 000000000..b2cee9451 --- /dev/null +++ b/.github/workflows/tests-system.yml @@ -0,0 +1,65 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby +name: "Run system tests CI" + +on: + push: + pull_request: + types: [ opened, reopened ] +env: + API_URL: http://localhost:9393 + REMOTE_API_KEY: 8b5b7825-538d-40e0-9e9e-5ab9274a9aeb + REMOTE_API_URL: https://data.bioontology.org + BIOMIXER_URL: ${{ secrets.BIOMIXER_URL }} + FAIRNESS_URL: ${{ secrets.FAIRNESS_URL }} + ANNOTATOR_URL: ${{ secrets.ANNOTATOR_URL }} + DB_HOST: 127.0.0.1 +jobs: + test: + runs-on: ubuntu-latest + services: + mysql: + image: mysql:8.0 + ports: + - "3306:3306" + env: + MYSQL_ROOT_PASSWORD: root + memcached: + image: memcached:1.6 + ports: + - 11211:11211 + chrome-server: + image: selenium/standalone-chrome:112.0-chromedriver-112.0-grid-4.9.0-20230421 + options: "--shm-size=2g" + ports: + - "4444:4444" + + steps: + - name: Checkout code + uses: actions/checkout@v3 + # Add or replace dependency steps here + - name: Install Ruby and gems + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.8 + bundler-cache: true + + - name: Run OntoPortal API + run: bin/run_api -k ${{ env.REMOTE_API_KEY }} -r agroportal -t development -s STY -u ${{ env.REMOTE_API_URL }} + + # Add or replace database setup steps here + - name: set up config file + run: cp config/database.yml.sample config/database.yml + - name: Set up database schema + run: RAILS_ENV=test bin/rails db:setup + - name: Set up yarn + run: yarn install + # Add or replace test runners here + - name: Run integration tests + run: | + CI=true RAILS_ENV=test bin/rails assets:precompile + CI=true RAILS_ENV=test bin/rails test -v test/system/* \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..c489db6e7 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,61 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby +name: "Run local API tests CI" + +on: + push: + pull_request: + types: [ opened, reopened ] +env: + API_URL: http://localhost:9393 + REMOTE_API_KEY: 8b5b7825-538d-40e0-9e9e-5ab9274a9aeb + REMOTE_API_URL: https://data.bioontology.org + BIOMIXER_URL: ${{ secrets.BIOMIXER_URL }} + FAIRNESS_URL: ${{ secrets.FAIRNESS_URL }} + ANNOTATOR_URL: ${{ secrets.ANNOTATOR_URL }} + DB_HOST: 127.0.0.1 +jobs: + test: + runs-on: ubuntu-latest + services: + mysql: + image: mysql:8.0 + ports: + - "3306:3306" + env: + MYSQL_ROOT_PASSWORD: root + memcached: + image: memcached:1.6 + ports: + - 11211:11211 + steps: + - name: Checkout code + uses: actions/checkout@v3 + # Add or replace dependency steps here + - name: Install Ruby and gems + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.8 + bundler-cache: true + + - name: Run OntoPortal API + run: bin/run_api -k ${{ env.REMOTE_API_KEY }} -r agroportal -t development -s STY -u ${{ env.REMOTE_API_URL }} + + # Add or replace database setup steps here + - name: set up config file + run: cp config/database.yml.sample config/database.yml + - name: Set up database schema + run: RAILS_ENV=test bin/rails db:setup + - name: Set up yarn + run: yarn install + # Add or replace test runners here + - name: Run tests + run: COVERAGE=true RAILS_ENV=test bin/rails test -v + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4ce7bb4e4..06f40b993 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,4 @@ docker-sync.yml /config/credentials/development.key /config/credentials/test.key /config/credentials/staging.key -/config/credentials/appliance.key - +/config/credentials/appliance.key \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 88f019fdb..29de61e2a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,50 +1,50 @@ -FROM ruby:2.7.5-alpine AS app - +# Make sure it matches the Ruby version in .ruby-version and Gemfile +ARG RUBY_VERSION=3.2.0 +FROM ruby:${RUBY_VERSION}-alpine + +# Install libvips for Active Storage preview support +RUN apk add --no-cache build-base \ + libxml2-dev \ + libxslt-dev \ + mariadb-dev \ + git \ + tzdata \ + nodejs yarn \ + less \ + bash \ + docker \ + docker-compose \ + && mkdir /node_modules + +# Rails app lives here WORKDIR /app -ARG UID=1000 -ARG GID=1000 - -RUN apk add --no-cache \ - build-base \ - libxml2-dev \ - libxslt-dev \ - mariadb-dev \ - git \ - nodejs \ - tzdata \ - yarn \ - less \ - && addgroup --gid ${GID} ruby \ - && adduser -u ${UID} -G ruby -D ruby \ - && chown ruby:ruby -R /app \ - && mkdir /node_modules \ - && chown ruby:ruby -R /node_modules /app - -USER ruby - -COPY --chown=ruby:ruby bin/ ./bin -RUN chmod 0755 bin/* - +# Set production environment ARG RAILS_ENV="production" +ARG BUNDLE_WITHOUT="development test" + +ENV RAILS_LOG_TO_STDOUT="1" \ + RAILS_SERVE_STATIC_FILES="true" \ + RAILS_ENV="${RAILS_ENV}" \ + BUNDLE_PATH=/usr/local/bundle \ + BUNDLE_WITHOUT="${BUNDLE_WITHOUT}" + +RUN gem update --system --no-document && \ + gem install -N bundler -ENV RAILS_ENV="${RAILS_ENV}" \ - NODE_ENV="${NODE_ENV}" \ - PATH="${PATH}:/home/ruby/.local/bin:/node_modules/.bin" \ - USER="ruby" \ - BUNDLE_PATH=/usr/local/bundle +COPY . . -COPY --chown=ruby:ruby Gemfile* ./ -RUN bundle install --jobs "$(nproc)" -RUN gem install rails +RUN bundle install +RUN yarn install && yarn build -RUN echo "--modules-folder /node_modules" > .yarnrc -COPY --chown=ruby:ruby package.json *yarn* ./ -RUN yarn install +# 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 +ENV BINDING="0.0.0.0" EXPOSE 3000 -CMD ["sh"] \ No newline at end of file +CMD ["bash"] \ No newline at end of file diff --git a/Gemfile b/Gemfile index 6760c3e10..30bed7a0f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '7.0.3' +gem 'rails', '7.0.7' gem 'jsbundling-rails' @@ -54,9 +54,10 @@ gem 'flamegraph' gem 'graphql-client' gem 'haml', '~> 5.1' gem 'i18n' +gem 'rails-i18n', '~> 7.0.0' gem 'iconv' gem 'multi_json' -gem 'mysql2', '0.5.3' +gem 'mysql2' gem 'oj' gem 'open_uri_redirections' gem 'pry' @@ -71,9 +72,19 @@ gem 'thin' gem 'view_component', '~> 2.72' gem 'turnout' 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' group :staging, :production, :appliance do # application monitoring @@ -104,7 +115,9 @@ group :development do # Use console on exceptions pages [https://github.com/rails/web-console] gem 'web-console' - gem "lookbook", '~> 1.5.5' + gem 'i18n-tasks' + gem 'deepl-rb' + gem 'letter_opener_web', '~> 2.0' end group :test, :development do @@ -115,9 +128,13 @@ group :test do # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] gem 'capybara' gem 'selenium-webdriver' - gem 'webdrivers' + gem 'simplecov', require: false + gem 'simplecov-cobertura' # for codecov.io + #gem 'webdrivers' end gem "net-ftp", "~> 0.2.0", require: false -gem "net-http" +gem "net-http", "~> 0.3.2" + + diff --git a/Gemfile.lock b/Gemfile.lock index 637f30618..0de70f349 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,73 +17,74 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.3) - actionpack (= 7.0.3) - activesupport (= 7.0.3) + actioncable (7.0.7) + actionpack (= 7.0.7) + activesupport (= 7.0.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.3) - actionpack (= 7.0.3) - activejob (= 7.0.3) - activerecord (= 7.0.3) - activestorage (= 7.0.3) - activesupport (= 7.0.3) + actionmailbox (7.0.7) + actionpack (= 7.0.7) + activejob (= 7.0.7) + activerecord (= 7.0.7) + activestorage (= 7.0.7) + activesupport (= 7.0.7) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.3) - actionpack (= 7.0.3) - actionview (= 7.0.3) - activejob (= 7.0.3) - activesupport (= 7.0.3) + actionmailer (7.0.7) + actionpack (= 7.0.7) + actionview (= 7.0.7) + activejob (= 7.0.7) + activesupport (= 7.0.7) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.3) - actionview (= 7.0.3) - activesupport (= 7.0.3) - rack (~> 2.0, >= 2.2.0) + actionpack (7.0.7) + actionview (= 7.0.7) + activesupport (= 7.0.7) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.3) - actionpack (= 7.0.3) - activerecord (= 7.0.3) - activestorage (= 7.0.3) - activesupport (= 7.0.3) + actiontext (7.0.7) + actionpack (= 7.0.7) + activerecord (= 7.0.7) + activestorage (= 7.0.7) + activesupport (= 7.0.7) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.3) - activesupport (= 7.0.3) + actionview (7.0.7) + activesupport (= 7.0.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.3) - activesupport (= 7.0.3) + activejob (7.0.7) + activesupport (= 7.0.7) globalid (>= 0.3.6) - 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) + 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) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.3) + activesupport (7.0.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) + aes_key_wrap (1.1.0) airbrussh (1.5.0) sshkit (>= 1.6.1, != 1.7.0) ast (2.4.2) @@ -91,6 +92,14 @@ 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 + bindata (2.4.15) bindex (0.8.1) bootsnap (1.17.0) msgpack (~> 1.2) @@ -139,7 +148,9 @@ GEM debug (1.8.0) irb (>= 1.5.0) reline (>= 0.3.1) + deepl-rb (2.5.3) diff-lcs (1.5.0) + docile (1.4.0) domain_name (0.6.20231109) ed25519 (1.3.0) erubi (1.12.0) @@ -153,14 +164,18 @@ GEM faraday-excon (2.0.0) excon (>= 0.27.4) faraday (~> 2.0.0.alpha.pre.2) + faraday-follow_redirects (0.3.0) + faraday (>= 1, < 3) faraday-multipart (1.0.4) multipart-post (~> 2) faraday-net_http (2.1.0) ffi (1.16.3) + flag-icons-rails (3.4.6.1) + sass-rails flamegraph (0.9.5) globalid (1.2.1) activesupport (>= 6.1) - graphql (2.1.7) + graphql (2.1.6) racc (~> 1.4) graphql-client (0.18.0) activesupport (>= 3.0) @@ -168,25 +183,44 @@ GEM haml (5.2.2) temple (>= 0.8.0) tilt + hashie (5.0.0) + highline (2.1.0) html2haml (2.3.0) erubis (~> 2.7.0) haml (>= 4.0) nokogiri (>= 1.6.0) ruby_parser (~> 3.5) + htmlbeautifier (1.4.2) + htmlentities (4.3.4) http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) i18n (1.14.1) concurrent-ruby (~> 1.0) + i18n-tasks (1.0.13) + 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) + rails-i18n + rainbow (>= 2.2.2, < 4.0) + terminal-table (>= 1.5.1) iconv (1.0.8) importmap-rails (1.2.3) actionpack (>= 6.0.0) activesupport (>= 6.0.0) railties (>= 6.0.0) + inline_svg (1.9.0) + activesupport (>= 3.0) + nokogiri (>= 1.6) io-console (0.6.0) - irb (1.10.0) + irb (1.9.1) rdoc reline (>= 0.3.8) + iso-639 (0.3.6) jquery-rails (4.6.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -195,14 +229,43 @@ GEM railties (>= 3.2.16) jsbundling-rails (1.2.1) railties (>= 6.0.0) - json (2.7.1) + json (2.7.0) + json-jwt (1.16.3) + activesupport (>= 4.2) + aes_key_wrap + bindata + faraday (~> 2.0) + faraday-follow_redirects + jwt (2.7.1) language_server-protocol (3.17.0.3) + launchy (2.5.2) + addressable (~> 2.8) + letter_opener (1.8.1) + launchy (>= 2.2, < 3) + letter_opener_web (2.0.0) + actionmailer (>= 5.2) + letter_opener (~> 1.7) + railties (>= 5.2) + rexml listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) + lookbook (1.5.5) + actioncable + activemodel + css_parser + htmlbeautifier (~> 1.3) + htmlentities (~> 4.3.4) + listen (~> 3.0) + railties (>= 5.0) + redcarpet (~> 3.5) + rouge (>= 3.26, < 5.0) + view_component (> 2.0, < 4) + yard (~> 0.9.25) + zeitwerk (~> 2.5) lz4-ruby (0.3.3) mail (2.8.1) mini_mime (>= 0.1.1) @@ -219,8 +282,9 @@ GEM minitest (5.20.0) msgpack (1.7.2) multi_json (1.15.0) + multi_xml (0.6.0) multipart-post (2.3.0) - mysql2 (0.5.3) + mysql2 (0.5.5) net-ftp (0.2.0) net-protocol time @@ -242,9 +306,43 @@ GEM newrelic_rpm (9.6.0) base64 nio4r (2.7.0) + nokogiri (1.15.5-x86_64-darwin) + racc (~> 1.4) nokogiri (1.15.5-x86_64-linux) racc (~> 1.4) + oauth2 (2.0.9) + faraday (>= 0.17.3, < 3.0) + jwt (>= 1.0, < 3.0) + multi_xml (~> 0.5) + rack (>= 1.2, < 4) + snaky_hash (~> 2.0) + version_gem (~> 1.1) oj (3.16.1) + omniauth (2.1.1) + hashie (>= 3.4.6) + rack (>= 2.2.3) + rack-protection + omniauth-github (2.0.0) + omniauth (~> 2.0) + omniauth-oauth2 (~> 1.7.1) + omniauth-google-oauth2 (0.8.0) + jwt (>= 2.0) + omniauth (>= 1.1.1) + omniauth-oauth2 (>= 1.6) + omniauth-keycloak (1.5.1) + faraday + json-jwt (> 1.13.0) + omniauth (>= 2.0) + omniauth-oauth2 (~> 1.7.1) + omniauth-oauth2 (1.7.3) + oauth2 (>= 1.4, < 3) + omniauth (>= 1.9, < 3) + omniauth-orcid (2.1.1) + omniauth-oauth2 (~> 1.3) + ruby_dig (~> 0.0.2) + omniauth-rails_csrf_protection (1.0.1) + actionpack (>= 4.2) + omniauth (~> 2.0) open_uri_redirections (0.2.1) parallel (1.23.0) parser (3.2.2.4) @@ -264,22 +362,24 @@ GEM rack (>= 0.4) rack-mini-profiler (3.1.1) rack (>= 1.2.0) + rack-protection (3.1.0) + rack (~> 2.2, >= 2.2.4) rack-test (2.1.0) rack (>= 1.3) - 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) + 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) bundler (>= 1.15.0) - railties (= 7.0.3) + railties (= 7.0.7) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -287,13 +387,16 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) + rails-i18n (7.0.8) + i18n (>= 0.7, < 2) + railties (>= 6.0.0, < 8) rails_autolink (1.1.8) actionview (> 3.1) activesupport (> 3.1) railties (> 3.1) - railties (7.0.3) - actionpack (= 7.0.3) - activesupport (= 7.0.3) + railties (7.0.7) + actionpack (= 7.0.7) + activesupport (= 7.0.7) method_source rake (>= 12.2) thor (~> 1.0) @@ -306,7 +409,8 @@ GEM rdoc (6.3.3) recaptcha (5.9.0) json - regexp_parser (2.8.3) + redcarpet (3.6.0) + regexp_parser (2.8.2) reline (0.4.1) io-console (~> 0.5) rest-client (2.1.0) @@ -315,6 +419,7 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.2.6) + rouge (4.2.0) rspec-core (3.12.2) rspec-support (~> 3.12.0) rspec-expectations (3.12.3) @@ -347,9 +452,12 @@ GEM parser (>= 3.2.1.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) + ruby_dig (0.0.2) ruby_parser (3.20.3) sexp_processor (~> 4.16) rubyzip (2.3.2) + sass-rails (6.0.0) + sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) ffi (~> 1.9) sassc-rails (2.1.2) @@ -364,6 +472,19 @@ GEM rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) sexp_processor (4.17.0) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-cobertura (2.1.0) + rexml + 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) spawnling (2.1.5) sprockets (4.2.1) concurrent-ruby (~> 1.0) @@ -379,6 +500,8 @@ GEM stimulus-rails (1.3.0) railties (>= 6.0.0) temple (0.10.3) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) terser (1.1.20) execjs (>= 0.3.0, < 3) thin (1.8.2) @@ -403,6 +526,7 @@ GEM concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) uri (0.13.0) + version_gem (1.1.3) view_component (2.82.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) @@ -412,10 +536,6 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webdrivers (5.3.1) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0, < 4.11) websocket (1.2.10) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -423,12 +543,13 @@ GEM will_paginate (3.3.1) xpath (3.2.0) nokogiri (~> 1.8) + yard (0.9.34) zeitwerk (2.6.12) PLATFORMS - ruby - x86_64-darwin-21 + x86_64-darwin-23 x86_64-linux + x86_64-linux-musl DEPENDENCIES bcrypt_pbkdf (>= 1.0, < 2.0) @@ -446,31 +567,45 @@ DEPENDENCIES cube-ruby dalli debug + deepl-rb ed25519 (>= 1.2, < 2.0) + flag-icons-rails (~> 3.4) flamegraph graphql-client haml (~> 5.1) html2haml i18n + i18n-tasks iconv importmap-rails + inline_svg + iso-639 (~> 0.3.6) jquery-rails jquery-ui-rails jsbundling-rails + letter_opener_web (~> 2.0) listen + lookbook (~> 1.5.5) multi_json - mysql2 (= 0.5.3) + mysql2 net-ftp (~> 0.2.0) - net-http + net-http (~> 0.3.2) newrelic_rpm oj + omniauth + omniauth-github + omniauth-google-oauth2 + omniauth-keycloak + omniauth-orcid + omniauth-rails_csrf_protection ontologies_api_client! open_uri_redirections pry psych (< 4) puma (~> 5.0) rack-mini-profiler - rails (= 7.0.3) + rails (= 7.0.7) + rails-i18n (~> 7.0.0) rails_autolink rdoc recaptcha (~> 5.9.0) @@ -480,6 +615,8 @@ DEPENDENCIES sassc-rails select2-rails selenium-webdriver + simplecov + simplecov-cobertura sprockets-rails stackprof stimulus-rails @@ -490,8 +627,7 @@ DEPENDENCIES tzinfo-data view_component (~> 2.72) web-console - webdrivers will_paginate (~> 3.0) BUNDLED WITH - 2.3.23 + 2.4.21 diff --git a/README b/README deleted file mode 100644 index 0d6affddc..000000000 --- a/README +++ /dev/null @@ -1,182 +0,0 @@ -== Welcome to Rails - -Rails is a web-application and persistence framework that includes everything -needed to create database-backed web-applications according to the -Model-View-Control pattern of separation. This pattern splits the view (also -called the presentation) into "dumb" templates that are primarily responsible -for inserting pre-built data in between HTML tags. The model contains the -"smart" domain objects (such as Account, Product, Person, Post) that holds all -the business logic and knows how to persist themselves to a database. The -controller handles the incoming requests (such as Save New Account, Update -Product, Show Post) by manipulating the model and directing data to the view. - -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. - -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. - - -== Getting started - -1. At the command prompt, start a new rails application using the rails command - and your application name. Ex: rails myapp - (If you've downloaded rails in a complete tgz or zip, this step is already done) -2. Change directory into myapp and start the web server: script/server (run with --help for options) -3. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!" -4. Follow the guidelines to start developing your application - - -== Web Servers - -By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise -Rails will use the WEBrick, the webserver that ships with Ruby. When you run script/server, -Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures -that you can always get up and running quickly. - -Mongrel is a Ruby-based webserver with a C-component (which requires compilation) that is -suitable for development and deployment of Rails applications. If you have Ruby Gems installed, -getting up and running with mongrel is as easy as: gem install mongrel. -More info at: http://mongrel.rubyforge.org - -If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than -Mongrel and WEBrick and also suited for production use, but requires additional -installation and currently only works well on OS X/Unix (Windows users are encouraged -to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from -http://www.lighttpd.net. - -And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby -web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not -for production. - -But of course its also possible to run Rails on any platform that supports FCGI. -Apache, LiteSpeed, IIS are just a few. For more information on FCGI, -please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI - - -== Debugging Rails - -Have "tail -f" commands running on the server.log and development.log. Rails will -automatically display debugging and runtime information to these files. Debugging -info will also be shown in the browser on requests from 127.0.0.1. - - -== Breakpoints - -Breakpoint support is available through the script/breakpointer client. This -means that you can break out of execution at any point in the code, investigate -and change the model, AND then resume execution! Example: - - class WeblogController < ActionController::Base - def index - @posts = Post.find(:all) - breakpoint "Breaking out from the list" - end - end - -So the controller will accept the action, run the first line, then present you -with a IRB prompt in the breakpointer window. Here you can do things like: - -Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint' - - >> @posts.inspect - => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, - #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" - >> @posts.first.title = "hello from a breakpoint" - => "hello from a breakpoint" - -...and even better is that you can examine how your runtime objects actually work: - - >> f = @posts.first - => #nil, "body"=>nil, "id"=>"1"}> - >> f. - Display all 152 possibilities? (y or n) - -Finally, when you're ready to resume execution, you press CTRL-D - - -== Console - -You can interact with the domain model by starting the console through script/console. -Here you'll have all parts of the application configured, just like it is when the -application is running. You can inspect domain models, change values, and save to the -database. Starting the script without arguments will launch it in the development environment. -Passing an argument will specify a different environment, like script/console production. - -To reload your controllers and models after launching the console run reload! - -To reload your controllers and models after launching the console run reload! - - - -== Description of contents - -app - Holds all the code that's specific to this particular application. - -app/controllers - Holds controllers that should be named like weblogs_controller.rb for - automated URL mapping. All controllers should descend from ApplicationController - which itself descends from ActionController::Base. - -app/models - Holds models that should be named like post.rb. - Most models will descend from ActiveRecord::Base. - -app/views - Holds the template files for the view that should be named like - weblogs/index.rhtml for the WeblogsController#index action. All views use eRuby - syntax. - -app/views/layouts - Holds the template files for layouts to be used with views. This models the common - header/footer method of wrapping views. In your views, define a layout using the - layout :default and create a file named default.rhtml. Inside default.rhtml, - call <% yield %> to render the view using this layout. - -app/helpers - Holds view helpers that should be named like weblogs_helper.rb. These are generated - for you automatically when using script/generate for controllers. Helpers can be used to - wrap functionality for your views into methods. - -config - Configuration files for the Rails environment, the routing map, the database, and other dependencies. - -components - Self-contained mini-applications that can bundle together controllers, models, and views. - -db - Contains the database schema in schema.rb. db/migrate contains all - the sequence of Migrations for your schema. - -doc - This directory is where your application documentation will be stored when generated - using rake doc:app - -lib - Application specific libraries. Basically, any kind of custom code that doesn't - belong under controllers, models, or helpers. This directory is in the load path. - -public - The directory available for the web server. Contains subdirectories for images, stylesheets, - and javascripts. Also contains the dispatchers and the default HTML files. This should be - set as the DOCUMENT_ROOT of your web server. - -script - Helper scripts for automation and generation. - -test - Unit and functional tests along with fixtures. When using the script/generate scripts, template - test files will be generated for you and placed in this directory. - -vendor - External libraries that the application depends on. Also includes the plugins subdirectory. - This directory is in the load path. diff --git a/README.md b/README.md index 33bdfb964..f3dd62b69 100644 --- a/README.md +++ b/README.md @@ -1,130 +1,40 @@ -bioportal_web_ui -================ +# Run ontologies_web_ui -A rails application for biological ontologies, see http://bioportal.bioontology.org/ +## Using OntoPortal UI utilities script +### See help -## Todo - -### Finir les metadata (grosse prio) - -#### Grosses tâches - -* Faire en sorte de bien récupèrer quand on a des array de metadata (en ajoutant les champs) - * Faire une method générique (genre une class) qui permet de facilement distinguer les types de metadata (pas besoin d'ajouter les champs en dur dans le code) - -* Passer à bootstrap pour faire le joli layout? - * Dans un premier temps import bootstrap seulement dans le form? - * On pourrait faire des col pour ranger les metadata par "dates", "description", "links" dans des panels bootstrap - -* Meilleur affichage des metadata : - * Bouton pour les valeurs qui reviennent tout le temps - -#### Plein de petits todo - -* Permettre de filtrer les ontos en fct des metadata -Voir col P : https://docs.google.com/spreadsheets/d/1r1twxJvXdQXrkX0Ocis6YY08nlO5cGneCAQ5F7U_CoA/edit#gid=0 -Surtout dans les pages browse et welcome (voir les trucs en orange). Page Landscape plus tard - -Le but c'est d'avoir des résultats visibles - -* 3 blocks dans edit submission metadata - * Origin - * Toutes les prop qui ont la valeur S (colonne Q de docs.google Review of metadata prop) pour Simple - * Toutes les prop qui ont la valeur C (colonne Q de docs.google Review of metadata prop) pour Complete - - -**SEPARER License, onto hasSyntax, etc. Des meatdata de base de NCBO** - -**Change tout** -* Premier bloc avec les metadata pas du tout extraites (formats, contact, file upload...) -* 2eme bloc avec les metadata que le portal utilise: description, documentation... -* Expliquer que quand on ajoute un fichier les metadata sont updatés avec les metadata contenues dans le fichier (après le bloc des metadata non extraites)a - -X Add contact > même type de bouton que Add new Language - -Petit ? quand tu le survoles on dit qu'on extrait normalement cette metadata de l'onto à partir de: list des metadata mappings -Et on note "omv:description + rdfs:comment" quand on prend les valeurs de chaque propriété -Trouver comment montrer qu'on prend omv:naturalLanguage plusieurs fois si remplis plusieurs fois - -Rassembler toutes les dates ensembles (au lieu de simple et complete) dans un même table - -X Attention date picker par défaut marche pas sur firefox (utiliser date picker de jQuery, celui used par NCBO) - -Mettre des espaces : HASCONTRIBUTOR & cie - -Afficher dans l'interface graphique quelle metadata on remplis par défaut (on les met toutes au même endroit ?) - -TODO list : mettre un champ text plus gros ? (comme description) - -Le but est d'éviter d'avoir un gros listing indigeste de metadata - -Exemple : -Sous description ajouter bouton "voulez-vous ajouter un abstract?" -Sous release date "voulez-vous ajouter d'autre dates" -Sous contact "voulez-vous ajouter d'autres peoples" ? -Sous publisher "voulez-vous ajouter d'autre rôle d'institut etc" - -Depiction et logo à côté l'un de l'autre - -Generalizes, HASDISJUNCTIONSWITH, et toutes les metadata de relations entre onto: faire en sorte de pouvoir choisir des ontos dans BioPortal (pitit popip) - -Dans Default Properties sur le spreadsheet des metadata elles sont groupées - - -* Dans browse: permettre de trier dans l'ordre alphabétique -* Ajouter des petits drapeaux à côté des naturalLang (dans browse et dans la page de présentation des submissions) - -* IncludedInDataCatalog: faudrait avoir des boutons plutôt (voir avec logo, pour les catalogs connus comme ontobee) - -* Handling des metadata plus propre et - - -### Fermer les issues - - - -### Page Welcome - -https://github.com/sifrproject/bioportal_web_ui/issues/12 -https://github.com/agroportal/agroportal_web_ui/issues/59 - - -Dans links par exemple on peut mettre "need an ontology mapper: yamplusplus.lirmm.fr" - -Feed twitter: https://support.twitter.com/articles/20170071 -https://twitter.com/agrohackathon - - -## Resolve problems - -* Ca lag beaucoup sur des ontos avec beaucoup de submissions (comme TRANSMAT sur AgroPortal) - -* En particulier quand on ajoute une nouvelle submission - -* Add New submission bug parfois (avec uploadFile) : faire des tests, s'il faut ça fait un moment que ça plante sans qu'on s'en rende compte (on utilise beaucoup l'URL en ce moment) -```ruby -TypeError (no implicit conversion of Symbol into Integer): - app/controllers/submissions_controller.rb:28:in `[]' - app/controllers/submissions_controller.rb:28:in `create' - - -# Following line bugs -if @errors[:error][:uploadFilePath] && @errors[:error][:uploadFilePath].first[:options] -end +```bash +bin/ontoportal help ``` -## How to - -### Add a facet filter for a metadata in browse - -In 3 files : -* app/views/ontologies/browse.html.erb -* public/browse/app.js -* app/controllers/ontologies_controller.rb +``` +Usage: bin/ontoportal {dev|test|run|help} [--reset-cache] [--api-url API_URL] [--api-key API_KEY] + dev : Start the Ontoportal Web UI development server. + Example: bin/ontoportal dev --api-url http://localhost:9393 --api-key my_api_key + Use --reset-cache to remove volumes: bin/ontoportal dev --reset-cache + test : Run tests. Specify either a test file:line_number or empty for 'all'. + Example: $0 test test/integration/login_flows_test.rb:22 + run : Run a command in the Ontoportal Web UI Docker container. + help : Show this help message. + +Description: + This script provides convenient commands for managing an Ontoportal Web UI + application using Docker Compose. It includes options for starting the development server, + running tests, and executing commands within the Ontoportal Web UI Docker container. + +Goals: + - Simplify common tasks related to Ontoportal Web UI development using Docker. + - Provide a consistent and easy-to-use interface for common actions. +``` -## Log to production.log +### Run dev +```bash +bin/ontoportal dev --api-url --api-key +``` -```ruby -Rails.logger.warn "Submission params: #{params[:submission]}" +### Run test with a local OntoPortal API +```bash +bin/ontoportal test ``` + diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index 3e1c15c81..000000000 --- a/README.rdoc +++ /dev/null @@ -1,261 +0,0 @@ -== Welcome to Rails - -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. - -This pattern splits the view (also called the presentation) into "dumb" -templates that are primarily responsible for inserting pre-built data in between -HTML tags. The model contains the "smart" domain objects (such as Account, -Product, Person, Post) that holds all the business logic and knows how to -persist themselves to a database. The controller handles the incoming requests -(such as Save New Account, Update Product, Show Post) by manipulating the model -and directing data to the view. - -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. - -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. - - -== Getting Started - -1. At the command prompt, create a new Rails application: - rails new myapp (where myapp is the application name) - -2. Change directory to myapp and start the web server: - cd myapp; rails server (run with --help for options) - -3. Go to http://localhost:3000/ and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" - -4. Follow the guidelines to start developing your application. You can find -the following resources handy: - -* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html -* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ - - -== Debugging Rails - -Sometimes your application goes wrong. Fortunately there are a lot of tools that -will help you debug it and get it back on the rails. - -First area to check is the application log files. Have "tail -f" commands -running on the server.log and development.log. Rails will automatically display -debugging and runtime information to these files. Debugging info will also be -shown in the browser on requests from 127.0.0.1. - -You can also log your own messages directly into the log file from your code -using the Ruby logger class from inside your controllers. Example: - - class WeblogController < ActionController::Base - def destroy - @weblog = Weblog.find(params[:id]) - @weblog.destroy - logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") - end - end - -The result will be a message in your log file along the lines of: - - Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! - -More information on how to use the logger is at http://www.ruby-doc.org/core/ - -Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are -several books available online as well: - -* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) -* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) - -These two books will bring you up to speed on the Ruby language and also on -programming in general. - - -== Debugger - -Debugger support is available through the debugger command when you start your -Mongrel or WEBrick server with --debugger. This means that you can break out of -execution at any point in the code, investigate and change the model, and then, -resume execution! You need to install ruby-debug to run the server in debugging -mode. With gems, use sudo gem install ruby-debug. Example: - - class WeblogController < ActionController::Base - def index - @posts = Post.all - debugger - end - end - -So the controller will accept the action, run the first line, then present you -with a IRB prompt in the server window. Here you can do things like: - - >> @posts.inspect - => "[#nil, "body"=>nil, "id"=>"1"}>, - #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" - >> @posts.first.title = "hello from a debugger" - => "hello from a debugger" - -...and even better, you can examine how your runtime objects actually work: - - >> f = @posts.first - => #nil, "body"=>nil, "id"=>"1"}> - >> f. - Display all 152 possibilities? (y or n) - -Finally, when you're ready to resume execution, you can enter "cont". - - -== Console - -The console is a Ruby shell, which allows you to interact with your -application's domain model. Here you'll have all parts of the application -configured, just like it is when the application is running. You can inspect -domain models, change values, and save to the database. Starting the script -without arguments will launch it in the development environment. - -To start the console, run rails console from the application -directory. - -Options: - -* Passing the -s, --sandbox argument will rollback any modifications - made to the database. -* Passing an environment name as an argument will load the corresponding - environment. Example: rails console production. - -To reload your controllers and models after launching the console run -reload! - -More information about irb can be found at: -link:http://www.rubycentral.org/pickaxe/irb.html - - -== dbconsole - -You can go to the command line of your database directly through rails -dbconsole. You would be connected to the database with the credentials -defined in database.yml. Starting the script without arguments will connect you -to the development database. Passing an argument will connect you to a different -database, like rails dbconsole production. Currently works for MySQL, -PostgreSQL and SQLite 3. - -== Description of Contents - -The default directory structure of a generated Ruby on Rails application: - - |-- app - | |-- assets - | | |-- images - | | |-- javascripts - | | `-- stylesheets - | |-- controllers - | |-- helpers - | |-- mailers - | |-- models - | `-- views - | `-- layouts - |-- config - | |-- environments - | |-- initializers - | `-- locales - |-- db - |-- doc - |-- lib - | |-- assets - | `-- tasks - |-- log - |-- public - |-- script - |-- test - | |-- fixtures - | |-- functional - | |-- integration - | |-- performance - | `-- unit - |-- tmp - | `-- cache - | `-- assets - `-- vendor - |-- assets - | |-- javascripts - | `-- stylesheets - `-- plugins - -app - Holds all the code that's specific to this particular application. - -app/assets - Contains subdirectories for images, stylesheets, and JavaScript files. - -app/controllers - Holds controllers that should be named like weblogs_controller.rb for - automated URL mapping. All controllers should descend from - ApplicationController which itself descends from ActionController::Base. - -app/models - Holds models that should be named like post.rb. Models descend from - ActiveRecord::Base by default. - -app/views - Holds the template files for the view that should be named like - weblogs/index.html.erb for the WeblogsController#index action. All views use - eRuby syntax by default. - -app/views/layouts - Holds the template files for layouts to be used with views. This models the - common header/footer method of wrapping views. In your views, define a layout - using the layout :default and create a file named default.html.erb. - Inside default.html.erb, call <% yield %> to render the view using this - layout. - -app/helpers - Holds view helpers that should be named like weblogs_helper.rb. These are - generated for you automatically when using generators for controllers. - Helpers can be used to wrap functionality for your views into methods. - -config - Configuration files for the Rails environment, the routing map, the database, - and other dependencies. - -db - Contains the database schema in schema.rb. db/migrate contains all the - sequence of Migrations for your schema. - -doc - This directory is where your application documentation will be stored when - generated using rake doc:app - -lib - Application specific libraries. Basically, any kind of custom code that - doesn't belong under controllers, models, or helpers. This directory is in - the load path. - -public - The directory available for the web server. Also contains the dispatchers and the - default HTML files. This should be set as the DOCUMENT_ROOT of your web - server. - -script - Helper scripts for automation and generation. - -test - Unit and functional tests along with fixtures. When using the rails generate - command, template test files will be generated for you and placed in this - directory. - -vendor - External libraries that the application depends on. Also includes the plugins - subdirectory. If the app has frozen rails, those gems also go here, under - vendor/rails/. This directory is in the load path. diff --git a/app/assets/images/icons/earth.svg b/app/assets/images/icons/earth.svg new file mode 100644 index 000000000..316885c03 --- /dev/null +++ b/app/assets/images/icons/earth.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/assets/images/icons/github.svg b/app/assets/images/icons/github.svg index 06f3dcd7d..656a9dede 100644 --- a/app/assets/images/icons/github.svg +++ b/app/assets/images/icons/github.svg @@ -1,3 +1,3 @@ - - - \ No newline at end of file + + + diff --git a/app/assets/images/icons/google.svg b/app/assets/images/icons/google.svg index f710f4020..01b25ab73 100644 --- a/app/assets/images/icons/google.svg +++ b/app/assets/images/icons/google.svg @@ -1,3 +1,3 @@ - - - \ No newline at end of file + + + diff --git a/app/assets/images/icons/keycloak.svg b/app/assets/images/icons/keycloak.svg index 8b3cfb86c..e9f3a43a9 100644 --- a/app/assets/images/icons/keycloak.svg +++ b/app/assets/images/icons/keycloak.svg @@ -1,6 +1,11 @@ - - - - - \ No newline at end of file + + + + + + + + + + + diff --git a/app/assets/images/icons/orcid.svg b/app/assets/images/icons/orcid.svg index 86a1ea871..0f367e7e9 100644 --- a/app/assets/images/icons/orcid.svg +++ b/app/assets/images/icons/orcid.svg @@ -1,3 +1,3 @@ - - - \ No newline at end of file + + + diff --git a/app/assets/images/icons/searching_database.svg b/app/assets/images/icons/searching_database.svg new file mode 100644 index 000000000..00e0cf703 --- /dev/null +++ b/app/assets/images/icons/searching_database.svg @@ -0,0 +1,8 @@ + + + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon + + \ No newline at end of file diff --git a/app/assets/javascripts/bp_property_tree.js b/app/assets/javascripts/bp_property_tree.js index 5d65a7a46..5eb3c2780 100644 --- a/app/assets/javascripts/bp_property_tree.js +++ b/app/assets/javascripts/bp_property_tree.js @@ -301,6 +301,7 @@ } else { ROOT.html("No properties exist for this ontology"); ROOT.css("font-size", "14px").css("margin", "5px"); + jQuery("#prop_contents .spinner-border").hide() } if (typeof OPTIONS.onInit === 'function') { OPTIONS.onInit(); } diff --git a/app/assets/javascripts/bp_search.js.erb b/app/assets/javascripts/bp_search.js.erb index d8f44b9f1..f8e46181a 100644 --- a/app/assets/javascripts/bp_search.js.erb +++ b/app/assets/javascripts/bp_search.js.erb @@ -159,6 +159,7 @@ jQuery(document).ready(function() { jQuery("#search_results a.additional_ont_results_link").live("click", showAdditionalOntResults); jQuery("#search_results a.additional_cls_results_link").live("click", showAdditionalClsResults); + jQuery("#search_options").hide(); jQuery("#advanced_options").on('click', toggleAdvancedSearchOptions); // Events to run whenever search results are updated (mainly counts) @@ -828,11 +829,22 @@ function updatePopupCounts() { function classLabelSpan(cls) { // Wrap the class prefLabel in a span, indicating that the class is obsolete // if necessary. + let prefLabel = cls.prefLabel + + if(Array.isArray(prefLabel)){ + let query = jQuery("#search_keywords").val() + // Filter labels containing the query or return the first label + let filteredLabels = prefLabel.filter(label => label.includes(query)) + // If there are matching labels, use the first one; otherwise, use the first label + prefLabel = filteredLabels.length > 0 ? filteredLabels[0] : prefLabel[0] + } + var MAX_LENGTH = 60, - labelText = cls.prefLabel, + labelText = prefLabel, labelSpan = null; + if (labelText > MAX_LENGTH) { - labelText = cls.prefLabel.substring(0, MAX_LENGTH) + "..."; + labelText = prefLabel.substring(0, MAX_LENGTH) + "..."; } labelSpan = jQuery("").text(labelText); if (cls.obsolete === true) { diff --git a/app/assets/stylesheets/bioportal.scss b/app/assets/stylesheets/bioportal.scss index 5badfe1aa..a00e324f2 100644 --- a/app/assets/stylesheets/bioportal.scss +++ b/app/assets/stylesheets/bioportal.scss @@ -50,6 +50,7 @@ body{ } .disabled-link{ + pointer-events: none; color: #888888 !important; span{ opacity: 0.6; diff --git a/app/assets/stylesheets/components/alert.scss b/app/assets/stylesheets/components/alert.scss index 118d55fe7..a1678544b 100644 --- a/app/assets/stylesheets/components/alert.scss +++ b/app/assets/stylesheets/components/alert.scss @@ -82,3 +82,8 @@ } +.alert-button{ + white-space: nowrap; + margin-right: 20px; +} + diff --git a/app/assets/stylesheets/components/dropdown.scss b/app/assets/stylesheets/components/dropdown.scss index b9c73b7f4..9d325c5c7 100644 --- a/app/assets/stylesheets/components/dropdown.scss +++ b/app/assets/stylesheets/components/dropdown.scss @@ -14,4 +14,8 @@ border-radius: 5px; margin-bottom: 20px; margin-top: 20px; +} + +.dropdown-item-selected { + background-color: var(--light-color) !important; } \ No newline at end of file diff --git a/app/assets/stylesheets/components/index.scss b/app/assets/stylesheets/components/index.scss index 7ea4777c1..8a6ff12cc 100644 --- a/app/assets/stylesheets/components/index.scss +++ b/app/assets/stylesheets/components/index.scss @@ -26,3 +26,4 @@ @import "image"; @import "alert"; @import "progress_pages"; +@import "select"; diff --git a/app/assets/stylesheets/components/input_field.scss b/app/assets/stylesheets/components/input_field.scss index 9b80f4e54..533dd7dac 100644 --- a/app/assets/stylesheets/components/input_field.scss +++ b/app/assets/stylesheets/components/input_field.scss @@ -34,62 +34,9 @@ margin-top: 5px; } -.ts-control { - padding: 12px; - border-radius: 5px; - border-color: #BDBDBD; -} - -.ts-dropdown-content .option { - padding: 12px; -} - -.ts-dropdown .active { - background-color: #f8f8f8; - -} - -.ts-dropdown { - margin: 0; - color: #666666; -} .chosen-container { padding: 0; border-radius: 5px; } -.ts-wrapper.single .ts-control:after { - border-color: #343a40 transparent transparent; - border-style: solid; - border-width: 5px 5px 0; - content: " "; - display: block; - height: 0; - margin-top: -3px; - position: absolute; - right: calc(0.75rem + 5px); - top: 50%; - width: 0; -} - -.ts-wrapper.multi .ts-control>div{ - border-radius: 5px; - font-size: 11px; - padding: 2px 0 4px 6px; - color: #888888; -} - -.ts-wrapper.plugin-remove_button:not(.rtl) .item .remove{ - border-left: none; - color: #888888 !important; - margin-left: 0; -} - -.ts-wrapper.plugin-remove_button .item .remove:hover { - background: unset; -} - -.has-items .ts-control > input { - width: unset !important; -} \ No newline at end of file diff --git a/app/assets/stylesheets/components/select.scss b/app/assets/stylesheets/components/select.scss new file mode 100644 index 000000000..b730bbb0f --- /dev/null +++ b/app/assets/stylesheets/components/select.scss @@ -0,0 +1,60 @@ +.ts-wrapper{ + display: flex; +} + +.ts-control { + padding: 12px; + border-radius: 5px; +} + +.ts-dropdown-content .option { + padding: 12px; +} + +.ts-dropdown .active { + background-color: #f8f8f8; +} + +.ts-dropdown { + flex: 1 1 auto; + min-width: 9rem; + margin: 0; + color: #666666; +} + + +.ts-wrapper.single .ts-control:after { + border-color: #343a40 transparent transparent; + border-style: solid; + border-width: 5px 5px 0; + content: " "; + display: block; + height: 0; + margin-top: -3px; + position: absolute; + right: calc(0.5rem); + top: 50%; + width: 0; +} + +.ts-wrapper.multi .ts-control>div{ + border-radius: 5px; + font-size: 11px; + padding: 2px 0 4px 6px; + color: #888888; +} + +.ts-wrapper.plugin-remove_button:not(.rtl) .item .remove{ + border-left: none; + color: #888888 !important; + margin-left: 0; +} + +.ts-wrapper.plugin-remove_button .item .remove:hover { + background: unset; +} + +.has-items .ts-control > input { + width: unset !important; +} + diff --git a/app/assets/stylesheets/login.scss b/app/assets/stylesheets/login.scss index 342c754eb..1b0c72489 100644 --- a/app/assets/stylesheets/login.scss +++ b/app/assets/stylesheets/login.scss @@ -51,4 +51,11 @@ margin-top: 10px; margin-bottom: 20px; width: 357px; +} +.disabled-login-sso{ + width: 40px; + margin: 7px 10px; +} +.login-active-sso path{ + fill: var(--primary-color) } \ No newline at end of file diff --git a/app/assets/stylesheets/nav_bar.scss b/app/assets/stylesheets/nav_bar.scss index fdc92676e..427389b82 100644 --- a/app/assets/stylesheets/nav_bar.scss +++ b/app/assets/stylesheets/nav_bar.scss @@ -96,15 +96,21 @@ } -.nav-language{ - background-color: transparent; - width: 47px; - color: white; - border: none; - outline: none; - cursor: pointer; +.nav-language { + width: 47px !important; + .ts-control{ + background-color: transparent; + color: white; + border: none; + outline: none; + cursor: pointer; + } + &.single.input-active .ts-control{ + background: transparent !important; + } } + .nav-language option{ background-color: white; color: black; diff --git a/app/assets/stylesheets/ontologies.scss b/app/assets/stylesheets/ontologies.scss index 494655a31..c5acad290 100644 --- a/app/assets/stylesheets/ontologies.scss +++ b/app/assets/stylesheets/ontologies.scss @@ -284,3 +284,12 @@ $widget-table-border-color: #EFEFEF; flex-direction: row; } + +#select_content_language{ + & ~ .ts-wrapper .ts-control{ + padding: 6px 8px !important; + .item{ + margin-right: 15px; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/summary.scss.erb b/app/assets/stylesheets/summary.scss.erb index 7773b9e56..d53e734f9 100644 --- a/app/assets/stylesheets/summary.scss.erb +++ b/app/assets/stylesheets/summary.scss.erb @@ -212,5 +212,3 @@ div.vis-network div.vis-navigation { background-image: none !important; } } - - diff --git a/app/assets/stylesheets/upload_ontology.scss b/app/assets/stylesheets/upload_ontology.scss index 978d3997d..85f18aec8 100644 --- a/app/assets/stylesheets/upload_ontology.scss +++ b/app/assets/stylesheets/upload_ontology.scss @@ -21,20 +21,6 @@ flex-direction: column; } -.Upload-ontology-title { - font-size: 18px; - display: flex; - font-weight: bold; - flex-direction: column; - align-items: center; -} - -.Upload-ontology-title hr { - border: 1px solid var(--primary-color); - width: 93px; - margin: 0; -} - .upload-ontology-progress { display: flex; align-items: center; diff --git a/app/components/chip_button_component/chip_button_component.html.haml b/app/components/chip_button_component/chip_button_component.html.haml index c5c76f4a7..98b44adc3 100644 --- a/app/components/chip_button_component/chip_button_component.html.haml +++ b/app/components/chip_button_component/chip_button_component.html.haml @@ -1,6 +1,9 @@ - if @type == "static" %span.chip_button_container{@html_options} = @text || content +- elsif !content + %a.chip_button_container_clickable{@html_options} + = @text - else %span.chip_button_container_clickable{@html_options} - = @text || content + = content diff --git a/app/components/display/alert_component.rb b/app/components/display/alert_component.rb index c381f2a37..6035f36f2 100644 --- a/app/components/display/alert_component.rb +++ b/app/components/display/alert_component.rb @@ -1,9 +1,10 @@ class Display::AlertComponent < ViewComponent::Base - def initialize(message: nil, closable: true, type: "info", auto_close_delay: nil) + def initialize(message: nil, closable: true, type: "info", auto_close_delay: nil, button: nil) @message = message @closable = closable @type = type @auto_close_delay = auto_close_delay + @button = button end def closable? @@ -44,4 +45,7 @@ def auto_close? !@auto_close_delay.nil? end + def button + @button + end end \ No newline at end of file diff --git a/app/components/display/alert_component/alert_component.html.haml b/app/components/display/alert_component/alert_component.html.haml index 66541be94..63a4aa5b3 100644 --- a/app/components/display/alert_component/alert_component.html.haml +++ b/app/components/display/alert_component/alert_component.html.haml @@ -7,6 +7,9 @@ = inline_svg_tag alert_icon .alert-message{class: alert_type_class} = message || content + - if button + .alert-button + = render button - if closable? .alert-close{"data-action":"click->alert-component#close", class: alert_type_class} = inline_svg_tag "x.svg" diff --git a/app/components/dropdown_section_button_component.rb b/app/components/dropdown_section_button_component.rb index c46074f75..069e37e40 100644 --- a/app/components/dropdown_section_button_component.rb +++ b/app/components/dropdown_section_button_component.rb @@ -5,9 +5,10 @@ class DropdownSectionButtonComponent < ViewComponent::Base renders_one :header renders_many :items - def initialize(divide: true) + def initialize(divide: true, selected_index: nil) super @divide = divide + @selected = selected_index end def show_divider? diff --git a/app/components/dropdown_section_button_component/dropdown_section_button_component.html.haml b/app/components/dropdown_section_button_component/dropdown_section_button_component.html.haml index 3070f75de..0f829c820 100644 --- a/app/components/dropdown_section_button_component/dropdown_section_button_component.html.haml +++ b/app/components/dropdown_section_button_component/dropdown_section_button_component.html.haml @@ -2,6 +2,6 @@ %div.dropdown-divider - if header? %h6.dropdown-header= header -- items.each do |i| - %span.dropdown-item - = i \ No newline at end of file +- items.each_with_index do |text, index| + %span.dropdown-item{class: index.eql?(@selected) ? 'dropdown-item-selected' : ''} + = text \ No newline at end of file diff --git a/app/components/input/language_selector_component.rb b/app/components/input/language_selector_component.rb index 3a7ec435c..ae5a391d3 100644 --- a/app/components/input/language_selector_component.rb +++ b/app/components/input/language_selector_component.rb @@ -2,25 +2,34 @@ class Input::LanguageSelectorComponent < ViewComponent::Base - def initialize(languages:, selected: nil, id: '', name: '' ) + def initialize(languages:, selected: nil, id: '', name: '' , enable_all: false, **html_options) super @languages = languages @id = id - @name = languages + @name = name @selected = selected + @data = html_options[:data] || {} + @enable_all = enable_all + @html_options = html_options end - def languages_options - values = [['All languages', 'all']] + def languages + values = [] + values = [["
#{render(LanguageFieldComponent.new(label: 'All languages', icon: 'icons/earth.svg', value: 'en'))}
", 'all']] if @enable_all @languages.each do |key, label| - option = "
#{render(LanguageFieldComponent.new(value: key.to_s.downcase, label: label))}
" + option = "
#{render(LanguageFieldComponent.new(value: key.to_s.downcase, label: label, auto_label: true))}
" values += [[option, key.to_s.downcase]] end values end def call - render SelectInputComponent.new(id: @id, name: @name, values: languages_options, selected: @selected, placeholder: 'Select a language') + render SelectInputComponent.new(id: @id, name: @name, values: languages, + selected: @selected, + data: @data, + required: true, + open_to_add_values: false, + placeholder: 'Select a language', **@html_options) end end diff --git a/app/components/input/select_component/select_component.html.haml b/app/components/input/select_component/select_component.html.haml deleted file mode 100644 index 242f79405..000000000 --- a/app/components/input/select_component/select_component.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -= render Input::InputFieldComponent.new(name: @name, error_message: @error_message, helper_text: @helper_text, label: @label) do - = render SelectInputComponent.new(id: @id, name: @name, values: @values , selected: @selected , multiple: @multiple, open_to_add_values: @open_to_add_values ) \ No newline at end of file diff --git a/app/components/language_field_component.rb b/app/components/language_field_component.rb index 2473e7b1c..7998432a9 100644 --- a/app/components/language_field_component.rb +++ b/app/components/language_field_component.rb @@ -5,15 +5,35 @@ class LanguageFieldComponent < ViewComponent::Base include FlagIconsRails::Rails::ViewHelpers - def initialize(value:, label: nil) + def initialize(value:, label: nil, auto_label: false, icon: nil) super @value = value - @lang_code = value&.is_a?(String) ? ISO_639.find(value.split('/').last)&.alpha2 : nil + @lang_code = nil @label = label + @icon = icon + + iso = ISO_639.find(value.to_s.split('/').last) + if iso + @lang_code = iso.alpha2 + @label ||= iso.english_name if auto_label + end end def lang_code - @lang_code = 'gb' if @lang_code.eql?('en') + case @lang_code + when 'en' + @lang_code = 'gb' + when 'ar' + @lang_code = 'sa' + when 'hi' + @lang_code = 'in' + when 'ur' + @lang_code = 'pk' + when 'zh' + @lang_code = 'cn' + when 'ja' + @lang_code = 'jp' + end @lang_code end diff --git a/app/components/language_field_component/language_field_component.html.haml b/app/components/language_field_component/language_field_component.html.haml index 8530fae3d..66c514f38 100644 --- a/app/components/language_field_component/language_field_component.html.haml +++ b/app/components/language_field_component/language_field_component.html.haml @@ -1,6 +1,6 @@ - if @lang_code .d-flex.align-items-center - = flag_icon(lang_code) + = @icon ? inline_svg_tag(@icon, width: "17px", height: "17px") : flag_icon(lang_code) - if @label %div.ml-1 = @label diff --git a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml index c9fe2ee50..55746eee3 100644 --- a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml +++ b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml @@ -42,7 +42,7 @@ - if ontology[:creationDate] %span.mr-1 = render ChipButtonComponent.new(type: "clickable") do - %span.mr-1 Uploaded + %span.mr-1 Submitted %span.browse-uploaded-date{data:{controller: 'timeago', 'timeago-datetime-value': ontology[:creationDate], 'timeago-add-suffix-value': 'true'}} - if ontology[:contact] %span.mx-1 by @@ -51,7 +51,7 @@ - if ontology[:released] - date = render DateTimeFieldComponent.new(value: ontology[:released]) - %span{data:{controller:'tooltip'}, title: "Release date #{date}"} + %span{data:{controller:'tooltip'}, title: "Creation date #{date}"} = render ChipButtonComponent.new(type: "clickable") do = DateTime.parse(date).year rescue date diff --git a/app/components/ontology_subscribe_button_component.rb b/app/components/ontology_subscribe_button_component.rb index 8b87a38b2..cff082694 100644 --- a/app/components/ontology_subscribe_button_component.rb +++ b/app/components/ontology_subscribe_button_component.rb @@ -17,10 +17,8 @@ def initialize(ontology_id:, subscribed:, user_id:, count: 0, link: 'javascript: }, title: title } - @link = link - @count = count end - + def title if @subscribed "#{@sub_text} this resource" diff --git a/app/components/select_input_component.rb b/app/components/select_input_component.rb index 59bc95d50..29dc41c7a 100644 --- a/app/components/select_input_component.rb +++ b/app/components/select_input_component.rb @@ -2,7 +2,7 @@ class SelectInputComponent < ViewComponent::Base - def initialize(id:, name:, values:, selected: nil, multiple: false, open_to_add_values: false, required: false, data: {}, placeholder: '') + def initialize(id:, name:, values:, selected: nil, multiple: false, open_to_add_values: false, required: false, data: {}, placeholder: '', **html_options) super @id = id || '' @name = name @@ -13,10 +13,7 @@ def initialize(id:, name:, values:, selected: nil, multiple: false, open_to_add_ @placeholder = placeholder @data = data @required = required - end - - def call - select_input_tag(@id, @values, @selected, multiple: @multiple, open_to_add_values: @open_to_add_values) + @html_options = html_options end def call @@ -43,8 +40,11 @@ def select_input_tag(id, name, values, selected, options = {}) placeholder: placeholder, autocomplete: 'off', multiple: multiple, - data: data - } + data: data, + }.merge(@html_options) + + select_html_options[:style] = "#{select_html_options[:style]}; visibility: hidden" + select_tag(name, options_for_select(values, selected), select_html_options) end diff --git a/app/components/select_input_component/select_input_component_controller.js b/app/components/select_input_component/select_input_component_controller.js index 1ec8b6499..c17b84c1f 100644 --- a/app/components/select_input_component/select_input_component_controller.js +++ b/app/components/select_input_component/select_input_component_controller.js @@ -1,48 +1,85 @@ -import {Controller} from "@hotwired/stimulus" -import {useTomSelect} from "../../javascript/mixins/useTomSelect" - -export default class extends Controller { - static values = { - multiple: {type: Boolean, default: false}, - openAdd: {type: Boolean, default: false}, - required: {type: Boolean, default: false} - }; - - - connect() { - let myOptions = {} - - myOptions = { - render: { - option: (data) => { - return `
${data.text}
` - }, - item: (data) => { - return `
${data.text}
` - } - } - } +import { Controller } from '@hotwired/stimulus' +import { useTomSelect } from '../../javascript/mixins/useTomSelect' - if (this.multipleValue) { - myOptions['onItemAdd'] = function(){ - this.setTextboxValue(''); - this.refreshOptions(); - } - myOptions['plugins'] = ['remove_button']; - } +export default class SelectInput extends Controller { + static DISPLAY_VALUE = 'value' + static DISPLAY_TEXT = 'text' - if (this.openAddValue) { - myOptions['create'] = true; + static values = { + multiple: { type: Boolean, default: false }, + openAdd: { type: Boolean, default: false }, + required: { type: Boolean, default: false }, + searchable: { type: Boolean, default: true }, + displayField: { type: String, default: SelectInput.DISPLAY_TEXT } + } + + connect () { + let myOptions = {} + myOptions = { + render: { + option: (data) => { + return `
${data.text}
` + }, + item: (data) => { + if (this.displayFieldValue === SelectInput.DISPLAY_TEXT) { + return `
${data.text}
` + } else { + return `
${data.value}
` + } } + } + } + + if(!this.searchableValue){ + myOptions['controlInput'] = null + } + + if (this.multipleValue) { + myOptions['onItemAdd'] = function () { + this.setTextboxValue('') + this.refreshOptions() + } + myOptions['plugins'] = ['remove_button'] + } - this.select = useTomSelect(this.element, myOptions, this.#triggerChange.bind(this)) + if (this.openAddValue) { + myOptions['create'] = true } - #triggerChange() { - if (this.requiredValue && !this.multipleValue && this.select.getValue() === ""){ - this.select.setValue(Object.keys(this.select.options)[0]) + this.select = useTomSelect(this.element, myOptions, this.#triggerChange.bind(this)) + this.element.style.visibilty = 'hidden'; + + [...this.element.attributes].forEach(attribute => { + if(attribute.name !== 'class' && attribute.name !== 'style' && attribute.name !== 'id' && attribute.name !== 'name'){ + this.select.control.setAttribute(attribute.name, attribute.value.replace('select-input', '')) + if(attribute.name === 'title'){ + this.select.control.setAttribute('data-controller', 'tooltip') } + } + }) + } - document.dispatchEvent(new Event('change', { target: this.element })) + #triggerChange () { + if (this.#isRequired() && !this.#isMultiple() && this.#isEmpty()) { + this.#selectFirstItem() } + + document.dispatchEvent(new Event('change', { target: this.element })) + } + + #isRequired () { + return this.requiredValue + } + + #isMultiple () { + return this.multipleValue + } + + #isEmpty () { + return this.select.getValue() === '' + } + + #selectFirstItem () { + this.select.setValue(Object.keys(this.select.options)[0], true) + } } \ No newline at end of file diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 90af90d25..1731b7b08 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -9,7 +9,7 @@ class AdminController < ApplicationController :$DEBUG_BEFORE, :$stdout, :$-0, :$-l, :$-I, :$DEBUG, :$', :$gems_rake_task, :$_, :$CODERAY_DEBUG, :$-F, :$", :$0, :$=, :$FILENAME, :$?, :$!, :$rdebug_in_irb, :$-K, :$TESTING, :$fileutils_rb_have_lchmod, - :$EMAIL_EXCEPTIONS, :$binding, :$-v, :$>, :$SAFE, :$/, + :$binding, :$-v, :$>, :$SAFE, :$/, :$fileutils_rb_have_lchown, :$-p, :$-W, :$:, :$__dbg_interface, :$stderr, :$\, :$&, :$<, :$debug, :$;, :$~, :$-a, :$DEBUG_RDOC, :$CGI_ENV, :$LOAD_PATH, :$-d, :$*, :$., :$-w, :$+, diff --git a/app/controllers/agents_controller.rb b/app/controllers/agents_controller.rb new file mode 100644 index 000000000..a0d8ef493 --- /dev/null +++ b/app/controllers/agents_controller.rb @@ -0,0 +1,290 @@ +class AgentsController < ApplicationController + include TurboHelper, AgentHelper + before_action :authorize_and_redirect, :only => [:edit, :update, :create, :new] + + def index + @agents = LinkedData::Client::Models::Agent.all(include: 'all') + end + + def show + # we use :agent_id not :id + @agent = LinkedData::Client::Models::Agent.find(params[:agent_id]) + not_found("Agent with id #{params[:agent_id]}") if @agent.nil? + + @agent_id = params[:id] || agent_id(@agent) + @name_prefix = params[:name_prefix] + @edit_on_modal = params[:edit_on_modal]&.eql?('true') + @deletable = params[:deletable]&.eql?('true') + end + + def ajax_agents + filters = { name: params[:name] } + filters[:agentType] = params[:agent_type] if params[:agent_type] + @agents = LinkedData::Client::Models::Agent.all(filters) + agents_json = @agents.map do |x| + { + id: x.id, + name: x.name, + type: x.agentType, + identifiers: x.identifiers.map { |i| "#{i.schemaAgency}:#{i.notation}" }.join(', ') + } + end + + render json: agents_json + end + + def new + @agent = LinkedData::Client::Models::Agent.new + @agent.id = params[:id] + @agent.creator = session[:user].id + @agent.agentType = params[:type] || 'person' + @agent.name = params[:name] + @name_prefix = params[:name_prefix] || '' + @show_affiliations = params[:show_affiliations].nil? || params[:show_affiliations]&.eql?('true') + @deletable = params[:deletable]&.eql?('true') + end + + def create + new_agent = save_agent(agent_params) + parent_id = params[:parent_id] + name_prefix = params[:name_prefix] + alert_id = agent_id_alert_container_id(params[:id], parent_id) + deletable = params[:deletable]&.eql?('true') + if new_agent.errors + render_turbo_stream alert_error(id: alert_id) { JSON.pretty_generate(response_errors(new_agent)) } + else + success_message = 'New Agent added successfully' + streams = [alert_success(id: alert_id) { success_message }] + + streams << prepend('agents_table_content', partial: 'agents/show_line', locals: { agent: new_agent }) + streams << replace_agent_form(new_agent, agent_id: nil, frame_id: params[:id], + parent_id: parent_id, name_prefix: name_prefix, + deletable: deletable + ) if params[:parent_id] + + render_turbo_stream(*streams) + end + end + + def edit + @agent = LinkedData::Client::Models::Agent.find("#{rest_url}/Agents/#{params[:id]}") + @name_prefix = params[:name_prefix] || '' + @show_affiliations = params[:show_affiliations].nil? || params[:show_affiliations].eql?('true') + @deletable = params[:deletable].to_s.eql?('true') + end + + def show_search + id = params[:id] + parent_id = params[:parent_id] + name_prefix = params[:name_prefix] + agent_type = params[:agent_type] + agent_deletable = params[:deletable].to_s.eql?('true') + + attribute_template_output = helpers.agent_search_input(id, agent_type, + parent_id: parent_id, + name_prefix: name_prefix, + deletable: agent_deletable) + render_turbo_stream(replace(helpers.agent_id_frame_id(id, parent_id)) { render_to_string(inline: attribute_template_output) } ) + + end + + def update + agent_update, agent = update_agent(params[:id].split('/').last, agent_params) + + parent_id = params[:parent_id] + alert_id = agent_alert_container_id(agent, parent_id) + deletable = params[:deletable]&.eql?('true') + if response_error?(agent_update) + render_turbo_stream(alert_error(id: alert_id) { JSON.pretty_generate(response_errors(agent_update)) }) + else + success_message = 'Agent successfully updated' + table_line_id = agent_table_line_id(agent_id(agent)) + agent = LinkedData::Client::Models::Agent.find(agent.id) + streams = [alert_success(id: alert_id) { success_message }, + replace(table_line_id, partial: 'agents/show_line', locals: { agent: agent }) + ] + + streams << replace_agent_form(agent, agent_id: agent_id(agent.id), name_prefix: params[:name_prefix] , parent_id: parent_id, deletable: deletable) if params[:parent_id] + + render_turbo_stream(*streams) + end + end + + def agent_usages + @agent = find_agent_display_all + @ontology_acronyms = LinkedData::Client::Models::Ontology.all(include: 'acronym', display_links: false, display_context: false, include_views: true).map(&:acronym) + not_found("Agent with id #{@agent.id}") if @agent.nil? + render partial: 'agents/agent_usage' + end + + def update_agent_usages + agent = find_agent_display_all + responses, new_usages = update_agent_usages_action(agent, agent_usages_params) + parent_id = params[:parent_id] + alert_id = agent_alert_container_id(agent, parent_id) + + + if responses.values.any? { |x| response_error?(x) } + errors = {} + responses.each do |ont, response| + errors[ont.acronym] = response_errors(response) if response_error?(response) + end + + render_turbo_stream(alert_error(id: alert_id) { helpers.agent_usage_errors_display(errors) }) + else + + success_message = 'Agent usages successfully updated' + table_line_id = agent_table_line_id(agent_id(agent)) + agent.usages = new_usages + streams = [alert_success(id: alert_id) { success_message }, + replace(table_line_id, partial: 'agents/show_line', locals: { agent: agent }) + ] + + render_turbo_stream(*streams) + end + + end + + def destroy + error = nil + @agent = LinkedData::Client::Models::Agent.find("#{rest_url}/Agents/#{params[:id]}") + success_text = '' + + if @agent.nil? + success_text = "Agent #{params[:id]} already deleted" + else + error_response = @agent.delete + + if response_error?(error_response) + error = response_errors(error_response) + else + success_text = "Agent #{params[:id]} deleted successfully" + end + end + + respond_to do |format| + format.turbo_stream do + if error.nil? + render turbo_stream: [ + alert(type: 'success') { success_text }, + turbo_stream.remove(agent_table_line_id(params[:id])) + ] + + else + render alert(type: 'danger') { error } + end + end + format.html { render json: { success: success_text, error: error } } + end + + end + + private + + def replace_agent_form(agent, agent_id: nil, frame_id: nil, parent_id:, partial: 'agents/agent_show', name_prefix: '', deletable: true) + + frame_id = frame_id ? agent_id_frame_id(frame_id, parent_id) : agent_frame_id(agent, parent_id) + + replace(frame_id, partial: partial, layout: false , + locals: { agent_id: agent_id, agent: agent, name_prefix: name_prefix, parent_id: parent_id, + edit_on_modal: false, + deletable: deletable}) + end + + def save_agent(params) + agent = LinkedData::Client::Models::Agent.new(values: params) + agent.creator = session[:user].id + agent.save + end + + def update_agent(id = params[:id], params) + agent = LinkedData::Client::Models::Agent.find("#{rest_url}/Agents/#{id}") + + params[:creator] = session[:user].id if (agent.creator.nil? || agent.creator.empty?) && (params[:creator] || '').empty? + + res = agent.update(values: params) + [res, agent.update_from_params(params)] + end + + def update_agent_usages_action(agent, params) + current_usages = helpers.agents_used_properties(agent) + new_usages = params + + diffs = current_usages.keys.each_with_object({}) do |key, result| + removed_values = current_usages[key] - Array(new_usages[key]) + added_values = Array(new_usages[key]) - current_usages[key] + result[key] = removed_values + added_values + end + + # changed_usages = new_usages.empty? ? current_usages : new_usages.select { |x, v| !((current_usages[x] - v) + (v - current_usages[x])).empty? } + + + changed_usages = diffs.reduce({}) do |h, attr_acronyms| + attr, acronyms = attr_acronyms + acronyms.each do |acronym| + h[acronym] ||= [] + h[acronym] << attr + end + h + end + responses = {} + changed_usages.each do |ontology, attrs| + ontology = LinkedData::Client::Models::Ontology.find_by_acronym(ontology).first + sub = ontology.explore.latest_submission({ include: attrs.join(',') }) + values = {} + attrs.each do |attr| + current_val = sub.send(attr) + if current_val.is_a?(Array) + existent_agent = current_val.find_index { |x| x.id.eql?(agent.id) } + if existent_agent + current_val.delete_at(existent_agent) + else + current_val << agent + end + values[attr.to_sym] = current_val.map { |x| x.id } + else + values[attr.to_sym] = agent + end + end + + responses[ontology] = sub.update(values: values, cache_refresh_all: false) + end + + [responses, new_usages] + end + + def agent_usages_params + p = params.permit(hasCreator: [], hasContributor: [], curatedBy: [], publisher: [], fundedBy: [], endorsedBy: [], translator: []) + p.to_h + end + + def agent_params + p = params.permit(:agentType, :name, :email, :acronym, :homepage, :creator, + { identifiers: [:notation, :schemaAgency, :creator] }, + { affiliations: [:id, :agentType, :name, :homepage, :acronym, :creator, { identifiers: [:notation, :schemaAgency, :creator] }] } + ) + p = p.to_h + p.transform_values do |v| + if v.is_a? Hash + v.values.reject(&:empty?) + elsif v.is_a? Array + v.reject(&:empty?) + else + v + end + end + p[:identifiers] = (p[:identifiers] || {}).values + p[:affiliations] = (p[:affiliations] || {}).values + p[:affiliations].each do |affiliation| + affiliation[:identifiers] = affiliation[:identifiers].values if affiliation.is_a?(Hash) && affiliation[:identifiers] + end + p + end + + def find_agent_display_all(id = params[:id]) + # TODO fix in the api client, the find with params + LinkedData::Client::Models::Agent.where({ display: 'all' }) do |obj| + obj.id.to_s.eql?("#{rest_url}/Agents/#{id}") + end.first + end +end diff --git a/app/controllers/annotator_controller.rb b/app/controllers/annotator_controller.rb index 0e4e53fdd..ae05a3a31 100644 --- a/app/controllers/annotator_controller.rb +++ b/app/controllers/annotator_controller.rb @@ -29,6 +29,7 @@ def index @recognizers = [] end @annotator_ontologies = LinkedData::Client::Models::Ontology.all + @text = params[:text] end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7e1174082..a546c5f67 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -13,10 +13,38 @@ # Likewise, all the methods added will be available for all controllers. class ApplicationController < ActionController::Base + + before_action :set_locale + + # Sets the locale based on the locale cookie or the value returned by detect_locale. + def set_locale + I18n.locale = cookies[:locale] || detect_locale + cookies.permanent[:locale] = I18n.locale if cookies[:locale].nil? + logger.debug "* Locale set to '#{I18n.locale}'" + session[:locale] = I18n.locale + end + + # Returns detedted locale based on the Accept-Language header of the request or the default locale if none is found. + def detect_locale + languages = request.headers['Accept-Language']&.split(',') + supported_languages = I18n.available_locales + + Array(languages).each do |language| + language_code = language.split(/[-;]/).first.downcase.to_sym + return language_code if supported_languages.include?(language_code) + end + + return I18n.default_locale + end + + helper :all # include all helpers, all the time helper_method :bp_config_json, :current_license, :using_captcha? - rescue_from ActiveRecord::RecordNotFound, with: :not_found_record - rescue_from StandardError, with: :internal_server_error + + unless Rails.env.development? || Rails.env.test? + rescue_from ActiveRecord::RecordNotFound, with: :not_found_record + rescue_from StandardError, with: :internal_server_error + end # Pull configuration parameters for REST connection. REST_URI = $REST_URL @@ -54,9 +82,6 @@ class ApplicationController < ActionController::Base $trial_license_initialized = false - if !$EMAIL_EXCEPTIONS.nil? && $EMAIL_EXCEPTIONS == true - include ExceptionNotifiable - end # See ActionController::RequestForgeryProtection for details protect_from_forgery @@ -106,10 +131,7 @@ def domain_ontology_set Thread.current[:slice] = @subdomain_filter end - def anonymous_user - user = DataAccess.getUser($ANONYMOUS_USER) - user ||= User.new({"id" => 0}) - end + def ontology_not_found(ontology_acronym) not_found("Ontology #{ontology_acronym} not found") @@ -184,6 +206,20 @@ def remote_file_exists?(url) check end + 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}" + end + + def check_http_file(url) session = Net::HTTP.new(url.host, url.port) session.use_ssl = true if url.port == 443 @@ -427,6 +463,8 @@ def using_captcha? def get_class(params) + lang = request_lang + if @ontology.flat? ignore_concept_param = params[:conceptid].nil? || @@ -443,7 +481,7 @@ def get_class(params) @concept.children = [] else # Display only the requested class in the tree - @concept = @ontology.explore.single_class({full: true}, params[:conceptid]) + @concept = @ontology.explore.single_class({full: true, lang: lang }, params[:conceptid]) @concept.children = [] end @root = LinkedData::Client::Models::Class.new @@ -452,6 +490,7 @@ def get_class(params) else # not ignoring 'bp_fake_root' here + include = 'prefLabel,hasChildren,obsolete' ignore_concept_param = params[:conceptid].nil? || params[:conceptid].empty? || params[:conceptid].eql?("root") @@ -461,7 +500,8 @@ def get_class(params) @roots = @ontology.explore.roots(concept_schemes: params[:concept_schemes]) if @roots.nil? || @roots.empty? LOG.add :debug, "Missing @roots for #{@ontology.acronym}" - @concept = @ontology.explore.classes.collection.first.explore.self(full: true) + classes = @ontology.explore.classes.collection + @concept = classes.first.explore.self(full: true) if classes.first return end @@ -471,22 +511,22 @@ def get_class(params) # get the initial concept to display root_child = @root.children.first - @concept = root_child.explore.self(full: true) - # Some ontologies have "too many children" at their root. These will not process and are handled here. - if @concept.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. + if @concept.nil? LOG.add :debug, "Missing class #{root_child.links.self}" not_found("Missing class #{root_child.links.self}") end else # if the id is coming from a param, use that to get concept - @concept = @ontology.explore.single_class({full: true}, params[:conceptid]) + @concept = @ontology.explore.single_class({full: true, lang: lang}, params[:conceptid]) if @concept.nil? || @concept.errors LOG.add :debug, "Missing class #{@ontology.acronym} / #{params[:conceptid]}" not_found("Missing class #{@ontology.acronym} / #{params[:conceptid]}") end # Create the tree - rootNode = @concept.explore.tree(concept_schemes: params[:concept_schemes]) + 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? @@ -508,13 +548,6 @@ def get_class(params) @concept 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 get_ontology_submission_ready(ontology) # Get the latest 'ready' submission @@ -765,9 +798,13 @@ def submission_metadata end helper_method :submission_metadata + def request_lang + helpers.request_lang + end private def not_found_record(exception) @error_message = exception.message + render 'errors/not_found', status: 404 end @@ -775,9 +812,8 @@ def internal_server_error(exception) current_user = session[:user] if defined?(session) request_ip = request.remote_ip if defined?(request) current_url = request.original_url if defined?(request) - + Notifier.error(exception, current_user, request_ip, current_url).deliver_now - render 'errors/internal_server_error', status: 500 end end diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb index 773b7361f..ad125dd17 100644 --- a/app/controllers/collections_controller.rb +++ b/app/controllers/collections_controller.rb @@ -10,7 +10,7 @@ def show_label collection_label = collection['prefLabel'] if collection collection_label = params[:id] if collection_label.nil? || collection_label.empty? - render LabelLinkComponent.inline(params[:id], collection_label) + render LabelLinkComponent.inline(params[:id], helpers.main_language_label(collection_label)) end def show_members @@ -18,7 +18,7 @@ def show_members @collection = get_request_collection page = params[:page] || '1' @auto_click = page.to_s.eql?('1') - @page = @collection.explore.members({page: page}) + @page = @collection.explore.members({page: page, language: request_lang}) @concepts = @page.collection if @ontology.nil? ontology_not_found params[:ontology] diff --git a/app/controllers/concepts_controller.rb b/app/controllers/concepts_controller.rb index 35950c375..6cd525453 100644 --- a/app/controllers/concepts_controller.rb +++ b/app/controllers/concepts_controller.rb @@ -19,7 +19,7 @@ def show_concept @submission = get_ontology_submission_ready(@ontology) @ob_instructions = helpers.ontolobridge_instructions_template(@ontology) - @concept = @ontology.explore.single_class({full: true}, params[:id]) + @concept = @ontology.explore.single_class({full: true, language: request_lang}, params[:id]) @instances_concept_id = @concept.id concept_not_found(params[:id]) if @concept.nil? @@ -53,9 +53,8 @@ def show @concept = @ontology.explore.single_class({full: true}, params[:id]) concept_not_found(params[:id]) if @concept.nil? - - show_uri_request # process a full call - render :file => '/ontologies/visualize', :use_full_path => true, :layout => 'ontology' + @schemes = params[:concept_schemes].split(',') + show_ajax_request # process a full call end end @@ -70,7 +69,7 @@ def show_label return end - render LabelLinkComponent.inline(cls_id, concept_label(ont_id, cls_id)) + render LabelLinkComponent.inline(cls_id, helpers.main_language_label(concept_label(ont_id, cls_id))) end def show_definition @@ -113,7 +112,8 @@ def show_date_sorted_list page: page, sortby:'modified,created', order:'desc,desc', - display: 'prefLabel,modified,created' + display: 'prefLabel,modified,created', + language: request_lang } if @last_date params.merge!(last_date: @last_date) @@ -189,7 +189,7 @@ def show_ajax_request gather_details render :partial => 'load' when 'children' # Children is called only for drawing the tree - @children = @concept.explore.children(pagesize: 750, concept_schemes: params[:concept_schemes]).collection || [] + @children = @concept.explore.children(pagesize: 750, concept_schemes: Array(@schemes).join(','), language: request_lang, display: 'prefLabel,obsolete,hasChildren').collection || [] @children.sort! { |x, y| (x.prefLabel || "").downcase <=> (y.prefLabel || "").downcase } unless @children.empty? render :partial => 'child_nodes' end diff --git a/app/controllers/concerns/ontology_updater.rb b/app/controllers/concerns/ontology_updater.rb new file mode 100644 index 000000000..966736aa3 --- /dev/null +++ b/app/controllers/concerns/ontology_updater.rb @@ -0,0 +1,106 @@ +module OntologyUpdater + extend ActiveSupport::Concern + include SubmissionUpdater + include TurboHelper + + def update_existent_ontology(acronym) + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(acronym).first + return nil if @ontology.nil? + + new_values = ontology_params + new_values.each do |key, values| + @ontology.send("#{key}=", values) + rescue StandardError + next + end + [@ontology, @ontology.update(values: new_values)] + end + + def ontology_from_params + ontology = LinkedData::Client::Models::Ontology.new(values: ontology_params) + ontology.viewOf = nil unless ontology.isView + ontology + end + + def ontology_params + return {} unless params[:ontology] + + p = params.require(:ontology).permit(:name, :acronym, { administeredBy: [] }, :viewingRestriction, { acl: [] }, + { hasDomain: [] }, :viewOf, :isView, :subscribe_notifications, { group: [] }) + + p[:administeredBy].reject!(&:blank?) if p[:administeredBy] + # p[:acl].reject!(&:blank?) + p[:hasDomain].reject!(&:blank?) if p[:hasDomain] + p[:group].reject!(&:blank?) if p[:group] + p[:viewOf] = '' if p.key?(:viewOf) && !p.key?(:isView) + p.to_h + end + + def show_new_errors(object, redirection = 'ontologies/new') + # TODO optimize + @ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym', include_views: true, display_links: false, display_context: false) + @categories = LinkedData::Client::Models::Category.all + @groups = LinkedData::Client::Models::Group.all(display_links: false, display_context: false) + @user_select_list = LinkedData::Client::Models::User.all.map { |u| [u.username, u.id] } + @user_select_list.sort! { |a, b| a[1].downcase <=> b[1].downcase } + @errors = response_errors(object) + @selected_attributes = (Array(errors_attributes) + Array(params[:submission]&.keys)).uniq + @ontology = ontology_from_params if @ontology.nil? + + @submission = submission_from_params(params[:submission]) if params[:submission] && @submission.nil? + reset_agent_attributes + if redirection.is_a?(Hash) && redirection[:id] + render_turbo_stream replace(redirection[:id], partial: redirection[:partial]) + else + render redirection, status: 422 + end + end + + def errors_attributes + @errors = @errors[:error] if @errors && @errors[:error] + @errors.keys.map(&:to_s) if @errors.is_a?(Hash) + end + + def new_submission_hash(ontology, submission = nil) + @submission = submission + new_submission_params = submission_params(params[:submission]) + + if @submission + old_submission_values = @submission.to_hash.delete_if { |k, v| !copyable_submission_params?(k, v)} + new_submission_params = ActionController::Parameters.new(old_submission_values.merge(new_submission_params)) + new_submission_params = submission_params(new_submission_params) + end + + new_submission_params.delete 'submissionId' + new_submission_params[:ontology] = ontology.acronym + ActionController::Parameters.new(new_submission_params) + end + + def update_submission_hash(acronym) + submission_params = submission_params(params[:submission]) + submission_params[:ontology] = acronym + submission_params + end + + private + def reset_agent_attributes + helpers.agent_attributes.each do |attr| + current_val = @submission[attr] + new_values = Array(current_val).map { |x| LinkedData::Client::Models::Agent.find(x) } + + new_values = new_values.first unless current_val.is_a?(Array) + + @submission[attr] = new_values + end + end + + 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, + :submissionStatus] + + !attr_to_not_copy.include?(key.to_sym) + end +end diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index c97434847..1c0ecedf0 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -10,23 +10,34 @@ 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] + @selected_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: params[:page] || 1) + request_params = filters_params(params, page: nil) init_filters(params) - @page = LinkedData::Client::Models::OntologySubmission.all(request_params) - - submissions = @page.collection - - # analytics = LinkedData::Client::Analytics.last_month - # @analytics = Hash[analytics.onts.map { |o| [o[:ont].to_s, o[:views]] }] + # 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 # get fair scores of all ontologies @fair_scores = fairness_service_enabled? ? get_fair_score('all') : nil - submissions.reject{|sub| sub.ontology.nil?}.map { |sub| ontology_hash(sub) } + 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] } + end + submissions end def ontologies_filter_url(filters, page: 1, count: false) @@ -39,7 +50,6 @@ def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pages request_params = { display_links: false, display_context: false, include: includes, include_status: 'RDF' } request_params.merge!(page: page, pagesize: pagesize) if page - params[:sort_by] ||= 'ontology_name' filters_values_map = { categories: :hasDomain, groups: :group, @@ -47,7 +57,7 @@ def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pages isOfType: :isOfType, format: :hasOntologyLanguage, hasFormalityLevel: :hasFormalityLevel, - search: %i[name acronym], + search: %i[name acronym description], sort_by: :order_by } @@ -65,6 +75,15 @@ def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pages request_params.merge!(v[:api_key] => v[:default]) end + if params[:show_retired].blank? + @filters[:show_retired] = '' + request_params[:status] = 'alpha,beta,production' + else + request_params[:status] = 'alpha,beta,production,retired' + @filters[:show_retired] = 'true' + end + + filters_values_map.each do |filter, api_key| next if params[filter].nil? || params[filter].empty? @@ -73,12 +92,8 @@ def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pages request_params.merge!(key => params[filter]) end end - @show_views = params[:show_views]&.eql?('true') - @show_private_only = params[:private_only]&.eql?('true') - @show_retired = params[:show_retired]&.eql?('true') - @selected_format = params[:format] - @search = params[:search] + request_params.delete(:order_by) if %w[visits fair].include?(request_params[:sort_by].to_s) request_params end @@ -102,7 +117,7 @@ def ontology_hash(sub) o[:note_count] = ont.notes.length o[:project_count] = ont.projects.length - # o[:popularity] = @analytics[ont.acronym] || 0 + o[:popularity] = @analytics[ont.acronym] || 0 # if o[:type].eql?('ontology_view') # unless ontologies_hash[ont.viewOf].blank? @@ -156,7 +171,7 @@ def add_fair_score_metrics(ont_hash, ont) ont_hash[:fairScore] = @fair_scores[ont.acronym]['score'] ont_hash[:normalizedFairScore] = @fair_scores[ont.acronym]['normalizedScore'] else - ont_hash[:fairScore] = nil + ont_hash[:fairScore] = 0 ont_hash[:normalizedFairScore] = 0 end end @@ -175,9 +190,17 @@ def ontology_filters_init(categories, groups) end @formats = [['All formats', ''], 'OBO', 'OWL', 'SKOS', 'UMLS'] - @sorts_options = [['Sort by', ''], ['Name', 'ontology_name'], - ['Class count', 'metrics_classes'], ['Instances/Concepts count', 'metrics_individuals'], - ['Upload date', 'creationDate'], ['Release date', 'released']] + @sorts_options = [ + ['Sort by name', 'ontology_name'], + ['Sort by class count', 'metrics_classes'], + ['Sort by instances/concepts count', 'metrics_individuals'], + ['Sort by upload date', 'creationDate'], + ['Sort by release date', 'released'], + ['Sort by FAIR score', 'fair'], + ['Sort by popularity', 'visits'], + ['Sort by notes', 'notes'], + ['Sort by projects', 'projects'], + ] init_filters(params) # @missingStatus = [ diff --git a/app/controllers/concerns/submission_updater.rb b/app/controllers/concerns/submission_updater.rb index 2ab43c70a..e88d739d2 100644 --- a/app/controllers/concerns/submission_updater.rb +++ b/app/controllers/concerns/submission_updater.rb @@ -1,39 +1,46 @@ module SubmissionUpdater extend ActiveSupport::Concern - def save_submission(new_submission_hash) + def submission_from_params(new_submission_hash) convert_values_to_types(new_submission_hash) - - @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(new_submission_hash[:ontology]).first - @submission = LinkedData::Client::Models::OntologySubmission.new(values: submission_params(new_submission_hash)) + LinkedData::Client::Models::OntologySubmission.new(values: submission_params(new_submission_hash)) + end + def save_submission(new_submission_hash) + @submission = submission_from_params(new_submission_hash) update_ontology_summary_only @submission.save(cache_refresh_all: false) end - def update_submission(new_submission_hash) + def update_submission(new_submission_hash, submission_id) convert_values_to_types(new_submission_hash) @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(new_submission_hash[:ontology]).first - @submission = @ontology.explore.submissions({ display: 'all' }, new_submission_hash[:id]) - - @submission.update_from_params(submission_params(new_submission_hash)) + new_submission_hash.delete(:ontology) + @submission = @ontology.explore.submissions({ display: 'all' }, submission_id) + + new_values = new_submission_hash + new_values.each do |key, values| + @submission.send("#{key}=", values) + rescue StandardError + next + end update_ontology_summary_only - @submission.update(cache_refresh_all: false) + [@submission, @submission.update(values: new_values, cache_refresh_all: false)] end def add_ontologies_to_object(ontologies,object) ontologies.each do |ont| next if object.ontologies.include?(ont) - ontology = LinkedData::Client::Models::Ontology.find(ont) - if object.type.match(/\/([^\/]+)$/)[1] == 'Group' - ontology.group.push(object.id) - else - ontology.hasDomain.push(object.id) - end - ontology.update + ontology = LinkedData::Client::Models::Ontology.find(ont) + if object.type.match(/\/([^\/]+)$/)[1] == 'Group' + ontology.group.push(object.id) + else + ontology.hasDomain.push(object.id) + end + ontology.update end end @@ -44,7 +51,7 @@ def delete_ontologies_from_object(new_ontologies,old_ontologies,object) ontology = LinkedData::Client::Models::Ontology.find(ont) if object.type.match(/\/([^\/]+)$/)[1] == 'Group' ontology.group.delete(object.id) - else + else ontology.hasDomain.delete(object.id) end ontology.update @@ -53,19 +60,19 @@ def delete_ontologies_from_object(new_ontologies,old_ontologies,object) private - def update_ontology_summary_only - @ontology.summaryOnly = @submission.isRemote.eql?('3') + def update_ontology_summary_only(is_remote = @submission.isRemote) + @ontology.summaryOnly = is_remote&.eql?('3') @ontology.update end def convert_values_to_types(new_submission_hash) unless new_submission_hash[:contact].nil? - new_submission_hash[:contact] = new_submission_hash[:contact].values + new_submission_hash[:contact] = new_submission_hash[:contact].values unless new_submission_hash[:contact].is_a?(Array) new_submission_hash[:contact].delete_if { |c| c[:name].empty? || c[:email].empty? } end # Convert metadata that needs to be integer to int - @metadata.map do |hash| + submission_metadata.map do |hash| if hash["enforce"].include?("integer") if !new_submission_hash[hash["attribute"]].nil? && !new_submission_hash[hash["attribute"]].eql?("") new_submission_hash[hash["attribute"].to_s.to_sym] = Integer(new_submission_hash[hash["attribute"].to_s.to_sym]) @@ -104,21 +111,19 @@ def submission_params(params) { contact: [:name, :email] }, :homepage, :documentation, - :publication + :publication, + {copyrightHolder: {}} # TODO add automatically no list Agents ] - @metadata.each do |m| - + submission_metadata.each do |m| m_attr = m["attribute"].to_sym - - attributes << if m["enforce"].include?("list") - [{ m_attr => {} }, { m_attr => []}] - else - m_attr - end + m_attr = Array(m["enforce"]).include?("list") ? [{ m_attr => {} }, { m_attr => []}] : m_attr + attributes << m_attr end p = params.permit(attributes.uniq) - p.to_h.transform_values do |v| + p['pullLocation'] = '' if p['isRemote']&.eql?('3') + + p = p.to_h.transform_values do |v| if v.is_a? Hash v.values.reject(&:empty?) elsif v.is_a? Array @@ -127,5 +132,22 @@ def submission_params(params) v end end + + if p["copyrightHolder"]&.first&.include?("id") # TODO automatize + p["copyrightHolder"] = p["copyrightHolder"].first["id"] + elsif p["copyrightHolder"] + p["copyrightHolder"] = '' + end + + + submission_metadata.each do |m| + m_attr = m['attribute'].to_sym + if p[m_attr] && m['enforce'].include?('list') + p[m_attr] = Array(p[m_attr]) unless p[m_attr].is_a?(Array) + p[m_attr] = p[m_attr].map { |x| x.is_a?(Hash) ? x.values.reject(&:empty?) : x.reject(&:empty?) }.flatten.uniq if m['enforce'].include?('Agent') + end + end + + p end end diff --git a/app/controllers/fair_score_controller.rb b/app/controllers/fair_score_controller.rb index 9564a5875..8f62995b3 100644 --- a/app/controllers/fair_score_controller.rb +++ b/app/controllers/fair_score_controller.rb @@ -28,7 +28,7 @@ def get_fair @fair_scores_data = create_fair_scores_data(get_fair_score(@ontologies).values.first, 1) end rescue NameError - raise StandardError, 'Error: load failed' + #raise StandardError, 'Error: load failed' end end end \ No newline at end of file diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 85dc8b202..bf1bc3316 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -7,60 +7,36 @@ class HomeController < ApplicationController include FairScoreHelper def index - @ontologies_views = LinkedData::Client::Models::Ontology.all(include_views: true) - @ontologies = @ontologies_views.select {|o| !o.viewOf} - @ontologies_hash = Hash[@ontologies_views.map {|o| [o.acronym, o]}] - @groups = LinkedData::Client::Models::Group.all - @notes = LinkedData::Client::Models::Note.all - @last_notes = [] - unless @notes.empty? - @notes.sort! {|a,b| b.created <=> a.created } - @notes[0..20].each do |n| - ont_uri = n.relatedOntology.first - ont = LinkedData::Client::Models::Ontology.find(ont_uri) - next if ont.nil? - username = n.creator.split("/").last - note = { - :uri => n.links['ui'], - :id => n.id, - :subject => n.subject, - :body => n.body, - :created => n.created, - :author => username, - :ont_name => ont.name - } - @last_notes.push note - break if @last_notes.length >= [$HOME_LATEST_NOTES_COUNT.to_i, 5].max - end - end - # Get the latest manual mappings - # All mapping classes are bidirectional. - # Each class in the list maps to all other classes in the list. - if $DISPLAY_RECENT.nil? || $DISPLAY_RECENT == true - @recent_mappings = get_recent_mappings # application_controller + @analytics = helpers.ontologies_analytics + # Calculate BioPortal summary statistics + @ont_count = @analytics.keys.size + metrics = LinkedData::Client::Models::Metrics.all + metrics = metrics.each_with_object(Hash.new(0)) do |h, sum| + h.to_hash.slice(:classes, :properties, :individuals).each { |k, v| sum[k] += v } end - - organize_groups - # Calculate BioPortal summary statistics - @ont_count = @ontologies.length - @cls_count = LinkedData::Client::Models::Metrics.all.map { |m| m.classes.to_i }.sum - @individuals_count = LinkedData::Client::Models::Metrics.all.map {|m| m.individuals.to_i}.sum - @prop_count = 36286 + @cls_count = metrics[:classes] + @individuals_count = metrics[:individuals] + @prop_count = metrics[:properties] @map_count = total_mapping_count - @analytics = LinkedData::Client::Analytics.last_month - - @ontology_names = @ontologies.map { |ont| ["#{ont.name} (#{ont.acronym})", ont.acronym] } - - @anal_ont_names = {} + @projects_count = LinkedData::Client::Models::Project.all.length + @users_count = LinkedData::Client::Models::User.all.length + + @upload_benefits = [ + t('home.benefit1'), + t('home.benefit2'), + t('home.benefit3'), + t('home.benefit4'), + t('home.benefit5') + ] + + @anal_ont_names = [] @anal_ont_numbers = [] - @analytics.onts[0..4].each do |visits| - ont = @ontologies_hash[visits[:ont].to_s] - @anal_ont_names[ont.acronym] = ont.name - @anal_ont_numbers << visits[:views] + @analytics.sort_by{|ont, count| -count}[0..4].each do |ont, count| + @anal_ont_names << ont + @anal_ont_numbers << count end - end def render_layout_partial @@ -68,12 +44,6 @@ def render_layout_partial render partial: "layouts/#{partial}" end - def help - # Show the header/footer or not - layout = params[:pop].eql?('true') ? 'popup' : 'ontology' - render layout: layout - end - def all_resources @conceptid = params[:conceptid] @ontologyid = params[:ontologyid] @@ -125,7 +95,7 @@ def feedback end unless @errors.empty? - render render 'home/feedback/feedback', layout: feedback_layout + render 'home/feedback/feedback', layout: feedback_layout return end @@ -177,6 +147,14 @@ def validate_ontology_file @process_id = response.process_id end + def annotator_recommender_form + if params[:submit_button] == "annotator" + redirect_to "/annotator?text=#{params[:text]}" + elsif params[:submit_button] == "recommender" + redirect_to "/recommender?text=#{params[:text]}" + end + end + private # Dr. Musen wants 5 specific groups to appear first, sorted by order of importance. diff --git a/app/controllers/label_xl_controller.rb b/app/controllers/label_xl_controller.rb index e5543aa6d..d38fd870c 100644 --- a/app/controllers/label_xl_controller.rb +++ b/app/controllers/label_xl_controller.rb @@ -10,7 +10,7 @@ def show_label label_xl_label = label_xl ? label_xl['literalForm'] : nil label_xl_label = params[:id] if label_xl_label.nil? || label_xl_label.empty? - render LabelLinkComponent.inline(params[:id], label_xl_label) + render LabelLinkComponent.inline(params[:id], helpers.main_language_label(label_xl_label)) end private diff --git a/app/controllers/landscape_controller.rb b/app/controllers/landscape_controller.rb index c3f81dc9a..a0505e8bc 100644 --- a/app/controllers/landscape_controller.rb +++ b/app/controllers/landscape_controller.rb @@ -43,9 +43,9 @@ def index ontologyFormatsCount = {"OWL" => 0, "SKOS" => 0, "UMLS" => 0, "OBO" => 0} - @metrics_average = [{attr: "numberOfClasses", label: "Number of classes", array: []}, - {attr: "numberOfIndividuals", label: "Number of individuals", array: []}, - {attr: "numberOfProperties", label: "Number of properties", array: []}, + @metrics_average = [{attr: "classes", label: "Number of classes", array: []}, + {attr: "individuals", label: "Number of individuals", array: []}, + {attr: "properties", label: "Number of properties", array: []}, {attr: "maxDepth", label: "Max depth", array: []}, {attr: "maxChildCount", label: "Max child count", array: []}, {attr: "averageChildCount", label: "Average child count", array: []}, @@ -73,11 +73,12 @@ def index # We need prefixes to display them, we remove them to call them in the include relations_attributes = @relations_array.map {|r| r.to_s.split(":")[1]} + metrics_attributes = @metrics_average.map {|m| m[:attr]} # Concat all attributes array and generate a string separated with comma for include param all_attributes = sub_attributes.concat(contributors_attr_list).concat(org_attr_list) - .concat(relations_attributes).concat(metrics_attributes).concat(pref_properties_attributes).join(",") + .concat(relations_attributes).concat([:metrics]).concat(pref_properties_attributes).join(",") # Special treatment for includedInDataCatalog: arrays with a lot of different values, so it trigger the SPARQL default @@ -100,7 +101,7 @@ def index end # Get all latest submissions with the needed attributes (this request can be slow) - @submissions = LinkedData::Client::Models::OntologySubmission.all(include_status: "any", include_views: true, display_links: false, display_context: false, include: all_attributes) + @submissions = LinkedData::Client::Models::OntologySubmission.all(include_status: "any", include_views: true, display_links: false, display_context: false, include: 'all') # Iterate ontologies to get the submissions with all metadata @submissions.each do |sub| @@ -610,11 +611,14 @@ def notes_create_hash_entry(uri_id, notes_type, hash) # Add metrics metadata from the param sub to the @metrics_average var to get the average for each metrics def get_metrics_for_average(sub) # Adding metrics to their arrays - - @metrics_average.each do |metrics| - if !sub.send(metrics[:attr]).nil? - metrics[:array].push(sub.send(metrics[:attr])) + metrics = sub.send(:metrics) + @metrics_average.each do |m| + if metrics.nil? || metrics.send(m[:attr]).nil? + m[:array].push(0) + else + m[:array].push(metrics.send(m[:attr])) end + end end diff --git a/app/controllers/language_controller.rb b/app/controllers/language_controller.rb new file mode 100644 index 000000000..ed1ec7c79 --- /dev/null +++ b/app/controllers/language_controller.rb @@ -0,0 +1,21 @@ +class LanguageController < ApplicationController + + # set locale to the language selected by the user + def set_locale_language + language = params[:language].strip.downcase.to_sym + supported_languages = I18n.available_locales + + if language + if supported_languages.include?(language) + cookies.permanent[:locale] = language + else + # in case we want to show a message if the language is not available + flash.now[:notice] = "#{language} translation not available" + logger.error flash.now[:notice] + end + end + + redirect_to request.referer || root_path + end + +end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb index bae176851..e58ef1af1 100755 --- a/app/controllers/login_controller.rb +++ b/app/controllers/login_controller.rb @@ -16,9 +16,14 @@ def index # logs in a user def create + if is_email(params[:user][:username]) + username = LinkedData::Client::Models::User.find_by_email(params[:user][:username]).first.username + else + username = params[:user][:username] + end @errors = validate(params[:user]) if @errors.size < 1 - logged_in_user = LinkedData::Client::Models::User.authenticate(params[:user][:username], params[:user][:password]) + logged_in_user = LinkedData::Client::Models::User.authenticate(username, params[:user][:password]) if logged_in_user && !logged_in_user.errors login(logged_in_user) redirect = "/" @@ -38,6 +43,28 @@ def create end end + + def create_omniauth + auth_data = request.env['omniauth.auth'] + auth_code = auth_data.credentials.token + token_provider = helpers.omniauth_token_provider(params[:provider]) + + logged_in_user = LinkedData::Client::HTTP.post("#{LinkedData::Client.settings.rest_url}/users/authenticate", { access_token: auth_code , token_provider: token_provider}) + if logged_in_user && !logged_in_user.errors + login(logged_in_user) + redirect = "/" + + if session[:redirect] + redirect = CGI.unescape(session[:redirect]) + end + + redirect_to redirect + else + @errors = ["#{params[:provider]} authentication failed"] + render :action => 'index' + end + end + # Login as the provided username (only for admin users) def login_as unless session[:user] && session[:user].admin? @@ -131,5 +158,9 @@ def validate(params) return errors end + def is_email(email) + email =~ /\A[^@\s]+@[^@\s]+\z/ + end + end diff --git a/app/controllers/mappings_controller.rb b/app/controllers/mappings_controller.rb index e35ef3a15..b8baeda3a 100644 --- a/app/controllers/mappings_controller.rb +++ b/app/controllers/mappings_controller.rb @@ -279,8 +279,8 @@ def mapping_form(mapping: nil) end else mapping = LinkedData::Client::Models::Mapping.new - @ontology_from = LinkedData::Client::Models::Ontology.find(params[:ontology_from]) - @ontology_to = LinkedData::Client::Models::Ontology.find(params[:ontology_to]) + @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 @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]) diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index 9cf416011..a6f44e56b 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -4,7 +4,8 @@ class OntologiesController < ApplicationController include InstancesHelper include ActionView::Helpers::NumberHelper include OntologiesHelper - include SchemesHelper, ConceptsHelper + include ConceptsHelper + include SchemesHelper include CollectionsHelper include MappingStatistics include OntologyUpdater @@ -25,7 +26,6 @@ class OntologiesController < ApplicationController EXTERNAL_MAPPINGS_GRAPH = "http://data.bioontology.org/metadata/ExternalMappings" INTERPORTAL_MAPPINGS_GRAPH = "http://data.bioontology.org/metadata/InterportalMappings" - # GET /ontologies def index @categories = LinkedData::Client::Models::Category.all(display_links: false, display_context: false) @@ -36,48 +36,39 @@ def index end def ontologies_filter - - params[:sort_by] = 'creationDate' if params[:search] - - - if params[:count] - request_params = filters_params(params, includes: 'ontology,naturalLanguage,hasFormalityLevel,isOfType', page: nil) - submissions = LinkedData::Client::Models::OntologySubmission.all(request_params) - @object_count = count_objects(submissions.map { |sub| ontology_hash(sub) }) - - 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' : ''}") - end - end - end - end.flatten - streams = [ - replace('ontologies_filter_count_request') do - helpers.content_tag(:p, class: "browse-desc-text", style: "margin-bottom: 12px !important;") { "Showing #{submissions.size}" } + @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' : ''}") + end end - ] + update_filters_counts - else - @ontologies = submissions_paginate_filter(params) - 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", src: ontologies_filter_url(@filters, page: nil, count: true)) do - helpers.browser_counter_loader - end - } - ] - else - [replace("ontologies_list_view-page-#{@page.page}", partial: 'ontologies/browser/ontologies')] - end - end - + end + end.flatten + count_streams = [ + replace('ontologies_filter_count_request') do + helpers.content_tag(:p, class: "browse-desc-text", style: "margin-bottom: 12px !important;") { "Showing #{@ontologies.size} of #{@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 + render turbo_stream: streams + count_streams end def classes @@ -101,7 +92,7 @@ def classes @current_purl = @concept.purl if $PURL_ENABLED - unless @concept.id == 'bp_fake_root' + unless @concept.nil? || @concept.id == 'bp_fake_root' @notes = @concept.explore.notes end @@ -113,6 +104,7 @@ def classes end def properties + @acronym = @ontology.acronym if request.xhr? return render 'ontologies/sections/properties', layout: false else @@ -121,36 +113,34 @@ def properties end def create - if params[:commit].eql? 'Cancel' - redirect_to ontologies_path and return + @is_update_ontology = false + @ontology = ontology_from_params.save + + if response_error?(@ontology) + show_new_errors(@ontology) + return end - @ontology = LinkedData::Client::Models::Ontology.new(values: ontology_params) - @ontology_saved = @ontology.save - if response_error?(@ontology_saved) - @categories = LinkedData::Client::Models::Category.all - @groups = LinkedData::Client::Models::Group.all(display_links: false, display_context: false) - @user_select_list = LinkedData::Client::Models::User.all.map { |u| [u.username, u.id] } - @user_select_list.sort! { |a, b| a[1].downcase <=> b[1].downcase } - @errors = response_errors(@ontology_saved) - render 'new' + @submission = save_submission(new_submission_hash(@ontology)) + + if response_error?(@submission) + @ontology.delete + show_new_errors(@submission) else - if @ontology_saved.summaryOnly - redirect_to "/ontologies/success/#{@ontology.acronym}" - else - redirect_to new_ontology_submission_path(@ontology.acronym) - end + redirect_to "/ontologies/success/#{@ontology.acronym}" end end def edit - # Note: find_by_acronym includes ontology views @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:id]).first redirect_to_home unless session[:user] && @ontology.administeredBy.include?(session[:user].id) || session[:user].admin? - @categories = LinkedData::Client::Models::Category.all - @groups = LinkedData::Client::Models::Group.all - @user_select_list = LinkedData::Client::Models::User.all.map {|u| [u.username, u.id]} - @user_select_list.sort! {|a,b| a[1].downcase <=> b[1].downcase} + + submission = @ontology.explore.latest_submission(include: 'submissionId') + if submission + redirect_to edit_ontology_submission_path(@ontology.acronym, submission.submissionId) + else + redirect_to new_ontology_submission_path(@ontology.acronym) + end end def mappings @@ -165,8 +155,8 @@ def mappings def new @ontology = LinkedData::Client::Models::Ontology.new - @ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym', include_views: true, -display_links: false, display_context: false) + @submission = LinkedData::Client::Models::OntologySubmission.new + @ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym', include_views: true, display_links: false, display_context: false) @categories = LinkedData::Client::Models::Category.all @groups = LinkedData::Client::Models::Group.all @user_select_list = LinkedData::Client::Models::User.all.map { |u| [u.username, u.id] } @@ -227,10 +217,6 @@ def show params[:ontology] = params[:ontology].nil? ? params[:id] : params[:ontology] # Hash to convert Lexvo URI to flag code - $LEXVO_TO_FLAG = { 'http://lexvo.org/id/iso639-3/aar' => 'aa', 'http://lexvo.org/id/iso639-3/abk' => 'ab', - 'http://lexvo.org/id/iso639-3/ave' => 'ae', 'http://lexvo.org/id/iso639-3/afr' => 'af', - 'http://lexvo.org/id/iso639-3/aka' => 'ak', 'http://lexvo.org/id/iso639-3/amh' => 'am', - 'http://lexvo.org/id/iso639-3/arg' => 'an', 'http://lexvo.org/id/iso639-3/ara' => 'ar', 'http://lexvo.org/id/iso639-3/asm' => 'as', 'http://lexvo.org/id/iso639-3/ava' => 'av', 'http://lexvo.org/id/iso639-3/aym' => 'ay', 'http://lexvo.org/id/iso639-3/aze' => 'az', 'http://lexvo.org/id/iso639-3/bak' => 'ba', 'http://lexvo.org/id/iso639-3/bel' => 'be', 'http://lexvo.org/id/iso639-3/bul' => 'bg', 'http://lexvo.org/id/iso639-3/bis' => 'bi', 'http://lexvo.org/id/iso639-3/bam' => 'bm', 'http://lexvo.org/id/iso639-3/ben' => 'bn', 'http://lexvo.org/id/iso639-3/bod' => 'bo', 'http://lexvo.org/id/iso639-3/bre' => 'br', 'http://lexvo.org/id/iso639-3/bos' => 'bs', 'http://lexvo.org/id/iso639-3/cat' => 'ca', 'http://lexvo.org/id/iso639-3/che' => 'ce', 'http://lexvo.org/id/iso639-3/cha' => 'ch', 'http://lexvo.org/id/iso639-3/cos' => 'co', 'http://lexvo.org/id/iso639-3/cre' => 'cr', 'http://lexvo.org/id/iso639-3/ces' => 'cs', 'http://lexvo.org/id/iso639-3/chu' => 'cu', 'http://lexvo.org/id/iso639-3/chv' => 'cv', 'http://lexvo.org/id/iso639-3/cym' => 'cy', 'http://lexvo.org/id/iso639-3/dan' => 'da', 'http://lexvo.org/id/iso639-3/deu' => 'de', 'http://lexvo.org/id/iso639-3/div' => 'dv', 'http://lexvo.org/id/iso639-3/dzo' => 'dz', 'http://lexvo.org/id/iso639-3/ewe' => 'ee', 'http://lexvo.org/id/iso639-3/ell' => 'el', 'http://lexvo.org/id/iso639-3/eng' => 'en', 'http://lexvo.org/id/iso639-3/epo' => 'eo', 'http://lexvo.org/id/iso639-3/spa' => 'es', 'http://lexvo.org/id/iso639-3/est' => 'et', 'http://lexvo.org/id/iso639-3/eus' => 'eu', 'http://lexvo.org/id/iso639-3/fas' => 'fa', 'http://lexvo.org/id/iso639-3/ful' => 'ff', 'http://lexvo.org/id/iso639-3/fin' => 'fi', 'http://lexvo.org/id/iso639-3/fij' => 'fj', 'http://lexvo.org/id/iso639-3/fao' => 'fo', 'http://lexvo.org/id/iso639-3/fra' => 'fr', 'http://lexvo.org/id/iso639-3/fry' => 'fy', 'http://lexvo.org/id/iso639-3/gle' => 'ga', 'http://lexvo.org/id/iso639-3/gla' => 'gd', 'http://lexvo.org/id/iso639-3/glg' => 'gl', 'http://lexvo.org/id/iso639-3/grn' => 'gn', 'http://lexvo.org/id/iso639-3/guj' => 'gu', 'http://lexvo.org/id/iso639-3/glv' => 'gv', 'http://lexvo.org/id/iso639-3/hau' => 'ha', 'http://lexvo.org/id/iso639-3/heb' => 'he', 'http://lexvo.org/id/iso639-3/hin' => 'hi', 'http://lexvo.org/id/iso639-3/hmo' => 'ho', 'http://lexvo.org/id/iso639-3/hrv' => 'hr', 'http://lexvo.org/id/iso639-3/hat' => 'ht', 'http://lexvo.org/id/iso639-3/hun' => 'hu', 'http://lexvo.org/id/iso639-3/hye' => 'hy', 'http://lexvo.org/id/iso639-3/her' => 'hz', 'http://lexvo.org/id/iso639-3/ina' => 'ia', 'http://lexvo.org/id/iso639-3/ind' => 'id', 'http://lexvo.org/id/iso639-3/ile' => 'ie', 'http://lexvo.org/id/iso639-3/ibo' => 'ig', 'http://lexvo.org/id/iso639-3/iii' => 'ii', 'http://lexvo.org/id/iso639-3/ipk' => 'ik', 'http://lexvo.org/id/iso639-3/ido' => 'io', 'http://lexvo.org/id/iso639-3/isl' => 'is', 'http://lexvo.org/id/iso639-3/ita' => 'it', 'http://lexvo.org/id/iso639-3/iku' => 'iu', 'http://lexvo.org/id/iso639-3/jpn' => 'ja', 'http://lexvo.org/id/iso639-3/jav' => 'jv', 'http://lexvo.org/id/iso639-3/kat' => 'ka', 'http://lexvo.org/id/iso639-3/kon' => 'kg', 'http://lexvo.org/id/iso639-3/kik' => 'ki', 'http://lexvo.org/id/iso639-3/kua' => 'kj', 'http://lexvo.org/id/iso639-3/kaz' => 'kk', 'http://lexvo.org/id/iso639-3/kal' => 'kl', 'http://lexvo.org/id/iso639-3/khm' => 'km', 'http://lexvo.org/id/iso639-3/kan' => 'kn', 'http://lexvo.org/id/iso639-3/kor' => 'ko', 'http://lexvo.org/id/iso639-3/kau' => 'kr', 'http://lexvo.org/id/iso639-3/kas' => 'ks', 'http://lexvo.org/id/iso639-3/kur' => 'ku', 'http://lexvo.org/id/iso639-3/kom' => 'kv', 'http://lexvo.org/id/iso639-3/cor' => 'kw', 'http://lexvo.org/id/iso639-3/kir' => 'ky', 'http://lexvo.org/id/iso639-3/lat' => 'la', 'http://lexvo.org/id/iso639-3/ltz' => 'lb', 'http://lexvo.org/id/iso639-3/lug' => 'lg', 'http://lexvo.org/id/iso639-3/lim' => 'li', 'http://lexvo.org/id/iso639-3/lin' => 'ln', 'http://lexvo.org/id/iso639-3/lao' => 'lo', 'http://lexvo.org/id/iso639-3/lit' => 'lt', 'http://lexvo.org/id/iso639-3/lub' => 'lu', 'http://lexvo.org/id/iso639-3/lav' => 'lv', 'http://lexvo.org/id/iso639-3/mlg' => 'mg', 'http://lexvo.org/id/iso639-3/mah' => 'mh', 'http://lexvo.org/id/iso639-3/mri' => 'mi', 'http://lexvo.org/id/iso639-3/mkd' => 'mk', 'http://lexvo.org/id/iso639-3/mal' => 'ml', 'http://lexvo.org/id/iso639-3/mon' => 'mn', 'http://lexvo.org/id/iso639-3/mar' => 'mr', 'http://lexvo.org/id/iso639-3/msa' => 'ms', 'http://lexvo.org/id/iso639-3/mlt' => 'mt', 'http://lexvo.org/id/iso639-3/mya' => 'my', 'http://lexvo.org/id/iso639-3/nau' => 'na', 'http://lexvo.org/id/iso639-3/nob' => 'nb', 'http://lexvo.org/id/iso639-3/nde' => 'nd', 'http://lexvo.org/id/iso639-3/nep' => 'ne', 'http://lexvo.org/id/iso639-3/ndo' => 'ng', 'http://lexvo.org/id/iso639-3/nld' => 'nl', 'http://lexvo.org/id/iso639-3/nno' => 'nn', 'http://lexvo.org/id/iso639-3/nor' => 'no', 'http://lexvo.org/id/iso639-3/nbl' => 'nr', 'http://lexvo.org/id/iso639-3/nav' => 'nv', 'http://lexvo.org/id/iso639-3/nya' => 'ny', 'http://lexvo.org/id/iso639-3/oci' => 'oc', 'http://lexvo.org/id/iso639-3/oji' => 'oj', 'http://lexvo.org/id/iso639-3/orm' => 'om', 'http://lexvo.org/id/iso639-3/ori' => 'or', 'http://lexvo.org/id/iso639-3/oss' => 'os', 'http://lexvo.org/id/iso639-3/pan' => 'pa', 'http://lexvo.org/id/iso639-3/pli' => 'pi', 'http://lexvo.org/id/iso639-3/pol' => 'pl', 'http://lexvo.org/id/iso639-3/pus' => 'ps', 'http://lexvo.org/id/iso639-3/por' => 'pt', 'http://lexvo.org/id/iso639-3/que' => 'qu', 'http://lexvo.org/id/iso639-3/roh' => 'rm', 'http://lexvo.org/id/iso639-3/run' => 'rn', 'http://lexvo.org/id/iso639-3/ron' => 'ro', 'http://lexvo.org/id/iso639-3/rus' => 'ru', 'http://lexvo.org/id/iso639-3/kin' => 'rw', 'http://lexvo.org/id/iso639-3/san' => 'sa', 'http://lexvo.org/id/iso639-3/srd' => 'sc', 'http://lexvo.org/id/iso639-3/snd' => 'sd', 'http://lexvo.org/id/iso639-3/sme' => 'se', 'http://lexvo.org/id/iso639-3/sag' => 'sg', 'http://lexvo.org/id/iso639-3/hbs' => 'sh', 'http://lexvo.org/id/iso639-3/sin' => 'si', 'http://lexvo.org/id/iso639-3/slk' => 'sk', 'http://lexvo.org/id/iso639-3/slv' => 'sl', 'http://lexvo.org/id/iso639-3/smo' => 'sm', 'http://lexvo.org/id/iso639-3/sna' => 'sn', 'http://lexvo.org/id/iso639-3/som' => 'so', 'http://lexvo.org/id/iso639-3/sqi' => 'sq', 'http://lexvo.org/id/iso639-3/srp' => 'sr', 'http://lexvo.org/id/iso639-3/ssw' => 'ss', 'http://lexvo.org/id/iso639-3/sot' => 'st', 'http://lexvo.org/id/iso639-3/sun' => 'su', 'http://lexvo.org/id/iso639-3/swe' => 'sv', 'http://lexvo.org/id/iso639-3/swa' => 'sw', 'http://lexvo.org/id/iso639-3/tam' => 'ta', 'http://lexvo.org/id/iso639-3/tel' => 'te', 'http://lexvo.org/id/iso639-3/tgk' => 'tg', 'http://lexvo.org/id/iso639-3/tha' => 'th', 'http://lexvo.org/id/iso639-3/tir' => 'ti', 'http://lexvo.org/id/iso639-3/tuk' => 'tk', 'http://lexvo.org/id/iso639-3/tgl' => 'tl', 'http://lexvo.org/id/iso639-3/tsn' => 'tn', 'http://lexvo.org/id/iso639-3/ton' => 'to', 'http://lexvo.org/id/iso639-3/tur' => 'tr', 'http://lexvo.org/id/iso639-3/tso' => 'ts', 'http://lexvo.org/id/iso639-3/tat' => 'tt', 'http://lexvo.org/id/iso639-3/twi' => 'tw', 'http://lexvo.org/id/iso639-3/tah' => 'ty', 'http://lexvo.org/id/iso639-3/uig' => 'ug', 'http://lexvo.org/id/iso639-3/ukr' => 'uk', 'http://lexvo.org/id/iso639-3/urd' => 'ur', 'http://lexvo.org/id/iso639-3/uzb' => 'uz', 'http://lexvo.org/id/iso639-3/ven' => 've', 'http://lexvo.org/id/iso639-3/vie' => 'vi', 'http://lexvo.org/id/iso639-3/vol' => 'vo', 'http://lexvo.org/id/iso639-3/wln' => 'wa', 'http://lexvo.org/id/iso639-3/wol' => 'wo', 'http://lexvo.org/id/iso639-3/xho' => 'xh', 'http://lexvo.org/id/iso639-3/yid' => 'yi', 'http://lexvo.org/id/iso639-3/yor' => 'yo', 'http://lexvo.org/id/iso639-3/zha' => 'za', 'http://lexvo.org/id/iso639-3/zho' => 'zh', 'http://lexvo.org/id/iso639-3/zul' => 'zu' } # PURL-specific redirect to handle /ontologies/{ACR}/{CLASS_ID} paths if params[:purl_conceptid] @@ -258,18 +244,19 @@ def show # Handle the case where an ontology is converted to summary only. # See: https://github.com/ncbo/bioportal_web_ui/issues/133. - if @ontology.summaryOnly && params[:p].present? - pages = KNOWN_PAGES - ['summary', 'notes'] - if pages.include?(params[:p]) + 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 - end end #@ob_instructions = helpers.ontolobridge_instructions_template(@ontology) # Get the latest submission (not necessarily the latest 'ready' submission) - @submission_latest = @ontology.explore.latest_submission rescue @ontology.explore.latest_submission(include: '') + @submission_latest = @ontology.explore.latest_submission(include: 'all') 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 + end # Is the ontology downloadable? @ont_restricted = ontology_restricted?(@ontology.acronym) @@ -312,29 +299,46 @@ def submit_success # Main ontology description page (with metadata): /ontologies/ACRONYM def summary - # Note: find_by_acronym includes ontology views - @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:id]).first if @ontology.nil? - ontology_not_found(params[:id]) if @ontology.nil? - # Check to see if user is requesting json-ld, return the file from REST service if so - - if request.accept.to_s.eql?('application/ld+json') || request.accept.to_s.eql?('application/json') - headers['Content-Type'] = request.accept.to_s - render plain: @ontology.to_jsonld - return - end @metrics = @ontology.explore.metrics rescue [] #@reviews = @ontology.explore.reviews.sort {|a,b| b.created <=> a.created} || [] @projects = @ontology.explore.projects.sort { |a, b| a.name.downcase <=> b.name.downcase } || [] @analytics = LinkedData::Client::HTTP.get(@ontology.links['analytics']) - #Call to fairness assessment service + # Call to fairness assessment service tmp = fairness_service_enabled? ? get_fair_score(@ontology.acronym) : nil @fair_scores_data = create_fair_scores_data(tmp.values.first) unless tmp.nil? @views = get_views(@ontology) - @view_decorators = @views.map{ |view| ViewDecorator.new(view, view_context) } - + @view_decorators = @views.map { |view| ViewDecorator.new(view, view_context) } + @ontology_relations_data = ontology_relations_data + + category_attributes = submission_metadata.group_by{|x| x['category']}.transform_values{|x| x.map{|attr| attr['attribute']} } + @relations_array_display = @relations_array.map do |relation| + attr = relation.split(':').last + ["#{helpers.attr_label(attr, attr_metadata: helpers.attr_metadata(attr), show_tooltip: false)}(#{relation})", + relation] + end + @config_properties = properties_hash_values(category_attributes["object description properties"]) + @methodology_properties = properties_hash_values(category_attributes["methodology"]) + @agents_properties = properties_hash_values(category_attributes["persons and organizations"]) + @dates_properties = properties_hash_values(category_attributes["dates"]) + @links_properties = properties_hash_values([:isFormatOf, :hasFormat, :source, :includedInDataCatalog]) + @content_properties = properties_hash_values(category_attributes["content"]) + @community_properties = properties_hash_values(category_attributes["community"] + [:notes]) + @identifiers = properties_hash_values([:URI, :versionIRI, :identifier]) + @projects_properties = properties_hash_values(category_attributes["usage"]) + @ontology_icon_links = [%w[summary/download dataDump], + %w[summary/homepage homepage], + %w[summary/documentation documentation], + %w[icons/github repository], + %w[summary/sparql endpoint], + %w[icons/publication publication], + %w[icons/searching_database openSearchDescription] + ] + @ontology_icon_links.each do |icon| + icon << helpers.attr_label(icon[1], attr_metadata: helpers.attr_metadata(icon[1]), show_tooltip: false) + end if request.xhr? render partial: 'ontologies/sections/metadata', layout: false else @@ -342,33 +346,6 @@ def summary end end - def update - if params['commit'] == 'Cancel' - acronym = params['id'] - redirect_to "/ontologies/#{acronym}" - return - end - # Note: find_by_acronym includes ontology views - @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology][:acronym] || params[:id]).first - @ontology.update_from_params(ontology_params) - @ontology.viewOf = '' if @ontology.isView.eql? "0" - error_response = @ontology.update - if response_error?(error_response) - @categories = LinkedData::Client::Models::Category.all - @user_select_list = LinkedData::Client::Models::User.all.map {|u| [u.username, u.id]} - @user_select_list.sort! {|a,b| a[1].downcase <=> b[1].downcase} - @errors = response_errors(error_response) - @errors = { acronym: 'Acronym already exists, please use another' } if error_response.status == 409 - flash[:error] = @errors - redirect_to "/ontologies/#{@ontology.acronym}/edit" - else - # TODO_REV: Enable subscriptions - # if params["ontology"]["subscribe_notifications"].eql?("1") - # DataAccess.createUserSubscriptions(@ontology.administeredBy, @ontology.ontologyId, NOTIFICATION_TYPES[:all]) - # end - redirect_to "/ontologies/#{@ontology.acronym}" - end - end def virtual redirect_new_api @@ -385,23 +362,111 @@ def widgets render partial: 'ontologies/sections/widgets', layout: 'ontology_viewer' end end + + + def show_additional_metadata + @metadata = submission_metadata + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:id]).first + @submission_latest = @ontology.explore.latest_submission(include: 'all', display_context: false, display_links: false) + render partial: 'ontologies/sections/additional_metadata' + end + + def show_licenses + + @metadata = submission_metadata + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:id]).first + @licenses= %w[hasLicense morePermissions copyrightHolder useGuidelines] + @submission_latest = @ontology.explore.latest_submission(include: @licenses.join(",")) + render partial: 'ontologies/sections/licenses' + end def ajax_ontologies - - + + render json: LinkedData::Client::Models::Ontology.all(include_views: true, - display: 'acronym,name', display_links: false, display_context: false) + display: 'acronym,name', display_links: false, display_context: false) end + + + + def metrics + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology_id]).first + @metrics = @ontology.explore.metrics(display_context: false, display_links: false) + render partial: 'ontologies/sections/metrics' + end + + def metrics_evolution + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology_id]).first + key = params[:metrics_key] + ontology_not_found(params[:ontology_id]) if @ontology.nil? + + # Retrieve submissions in descending submissionId order (should be reverse chronological order) + @submissions = @ontology.explore.submissions({ include: "metrics" }) + .sort { |a, b| a.submissionId.to_i <=> b.submissionId.to_i }.reverse || [] + + metrics = @submissions.map { |s| s.metrics } + + data = { + key => metrics.map { |m| m.nil? ? 0 : m[key] } + } + + render partial: 'ontologies/sections/metadata/metrics_evolution_graph', locals: { data: data } + end + private + def get_views(ontology) + views = ontology.explore.views || [] + views.select!{ |view| view.access?(session[:user]) } + views.sort{ |a,b| a.acronym.downcase <=> b.acronym.downcase } + end - def ontology_params - p = params.require(:ontology).permit(:name, :acronym, { administeredBy:[] }, :viewingRestriction, { acl:[] }, - { hasDomain:[] }, :isView, :viewOf, :subscribe_notifications, {group:[]}) + def ontology_relations_data(sub = @submission_latest) + ontology_relations_array = [] + @relations_array = ["omv:useImports", "door:isAlignedTo", "door:ontologyRelatedTo", "omv:isBackwardCompatibleWith", "omv:isIncompatibleWith", "door:comesFromTheSameDomain", "door:similarTo", + "door:explanationEvolution", "voaf:generalizes", "door:hasDisparateModelling", "dct:hasPart", "voaf:usedBy", "schema:workTranslation", "schema:translationOfWork"] - p[:administeredBy].reject!(&:blank?) - p[:acl].reject!(&:blank?) - p[:hasDomain].reject!(&:blank?) - p[:group].reject!(&:blank?) - p.to_h + return if sub.nil? + + ont = sub.ontology + # Get ontology relations between each other (ex: STY isAlignedTo GO) + @relations_array.each do |relation_attr| + relation_values = sub.send(relation_attr.to_s.split(':')[1]) + next if relation_values.nil? || relation_values.empty? + + relation_values = [relation_values] unless relation_values.kind_of?(Array) + + relation_values.each do |relation_value| + next if relation_value.eql?(ont.acronym) + + target_id = relation_value + target_in_portal = false + # if we find our portal URL in the ontology URL, then we just keep the ACRONYM to try to get the ontology. + relation_value = relation_value.split('/').last if relation_value.include?($UI_URL) + + # Use acronym to get ontology from the portal + target_ont = LinkedData::Client::Models::Ontology.find_by_acronym(relation_value).first + if target_ont + target_id = target_ont.acronym + target_in_portal = true + end + + ontology_relations_array.push({ source: ont.acronym, target: target_id, relation: relation_attr.to_s, targetInPortal: target_in_portal }) + end + end + + ontology_relations_array + end + def properties_hash_values(properties, sub: @submission_latest, custom_labels: {}) + return {} if sub.nil? + + 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 @@ -413,10 +478,4 @@ def determine_layout end end - def get_views(ontology) - views = ontology.explore.views || [] - views.select!{ |view| view.access?(session[:user]) } - views.sort{ |a,b| a.acronym.downcase <=> b.acronym.downcase } - end - end diff --git a/app/controllers/ontologies_metadata_curator_controller.rb b/app/controllers/ontologies_metadata_curator_controller.rb index 7b9c95cfb..5d25ab6a7 100644 --- a/app/controllers/ontologies_metadata_curator_controller.rb +++ b/app/controllers/ontologies_metadata_curator_controller.rb @@ -5,11 +5,12 @@ class OntologiesMetadataCuratorController < ApplicationController before_action :submission_metadata, only: [:result, :edit, :update, :show_metadata_by_ontology] def result - @ontologies_ids = params[:ontology][:ontologyId].drop(1) - @metadata_sel = params[:search][:metadata].drop(1) + @ontologies_ids = params[:ontology] ? params[:ontology][:ontologyId] : [] + @metadata_sel = params[:search] ? params[:search][:metadata] : [] @show_submissions = !params[:show_submissions].nil? @ontologies = [] @submissions = [] + if @ontologies_ids.nil? || @ontologies_ids.empty? @ontologies = LinkedData::Client::Models::Ontology.all else @@ -46,8 +47,7 @@ def show_metadata_by_ontology @acronym = params[:ontology] inline_save = params[:inline_save] && params[:inline_save].eql?('true') display_submission_attributes(@acronym, params[:properties]&.split(','), - submissionId: params[:submission_id], - show_sections: false, inline_save: inline_save) + submissionId: params[:submission_id], inline_save: inline_save) render partial: 'submissions/form_content', locals: { id: params[:form_id] || '', acronym: @acronym, submissionId: params[:submission_id] } end @@ -57,8 +57,9 @@ def show_metadata_value submission_id = params[:submission_id] @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(acronym).first @submission = @ontology.explore.submissions({ display: "#{attribute},submissionId" }, submission_id) - - render_submission_attribute(attribute) + id = attribute_input_frame_id(acronym, submission_id, attribute) + render_turbo_stream replace(id, partial: 'ontologies_metadata_curator/attribute_inline', locals: { id: id, attribute: attribute, + submission: @submission, ontology: @ontology }) end def edit @@ -86,7 +87,7 @@ def update new_data = active_submission_data new_data[:ontology] = onto new_data[:id] = sub_i - error_responses << update_submission(new_data) if new_data + error_responses << update_submission(new_data, sub_i).last if new_data @submissions << @submission end @@ -121,45 +122,4 @@ def append_submission(ontology, submission) @submissions << sub end - def metadata_params - attributes = [ - :ontology, - :description, - :hasOntologyLanguage, - :prefLabelProperty, - :synonymProperty, - :definitionProperty, - :authorProperty, - :obsoleteProperty, - :obsoleteParent, - :version, - :status, - :released, - :isRemote, - :pullLocation, - :filePath, - { contact: [:name, :email] }, - :homepage, - :documentation, - :publication - ] - - @metadata.each do |m| - - m_attr = m["attribute"].to_sym - - attributes << if m["enforce"].include?("list") - { m_attr => [] } - else - m_attr - end - end - out = [] - params.require(:submission).permit!.tap do |x| - x.keys.each do |y| - out << x.require(y).permit(attributes.uniq) - end - end - out - end end diff --git a/app/controllers/properties_controller.rb b/app/controllers/properties_controller.rb new file mode 100644 index 000000000..0363649c9 --- /dev/null +++ b/app/controllers/properties_controller.rb @@ -0,0 +1,8 @@ +class PropertiesController < ApplicationController + def show + @property = LinkedData::Client::HTTP.get("/ontologies/#{params[:acronym]}/properties/#{helpers.encode_param(params[:id])}") + + @acronym = params[:acronym] + render partial: 'show' + end +end diff --git a/app/controllers/recommender_controller.rb b/app/controllers/recommender_controller.rb index 591ffc326..256fee3e4 100644 --- a/app/controllers/recommender_controller.rb +++ b/app/controllers/recommender_controller.rb @@ -5,6 +5,7 @@ class RecommenderController < ApplicationController RECOMMENDER_URI = "/recommender" def index + @text = params[:text] end # def create diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index 661bda5ab..34956040e 100644 --- a/app/controllers/schemes_controller.rb +++ b/app/controllers/schemes_controller.rb @@ -10,7 +10,7 @@ def show_label scheme_label = scheme ? scheme['prefLabel'] : params[:id] scheme_label = scheme_label.nil? || scheme_label.empty? ? params[:id] : scheme_label - render LabelLinkComponent.inline(params[:id], scheme_label) + render LabelLinkComponent.inline(params[:id], helpers.main_language_label(scheme_label)) end private diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index b5d052583..6b35a2b26 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -29,7 +29,8 @@ def json_search # record_type = format_record_type(result[:recordType], result[:obsolete]) record_type = "" - target_value = result.prefLabel + target_value = result.prefLabel.select{|x| x.include?( params[:q].delete('*'))}.first || result.prefLabel.first + case params[:target] when "name" target_value = result.prefLabel @@ -46,7 +47,7 @@ def json_search json << "|#{record_type}" json << "|#{result.explore.ontology.acronym}" json << "|#{result.id}" # Duplicated because we used to have shortId and fullId - json << "|#{result.prefLabel}" + json << "|#{target_value}" # This is nasty, but hard to workaround unless we rewrite everything (form_autocomplete, jump_to, crossdomain_autocomplete) # to use JSON from the bottom up. To avoid this, we pass a tab separated column list # Columns: synonym diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 639eee192..1735f909c 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -1,5 +1,5 @@ class SubmissionsController < ApplicationController - include SubmissionsHelper, SubmissionUpdater + include SubmissionsHelper, SubmissionUpdater, OntologyUpdater layout :determine_layout before_action :authorize_and_redirect, :only => [:edit, :update, :create, :new] before_action :submission_metadata, only: [:create, :edit, :new, :update, :index] @@ -22,62 +22,101 @@ def index # When getting "Add submission" form to display def new - @required_only = params[:required].nil? || !params[:required]&.eql?('false') - @ontology = LinkedData::Client::Models::Ontology.get(CGI.unescape(params[:ontology_id])) rescue nil - @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology_id]).first unless @ontology - @submission = @ontology.explore.latest_submission - @submission ||= LinkedData::Client::Models::OntologySubmission.new + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology_id]).first + @submission = @ontology.explore.latest_submission || LinkedData::Client::Models::OntologySubmission.new @submission.id = nil + @categories = LinkedData::Client::Models::Category.all + @groups = LinkedData::Client::Models::Group.all + @user_select_list = LinkedData::Client::Models::User.all.map {|u| [u.username, u.id]} + @user_select_list.sort! {|a,b| a[1].downcase <=> b[1].downcase} + @is_update_ontology = true + render "ontologies/new" end # Called when form to "Add submission" is submitted def create - # Make the contacts an array - _, submission_params = params[:submission].each.first - @required_only = !params['required-only'].nil? - @filters_disabled = true - @submission_saved = save_submission(submission_params) - if response_error?(@submission_saved) - @errors = response_errors(@submission_saved) # see application_controller::response_errors - if @errors && @errors[:uploadFilePath] - @errors = ["Please specify the location of your ontology"] - elsif @errors && @errors[:contact] - @errors = ["Please enter a contact"] + @is_update_ontology = true + + if params[:ontology] + @ontology, response = update_existent_ontology(params[:ontology_id]) + + if response.nil? || response_error?(response) + show_new_errors(response) + return end + end + @submission = @ontology.explore.latest_submission({ display: 'all' }) + @submission = save_submission(new_submission_hash(@ontology, @submission)) - render "new" + if response_error?(@submission) + show_new_errors(@submission) else redirect_to "/ontologies/success/#{@ontology.acronym}" end end # Called when form to "Edit submission" is submitted - def edit - display_submission_attributes params[:ontology_id], params[:properties]&.split(','), submissionId: params[:id], - required: params[:required]&.eql?('true'), - show_sections: params[:show_sections].nil? || params[:show_sections].eql?('true'), + 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')) + + render inline: attribute_template_output + + end + + def edit + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology_id]).first + ontology_not_found(params[:ontology_id]) unless @ontology + category_attributes = submission_metadata.group_by{|x| x['category']}.transform_values{|x| x.map{|attr| attr['attribute']} } + category_attributes = category_attributes.reject{|key| ['no'].include?(key.to_s)} + category_attributes['general'] << %w[acronym name groups administeredBy categories] + category_attributes['licensing'] << 'viewingRestriction' + category_attributes['relations'] << 'viewOf' + @selected_attributes = Array(params[:properties]) + if @selected_attributes.empty? + @categories_order = ['general', 'description', 'dates', 'licensing', 'persons and organizations', 'links', 'media', 'community', 'usage' ,'relations', 'content','methodology', 'object description properties'] + @category_attributes = category_attributes + end + render 'submissions/edit', layout: params[:container_id] ? nil : 'ontology' end # When editing a submission (called when submit "Edit submission information" form) def update - error_responses = [] - _, submission_params = params[:submission].each.first - @required_only = !params['required-only'].nil? - @filters_disabled = true - - error_responses << update_submission(submission_params) - - if error_responses.compact.any? { |x| x.status != 204 } - @errors = error_responses.map { |error_response| response_errors(error_response) } + @is_update_ontology = true + acronym = params[:ontology_id] + submission_id = params[:id] + if params[:ontology] + @ontology = update_existent_ontology(acronym) + if @ontology.nil? || response_error?(@ontology) + show_new_errors(@ontology, partial: 'submissions/form_content', id: 'test') + return + end end - if params[:attribute] - render_submission_attribute(params[:attribute]) + if params[:submission].nil? + return redirect_to "/ontologies/#{acronym}", + notice: 'Submission updated successfully' + end + + @submission, response = update_submission(update_submission_hash(acronym), submission_id) + if params[:attribute].nil? + if response_error?(response) + show_new_errors(response, partial: 'submissions/form_content', id: 'test') + else + redirect_to "/ontologies/#{acronym}", + notice: 'Submission updated successfully', status: :see_other + end else - redirect_to "/ontologies/#{@ontology.acronym}" + @errors = response_errors(response) if response_error?(response) + @submission = submission_from_params(params[:submission]) + @submission.submissionId = submission_id + reset_agent_attributes + render_submission_attribute(params[:attribute]) end end + end diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb index 54040a415..1d483b912 100644 --- a/app/controllers/subscriptions_controller.rb +++ b/app/controllers/subscriptions_controller.rb @@ -25,6 +25,8 @@ def create # So here we re-generate a new subscription Array (instead of directly updating it, which causes error) all_subs = [] u.subscription.each do |subs| + next if subs.ontology.nil? + # Add all subscription to the array, but not the one to be deleted if !subs.ontology.split('/').last.eql?(ont.acronym) all_subs.push({ontology: subs.ontology, notification_type: subs.notification_type}) @@ -42,6 +44,7 @@ def create already_subscribed = false all_subs = [] u.subscription.each do |subs| + next if subs.ontology.nil? # add all existing subscriptions all_subs.push({ontology: subs.ontology, notification_type: subs.notification_type}) if subs.ontology.split("/").last == ont.acronym && subs.notification_type == "NOTES" diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 86d9a2e6a..dc6c75bdb 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -227,7 +227,7 @@ def validate(params) end end if ((!params[:orcidId].match(/^\d{4}+(-\d{4})+$/)) || (params[:orcidId].length != 19)) && !(params[:orcidId].nil? || params[:orcidId].length < 1) - errors << "Please enter a valide orcid id" + errors << "Please enter a valid ORCID." end 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]$/) @@ -251,7 +251,7 @@ def validate_update(params) errors << "Last name field is required" end if params[:orcidId].present? && ((!params[:orcidId].match(/^\d{4}-\d{4}-\d{4}-\d{4}$/)) || (params[:orcidId].length != 19)) - errors << "Please enter a valide orcide id" + errors << "Please enter a valid ORCID." end if !params[:password].eql?(params[:password_confirmation]) errors << "Your Password and Password Confirmation do not match" diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb new file mode 100644 index 000000000..7553a567d --- /dev/null +++ b/app/helpers/admin_helper.rb @@ -0,0 +1,6 @@ +module AdminHelper + def selected_admin_section?(section_title) + current_section = params[:section] || 'site' + current_section.eql?(section_title) + end +end diff --git a/app/helpers/agent_helper.rb b/app/helpers/agent_helper.rb new file mode 100644 index 000000000..0a8cbba48 --- /dev/null +++ b/app/helpers/agent_helper.rb @@ -0,0 +1,169 @@ +module AgentHelper + + def agent_id_alert_container_id(agent_id, parent_id) + "agents_alerts_#{agent_id_frame_id(agent_id, parent_id)}" + end + + def agent_alert_container_id(agent, parent_id) + agent_id_alert_container_id(agent_id(agent), parent_id) + end + + def agent_alert_container(agent, parent_id) + render_alerts_container(agent_alert_container_id(agent, parent_id)) + end + + def agent_id_alert_container(agent_id, parent_id) + render_alerts_container(agent_alert_container_id(agent, parent_id)) + end + + def agent_table_line_id(id) + "#{id}_table_item" + end + + def agent_frame_id(agent, parent_id) + agent_id_frame_id(agent_id(agent), parent_id) + end + + def agent_id_frame_id(agent_id, parent_id) + return 'application_modal_content' if parent_id.nil? + + return agent_id if parent_id.empty? + + "#{parent_id}_#{agent_id}" + end + + def agent_id(agent) + return if agent.nil? + + agent_id = agent.is_a?(String) ? agent : agent.id + agent_id ? agent_id.split('/').last : '' + end + + def link_to_agent_edit_modal(agent, parent_id = nil) + + link_to_modal(nil, edit_agent_path(agent_id(agent), parent_id: parent_id, show_affiliations: parent_id.nil? || parent_id.empty?), class: 'btn btn-sm btn-light', data: { show_modal_title_value: "Edit agent #{agent.name}" }) do + content_tag(:i, '', class: 'far fa-edit') + end + end + + def link_to_agent_edit(agent, parent_id, name_prefix, deletable: false, show_affiliations: true) + link_to(edit_agent_path(agent_id(agent), name_prefix: name_prefix, deletable: deletable, parent_id: parent_id, show_affiliations: show_affiliations), class: 'btn btn-sm btn-light') do + content_tag(:i, '', class: 'far fa-edit') + end + end + + + def link_to_search_agent(id, parent_id , name_prefix, agent_type, deletable) + link_to("/agents/show_search?id=#{id}&parent_id=#{parent_id}&agent_type=#{agent_type}&deletable=#{deletable}&name_prefix=#{name_prefix}", class: 'btn btn-sm btn-light') do + inline_svg_tag "x.svg", width: "25", height: "25" + end + end + + def agent_search_input(id, agent_type, parent_id: , name_prefix:, deletable: false) + render TurboFrameComponent.new(id: agent_id_frame_id(id, parent_id)) do + render AgentSearchInputComponent.new(id: id, agent_type: agent_type, + name_prefix: name_prefix, + parent_id: parent_id, deletable: deletable, + edit_on_modal: false) + end + end + + + def affiliation?(agent) + agent.agentType.eql?('organization') + end + + def identifier_link(link, link_to: true) + if link_to + link_to(link, link, target: '_blank') + else + link + end + + end + + def display_identifiers(identifiers, link: true) + schemes_urls = { ORCID: 'https://orcid.org/', ISNI: 'https://isni.org/', ROR: 'https://ror.org/', GRID: 'https://www.grid.ac/' } + Array(identifiers).map do |i| + if i["schemaAgency"] + schema_agency, notation = [i["schemaAgency"], i["notation"]] + else + schema_agency, notation = (i["id"] || i["@id"]).split('Identifiers/').last.delete(' ').split(':') + end + value = "#{schemes_urls[schema_agency.to_sym]}#{notation}" + identifier_link(value, link_to: link) + end.join(', ') + end + + def display_agent(agent, link: true) + return agent if agent.is_a?(String) + + out = agent.name.to_s + identifiers = display_identifiers(agent.identifiers, link: link) + out = "#{out} ( #{identifiers} )" unless identifiers.empty? + affiliations = Array(agent.affiliations).map { |a| display_agent(a, link: link) }.join(', ') + out = "#{out} (affiliations: #{affiliations})" unless affiliations.empty? + out + end + + def agent_field_name(name, name_prefix = '') + name_prefix&.empty? ? name : "#{name_prefix}[#{name}]" + end + + def agent_identifier_name(index, name, name_prefix) + agent_field_name("[identifiers][#{index}][#{name}]", name_prefix) + end + + def new_affiliation_obj + a = LinkedData::Client::Models::Agent.new + a.agentType = 'organization' + a.creator = session[:user].id + a + end + + def agent_usages(agent = @agent) + usages = agent.usages.to_h + usages.delete(:links) + usages.delete(:context) + usages + end + + def agent_usages_count(agent = @agent) + usages = agent_usages(agent) + usages.values.flatten.size + end + + def agents_metadata + submission_metadata.select { |x| x["enforce"]&.include?('Agent') }.map do |x| + SubmissionInputsHelper::SubmissionMetadataInput.new(attribute_key: x["attribute"], attr_metadata: x) + end + end + def agents_metadata_attributes + agents_metadata.map { |x| [x.attr, x.label] } + end + + def agents_used_properties(agent) + usages = agent_usages(agent) + attributes = agents_metadata_attributes + + attributes.map do |attr, label| + [attr, usages.select { |x, v| v.any? { |uri| uri[attr] } }.keys.map { |x| x.to_s.split('/')[-3] }] + end.to_h + end + + def agent_usage_errors_display(errors) + content_tag(:div) do + errors.map do |ont, message| + content_tag(:p) do + (content_tag(:strong, ont) + ' ontology is not valid, here are the errors: ' + agent_usage_error_display(message[:error])).html_safe + end + end.join.html_safe + end + end + + def agent_usage_error_display(error) + error.map do |attr, details| + details.values.join(', ').html_safe + end.join('. ').html_safe + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 6863fc264..e03ed69cb 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -6,21 +6,32 @@ require 'pry' # used in a rescue module ApplicationHelper + REST_URI = $REST_URL + API_KEY = $API_KEY - include ModalHelper + include ModalHelper, MultiLanguagesHelper 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/", :metadata_def => "http://data.bioontology.org/metadata/def/", :dc => "http://purl.org/dc/elements/1.1/", :xsd => "http://www.w3.org/2001/XMLSchema#", :oboinowl_gen => "http://www.geneontology.org/formats/oboInOwl#", :obo_purl => "http://purl.obolibrary.org/obo/", - :umls => "http://bioportal.bioontology.org/ontologies/umls/", :door => "http://kannel.open.ac.uk/ontology#", :dct => "http://purl.org/dc/terms/", - :void => "http://rdfs.org/ns/void#", :foaf => "http://xmlns.com/foaf/0.1/", :vann => "http://purl.org/vocab/vann/", :adms => "http://www.w3.org/ns/adms#", - :voaf => "http://purl.org/vocommons/voaf#", :dcat => "http://www.w3.org/ns/dcat#", :mod => "http://www.isibang.ac.in/ns/mod#", :prov => "http://www.w3.org/ns/prov#", + :umls => "http://bioportal.bioontology.org/ontologies/umls/", :door => "http://kannel.open.ac.uk/ontology#", :dct => "http://purl.org/dc/terms/", + :void => "http://rdfs.org/ns/void#", :foaf => "http://xmlns.com/foaf/0.1/", :vann => "http://purl.org/vocab/vann/", :adms => "http://www.w3.org/ns/adms#", + :voaf => "http://purl.org/vocommons/voaf#", :dcat => "http://www.w3.org/ns/dcat#", :mod => "http://www.isibang.ac.in/ns/mod#", :prov => "http://www.w3.org/ns/prov#", :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/"} + + def ontologies_analytics + LinkedData::Client::Analytics.all.to_h.map do |key, ontology_analytics| + next if key.eql?(:links) || key.eql?(:context) + + [key.to_s, ontology_analytics.to_h.values.map { |x| x&.values }.flatten.compact.sum] + end.compact.to_h + end + def get_apikey unless session[:user].nil? return session[:user].apikey @@ -29,6 +40,18 @@ def get_apikey end end + def omniauth_providers_info + $OMNIAUTH_PROVIDERS + end + + def omniauth_provider_info(strategy) + omniauth_providers_info.select {|k,v| v[:strategy].eql?(strategy.to_sym) || k.eql?(strategy)} + end + + def omniauth_token_provider(strategy) + omniauth_provider_info(strategy.to_sym).keys.first + end + def isOwner?(id) unless session[:user].nil? if session[:user].admin? @@ -40,6 +63,7 @@ def isOwner?(id) end end end + def encode_param(string) CGI.escape(string) @@ -73,6 +97,10 @@ def get_username(user_id) username end + def current_user + session[:user] + end + def current_user_admin? session[:user] && session[:user].admin? end @@ -168,7 +196,7 @@ def build_tree(node, string, id, acronym, concept_schemes: nil) return string if node.children.nil? || node.children.empty? - node.children.sort! { |a, b| (a.prefLabel || a.id).downcase <=> (b.prefLabel || b.id).downcase } + node.children.sort! { |a, b| (main_language_label(a.prefLabel) || a.id).downcase <=> (main_language_label(a.prefLabel) || b.id).downcase } node.children.each do |child| active_style = child.id.eql?(id) ? "active" : '' @@ -194,31 +222,44 @@ def build_tree(node, string, id, acronym, concept_schemes: nil) end def tree_link_to_concept(child:, ontology_acronym:, active_style:, node: nil, skos: false) + language = request_lang li_id = child.id.eql?('bp_fake_root') ? 'bp_fake_root' : short_uuid open = child.expanded? ? "class='open'" : '' - icons = child.relation_icon(node) + #icons = child.relation_icon(node) removed because slow muted_style = skos && Array(child.isInActiveScheme).empty? ? 'text-muted' : nil - muted_title = muted_style && !child.obsolete? ? "title='is not in a scheme'" : nil - href = ontology_acronym.blank? ? '#' : "/ontologies/#{child.explore.ontology.acronym}/concepts/?id=#{CGI.escape(child.id)}" + muted_title = muted_style && !child.obsolete? ? "title='is not in a scheme'" : nil + href = ontology_acronym.blank? ? '#' : "/ontologies/#{ontology_acronym}/concepts/?id=#{CGI.escape(child.id)}&language=#{language}" + + if child.prefLabel.nil? + pref_label_html = child.id.split('/').last + else + pref_label_lang, pref_label_html = select_language_label(child.prefLabel) + pref_label_lang = pref_label_lang.to_s.upcase + tooltip = pref_label_lang.eql?("@NONE") ? "" : "data-controller='tooltip' data-tooltip-position-value='right' title='#{pref_label_lang}'"; + end + link = <<-EOS - #{child.prefLabel ? child.prefLabel({ use_html: true }) : child.id} + class='#{muted_style} #{active_style}' #{muted_title}' + #{tooltip} + > + #{ pref_label_html } EOS - - "
  • #{link} #{icons}" + "
  • #{link}" end + def tree_link_to_children(child:, acronym: ,concept_schemes: nil) + language = request_lang li_id = child.id.eql?('bp_fake_root') ? 'bp_fake_root' : short_uuid concept_schemes = "&concept_schemes=#{concept_schemes.map{|x| CGI.escape(x)}.join(',')}" if concept_schemes - link = "ajax_class" + link = "ajax_class" "
    • #{link}
    " end @@ -260,19 +301,26 @@ def help_tooltip(content, html_attribs = {}, icon = 'fas fa-question-circle', cs BLOCK end - def anonymous_user - # - # TODO: Fix and failures from removing 'DataAccess' call here. - # - #user = DataAccess.getUser($ANONYMOUS_USER) - user ||= User.new({"id" => 0}) + def error_message_text + return @errors if @errors.is_a?(String) + @errors = @errors[:error] if @errors && @errors[:error] + "Errors in fields #{@errors.keys.join(', ')}" + end + + def error_message_alert + return if @errors.nil? + + content_tag(:div, class: 'my-1') do + render Display::AlertComponent.new(message: error_message_text, type: 'danger', closable: false) + end end + def render_advanced_picker(custom_ontologies = nil, selected_ontologies = [], align_to_dom_id = nil) selected_ontologies ||= [] init_ontology_picker(custom_ontologies, selected_ontologies) render :partial => "shared/ontology_picker_advanced", :locals => { - :custom_ontologies => custom_ontologies, :selected_ontologies => selected_ontologies, :align_to_dom_id => align_to_dom_id + :custom_ontologies => custom_ontologies, :selected_ontologies => selected_ontologies, :align_to_dom_id => align_to_dom_id } end @@ -351,14 +399,14 @@ def get_groups_data def metadata_for_select get_metadata return @metadata_for_select - end + end def get_metadata @metadata_for_select = [] submission_metadata.each do |data| @metadata_for_select << data["attribute"] end - end + end def ontologies_to_acronyms(ontologyIDs) @@ -383,6 +431,21 @@ def truncate_with_more(text, options = {}) output = "#{truncate(text, :length => length, :omission => trailing_text)}" + more + "" end + def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, &block) + content_tag(:div, data: { controller: 'tooltip' }, title: tooltip) do + check_input(id: id, name: name, value: value, label: label, checked: checked, &block) + end + end + + def group_chip_component(id: nil, name: , object: , checked: , value: nil, title: nil, &block) + title ||= object["name"] + value ||= (object["value"] || object["acronym"] || object["id"]) + + chips_component(id: id || value, name: name, label: object["acronym"], + checked: checked, + value: value, tooltip: title, &block) + end + alias :category_chip_component :group_chip_component def add_comment_button(parent_id, parent_type) if session[:user].nil? @@ -410,26 +473,42 @@ def add_proposal_button(parent_id, parent_type) class: "add_proposal btn btn-primary", data: { show_modal_title_value: "Add a new proposal"} end end - + def subscribe_button(ontology_id) + return if ontology_id.nil? + ontology_acronym = ontology_id.split('/').last + if session[:user].nil? - return link_to 'Subscribe to notes emails', "/login?redirect=#{request.url}", {style:'font-size: .9em;', class: 'link_button'} + link = "/login?redirect=#{request.url}" + subscribed = false + user_id = nil + else + user = LinkedData::Client::Models::User.find(session[:user].id) + subscribed = subscribed_to_ontology?(ontology_acronym, user) + link = "javascript:void(0);" + user_id = user.id end - user = LinkedData::Client::Models::User.find(session[:user].id) - ontology_acronym = ontology_id.split('/').last - subscribed = subscribed_to_ontology?(ontology_acronym, user) + count = count_subscriptions(ontology_id) + render OntologySubscribeButtonComponent.new(ontology_id: ontology_id, subscribed: subscribed, user_id: user_id, count: count, link: link) + end - render OntologySubscribeButtonComponent.new(ontology_id: ontology_id, subscribed: subscribed, user_id: user.id) + def admin_block(ontology: @ontology, user: session[:user], class_css: "admin-border", &block) + if ontology.admin?(user) + content_tag(:div, class: class_css) do + capture(&block) if block_given? + end + end end + def subscribed_to_ontology?(ontology_acronym, user) user.bring(:subscription) if user.subscription.nil? # user.subscription is an array of subscriptions like {ontology: ontology_id, notification_type: "NOTES"} return false if user.subscription.nil? or user.subscription.empty? user.subscription.each do |sub| #sub = {ontology: ontology_acronym, notification_type: "NOTES"} - sub_ont_acronym = sub[:ontology].split('/').last # make sure we get the acronym, even if it's a full URI + sub_ont_acronym = sub[:ontology] ? sub[:ontology].split('/').last : nil # make sure we get the acronym, even if it's a full URI return true if sub_ont_acronym == ontology_acronym end return false @@ -480,7 +559,7 @@ def bp_ont_link(ont_acronym) end def bp_class_link(cls_id, ont_acronym) - return "#{bp_ont_link(ont_acronym)}?p=classes&conceptid=#{escape(cls_id)}" + return "#{bp_ont_link(ont_acronym)}?p=classes&conceptid=#{escape(cls_id)}&language=#{request_lang}" end def bp_scheme_link(scheme_id, ont_acronym) @@ -507,19 +586,18 @@ def label_ajax_data_h(cls_id, ont_acronym, ajax_uri, cls_url) end def label_ajax_data(cls_id, ont_acronym, ajax_uri, cls_url) - tag.attributes label_ajax_data_h(cls_id, ont_acronym, ajax_uri, cls_url) + label_ajax_data_h(cls_id, ont_acronym, ajax_uri, cls_url) end - def label_ajax_link(link, cls_id, ont_acronym, ajax_uri, cls_url, target = '') - href_cls = " href='#{link}'" + 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) - style = 'btn btn-sm btn-light' - "#{cls_id}" + 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) end def get_link_for_cls_ajax(cls_id, ont_acronym, target = nil) - target = target.nil? ? '' : " target='#{target}' " - if cls_id.start_with?('http://') || cls_id.start_with?('https://') link = bp_class_link(cls_id, ont_acronym) ajax_url = '/ajax/classes/label' @@ -539,14 +617,14 @@ def get_link_for_ont_ajax(ont_acronym) def get_link_for_scheme_ajax(scheme, ont_acronym, target = '_blank') link = bp_scheme_link(scheme, ont_acronym) - ajax_url = '/ajax/schemes/label' + ajax_url = "/ajax/schemes/label?language=#{request_lang}" scheme_url = "?p=schemes&schemeid=#{CGI.escape(scheme)}" label_ajax_link(link, scheme, ont_acronym, ajax_url, scheme_url, target) end def get_link_for_collection_ajax(collection, ont_acronym, target = '_blank') link = bp_collection_link(collection, ont_acronym) - ajax_url = '/ajax/collections/label' + ajax_url = "/ajax/collections/label?language=#{request_lang}" collection_url = "?p=collections&collectionid=#{CGI.escape(collection)}" label_ajax_link(link, collection, ont_acronym, ajax_url, collection_url, target) end @@ -568,11 +646,12 @@ def get_link_for_label_xl_ajax(label_xl, ont_acronym, cls_id, modal: true) end ###END ruby equivalent of JS code in bp_ajax_controller. - def ontology_viewer_page_name(ontology_name, concept_name_title , page) - ontology_name + " | " +concept_name_title + " - #{page.capitalize}" + def ontology_viewer_page_name(ontology_name, concept_label, page) + ontology_name + " | " + " #{page.capitalize}" + end + def help_path(anchor: nil) + "#{Rails.configuration.settings.links[:help]}##{anchor}" end - - def uri?(url) url =~ /\A#{URI::DEFAULT_PARSER.make_regexp(%w[http https])}\z/ @@ -594,15 +673,9 @@ def skos? submission = @submission || @submission_latest submission&.hasOntologyLanguage === 'SKOS' end - + def current_page?(path) request.path.eql?(path) - end - - def request_lang - lang = params[:language] || params[:lang] - lang = 'EN' unless lang - lang.upcase end def bp_config_json @@ -629,15 +702,52 @@ 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 + def navitems + items = [["/ontologies", t('layout.header.browse')], + ["/mappings", t('layout.header.mappings')], + ["/recommender", t("layout.header.recommender")], + ["/annotator", t("layout.header.annotator")], + ["/landscape", t("layout.header.landscape")]] end - def navitems - items = [["/ontologies", "Browse"],["/mappings", "Mappings"],["/recommender", "Recommender"],["/annotator", "Annotator"], ["/landscape", "Landscape"]] + + def beta_badge(text = 'beta', tooltip: 'This feature is experimental and may have issues') + return unless text + content_tag(:span, text, data: { controller: 'tooltip' }, title: tooltip, class: 'badge badge-pill bg-secondary text-white') + end + + def attribute_enforced_values(attr) + submission_metadata.select {|x| x['@id'][attr]}.first['enforcedValues'] + end + + def prefix_properties(concept_properties) + modified_properties = {} + + concept_properties.each do |key, value| + if value.is_a?(Hash) && value.key?(:key) + key_string = value[:key].to_s + next if key_string.include?('metadata') + + modified_key = prefix_property_url(key_string, key) + modified_properties[modified_key] = value unless modified_key.nil? + end + end + + modified_properties end + def prefix_property_url(key_string, key = nil) + namespace_key, _ = RESOLVE_NAMESPACE.find { |_, value| key_string.include?(value) } + if key && namespace_key + "#{namespace_key}:#{key}" + elsif key.nil? && namespace_key + namespace_key + else # we don't try to guess the prefix + nil + end + end end diff --git a/app/helpers/collections_helper.rb b/app/helpers/collections_helper.rb index b2c20a2ba..f1c8ca4d6 100644 --- a/app/helpers/collections_helper.rb +++ b/app/helpers/collections_helper.rb @@ -2,13 +2,13 @@ module CollectionsHelper def get_collections(ontology, add_colors: false) - collections = ontology.explore.collections + collections = ontology.explore.collections(language: request_lang) generate_collections_colors(collections) if add_colors collections end def get_collection(ontology, collection_uri) - ontology.explore.collections({ include: 'all' },collection_uri) + ontology.explore.collections({ include: 'all', language: request_lang},collection_uri) end def get_collection_label(collection) @@ -25,14 +25,15 @@ def get_collections_labels(collections, main_uri = '') collections_labels = [] collections.each do |x| id = x['@id'] - label = get_collection_label(x) + label = select_language_label(get_collection_label(x)) if id.eql? main_uri selected_label = { 'prefLabel' => label, '@id' => id } else collections_labels.append( { 'prefLabel' => label, '@id' => id , 'color' => x['color'] }) end end - collections_labels.sort_by! { |s| s['prefLabel']} + + collections_labels = sorted_labels(collections_labels) collections_labels.unshift selected_label if selected_label [collections_labels, selected_label] end @@ -42,19 +43,36 @@ def no_collections? end def no_collections_alert - render AlertMessageComponent.new(id: 'collection-empty-info') do + render Display::AlertComponent.new do "#{@ontology.acronym} does not contain collections (skos:Collection)" end end - def collection_path(collection_id = '') - "/ontologies/#{@ontology.acronym}/collections/show?id=#{escape(collection_id)}" + def collection_path(collection_id = '', language = '') + "/ontologies/#{@ontology.acronym}/collections/show?id=#{escape(collection_id)}&language=#{language}" end def request_collection_id params[:id] || params[:collection_id] || params[:concept_collection] end + def sort_collections_label(collections_labels) + sorted_labels(collections_labels) + end + + def link_to_collection(collection, selected_collection_id) + pref_label_lang, pref_label_html = get_collection_label(collection) + tooltip = pref_label_lang.to_s.eql?('@none') ? '' : "data-controller='tooltip' data-tooltip-position-value='right' title='#{pref_label_lang.upcase}'" + <<-EOS + + #{pref_label_html} + + EOS + end + private def generate_collections_colors(collections) diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb index 3b931cb31..c67209696 100644 --- a/app/helpers/components_helper.rb +++ b/app/helpers/components_helper.rb @@ -6,18 +6,21 @@ def info_tooltip(text) def empty_state_message(message) content_tag(:p, message.html_safe, class: 'font-italic field-description_text') end - + def properties_list_component(c, properties, &block) - properties.each do |k, v| + properties.each do |k, value| + values, label = value c.row do content = if block_given? - capture(v, &block) + capture(values, &block) else - v + if link?(Array(values).first) + horizontal_list_container(values) { |v| link?(v) ? render(LinkFieldComponent.new(value: v)) : v } + else + Array(values).join(', ') + end end - render FieldContainerComponent.new(label: attr_label(k)) do - content - end + render FieldContainerComponent.new(label: attr_label(k, label, attr_metadata: attr_metadata(k.to_s), show_tooltip: false), value: content.to_s.html_safe) end end @@ -64,7 +67,7 @@ def properties_card(title, tooltip, properties, &block) def properties_dropdown(id, title, tooltip, properties, &block) render DropdownContainerComponent.new(title: title, id: id, tooltip: tooltip) do |d| d.empty_state do - properties_string = properties.keys[0..4].map{|key| "#{attr_label(key)}" }.join(', ')+'... ' if properties + properties_string = properties.keys[0..4].map { |key| "#{attr_label(key, attr_metadata: attr_metadata(key), show_tooltip: false)}" }.join(', ') + '... ' if properties empty_state_message "The fields #{properties_string} are empty" end @@ -77,4 +80,23 @@ def properties_dropdown(id, title, tooltip, properties, &block) end end end + + def form_save_button + render Buttons::RegularButtonComponent.new(id:'save-button', value: "Save", variant: "primary", size: "slim", type: "submit") do |btn| + btn.icon_left do + inline_svg_tag "check.svg" + end + end + end + + def form_cancel_button + render Buttons::RegularButtonComponent.new(id:'cancel-button', value: "Cancel", variant: "secondary", size: "slim") do |btn| + btn.icon_left do + inline_svg_tag "x.svg", width: "20", height: "20" + + end + end + end + + end diff --git a/app/helpers/concepts_helper.rb b/app/helpers/concepts_helper.rb index b56489dc0..4418e6d52 100644 --- a/app/helpers/concepts_helper.rb +++ b/app/helpers/concepts_helper.rb @@ -19,6 +19,8 @@ def exclude_relation?(relation_to_check, ontology = nil) end def get_concept_id(params, concept, root) + return nil if concept.nil? + if concept_id_param_exist?(params) concept.nil? ? '' : concept.id elsif !root.children.first.nil? @@ -48,7 +50,7 @@ def concept_label(ont_id, cls_id) ontology_not_found(ont_id) unless @ontology # Retrieve a class prefLabel or return the class ID (URI) # - mappings may contain class URIs that are not in bioportal (e.g. obo-xrefs) - cls = @ontology.explore.single_class(cls_id) + cls = @ontology.explore.single_class({language: request_lang, include: 'prefLabel'}, cls_id) # TODO: log any cls.errors # TODO: NCBO-402 might be implemented here, but it throws off a lot of ajax result rendering. #cls_label = cls.prefLabel({:use_html => true}) || cls_id @@ -66,7 +68,7 @@ def concept_date(concept) end def sorted_by_date_url(page = 1, last_concept = nil) - out = "/ajax/classes/date_sorted_list?ontology=#{@ontology.acronym}&page=#{page}" + out = "/ajax/classes/date_sorted_list?ontology=#{@ontology.acronym}&page=#{page}&language=#{request_lang}" out += "&last_date=#{concept_date(last_concept)}" if last_concept out end diff --git a/app/helpers/fair_score_helper.rb b/app/helpers/fair_score_helper.rb index 6febe47e6..7a272a25f 100644 --- a/app/helpers/fair_score_helper.rb +++ b/app/helpers/fair_score_helper.rb @@ -28,6 +28,12 @@ def get_fair_combined_score(ontologies_acronyms, apikey = user_apikey) get_fairness_json(ontologies_acronyms, apikey)['combinedScores'] end + def fairness_link + link_to(get_fairness_service_url, id: "fairness-service-url", target: "_blank", "aria-label": t("get_json_version"), title: t("get_json_version")) do + tag.img(src: asset_path('json.svg'), "aria-hidden" => "true", style: "margin-left: 0.5rem; width: 18px; margin-bottom: 4px;") + end + end + def create_fair_scores_data(fair_scores, count = nil) return nil if fair_scores.nil? diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index a2a34a755..ddb0d2aba 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -5,14 +5,22 @@ module HomeHelper def render_footer_link(options = {}) + link_content = options[:text][I18n.locale] || options[:text][:en] if options[:text] + link_content ||= image_tag(options[:img_src]) if options[:img_src] + link_content ||= content_tag(:i, '', class: options[:icon]) if options[:icon] + + link_to(link_content, options[:url], target: options[:target], class: options[:css_class].to_s, style: options[:text].blank? ? 'text-decoration: none' : '').html_safe if link_content + end - link_content = options[:text].presence - link_content ||= image_tag(options[:img_src]) if options[:img_src].present? - link_content ||= content_tag(:i, '', class: options[:icon]) if options[:icon].present? - unless link_content.blank? - link_to(link_content, options[:url], target: options[:target], class: options[:css_class].to_s, style: options[:text].blank? ? 'text-decoration: none' : '') - end.to_s.html_safe + def format_number_abbreviated(number) + if number >= 1_000_000 + (number / 1_000_000).to_s + 'M' + elsif number >= 1_000 + (number / 1_000).to_s + 'K' + else + number.to_s + end + end - end end \ No newline at end of file diff --git a/app/helpers/inputs_helper.rb b/app/helpers/inputs_helper.rb new file mode 100644 index 000000000..0130ecc2f --- /dev/null +++ b/app/helpers/inputs_helper.rb @@ -0,0 +1,74 @@ +module InputsHelper + + def text_input(name:, value:, label: nil, disabled: false, help: nil, error_message: nil) + render Input::TextInputComponent.new(label: input_label(label, name), name: name, value: value, + error_message: error_message || input_error_message(name), + disabled: disabled, + helper_text: help) + end + + def select_input(name:, values:, id: nil, label: nil, selected: nil, multiple: false, help: nil, open_to_add: false, required: false, data: {}) + render Input::SelectComponent.new(label: input_label(label, name), id: id || name, name: name, value: values, + selected: selected, + multiple: multiple, + helper_text: help, + open_to_add_values: open_to_add, + required: required, + data: data) + 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? + capture(c, &block) + end + end + end + + def switch_input(id:, name:, label:, checked: false, value: '', boolean_switch: false, style: nil) + render SwitchInputComponent.new(id: id, name: name, label: label, checked: checked, value: value, boolean_switch: boolean_switch, style: style) + end + + def url_input(name:, value:, label: nil, help: nil) + render Input::UrlComponent.new(label: input_label(label, name), name: name, value: value, + error_message: input_error_message(name), + helper_text: help) + end + + def text_area_input(name:, value:, label: nil, help: nil) + render Input::TextAreaComponent.new(label: input_label(label, name), name: name, value: value, + error_message: input_error_message(name), + helper_text: help) + end + + def date_input(name:, value:, label: nil, help: nil, max_date: nil) + render Input::DateComponent.new(label: input_label(label, name), name: name, value: value, + error_message: input_error_message(name), + helper_text: help, + max_date: max_date) + end + + private + + def method_name(name) + match = /.*\[(.*?)\]/.match(name) + match.nil? ? name : match[1] + end + + def input_label(label, name) + label || method_name(name).humanize + end + + def attribute_error(attr) + return '' if @errors&.is_a?(String) + return '' unless @errors && @errors[attr.to_sym] + + errors = @errors[attr.to_sym] + + errors.values.join(', ') + end + + def input_error_message(name) + attribute_error(method_name(name)) + end +end \ No newline at end of file diff --git a/app/helpers/label_xl_helper.rb b/app/helpers/label_xl_helper.rb index 37b099bcc..d5081bb40 100644 --- a/app/helpers/label_xl_helper.rb +++ b/app/helpers/label_xl_helper.rb @@ -1,7 +1,7 @@ module LabelXlHelper def get_label_xl(ontology, label_xl_uri) - ontology.explore.xl_labels({ include: 'all' }, label_xl_uri) + ontology.explore.xl_labels({ include: 'all', lang: 'all'}, label_xl_uri) end def get_label_xl_label(label_xl) diff --git a/app/helpers/mappings_helper.rb b/app/helpers/mappings_helper.rb index a3f03f65f..2f32f16d7 100644 --- a/app/helpers/mappings_helper.rb +++ b/app/helpers/mappings_helper.rb @@ -37,7 +37,6 @@ def translation?(relation_array) false end else - LOG.add :error, "Warning: Mapping relation is not an array" false end end @@ -48,26 +47,6 @@ def get_prefixed_uri(uri) return uri end - def get_link_for_cls_ajax(cls_id, ont_acronym, target = nil) - # Note: bp_ajax_controller.ajax_process_cls will try to resolve class labels. - # Uses 'http' as a more generic attempt to resolve class labels than .include? ont_acronym; the - # bp_ajax_controller.ajax_process_cls will try to resolve class labels and - # otherwise remove the UNIQUE_SPLIT_STR and the ont_acronym. - if target.nil? - target = "" - else - target = " target='#{target}' " - end - if cls_id.start_with? 'http://' - href_cls = " href='#{bp_class_link(cls_id, ont_acronym)}' " - data_cls = " data-cls='#{cls_id}' " - data_ont = " data-ont='#{ont_acronym}' " - return "#{cls_id}" - else - return auto_link(cls_id, :all, :target => '_blank') - end - end - # method to get (using http) prefLabel for interportal classes # Using bp_ajax_controller.ajax_process_interportal_cls will try to resolve class labels. def ajax_to_inter_portal_cls(cls) diff --git a/app/helpers/metadata_helper.rb b/app/helpers/metadata_helper.rb new file mode 100644 index 000000000..98c0c08a2 --- /dev/null +++ b/app/helpers/metadata_helper.rb @@ -0,0 +1,92 @@ +module MetadataHelper + + def input_type?(attr, type) + attr["enforce"].include?(type) + end + + def submission_metadata + @metadata ||= JSON.parse(Net::HTTP.get(URI.parse("#{$REST_URL}/submission_metadata?apikey=#{$API_KEY}"))) + end + + def attr_metadata(attr_key) + submission_metadata.select { |attr_hash| attr_hash["attribute"].to_s.eql?(attr_key) }.first + end + + def integer?(attr_label) + input_type?(attr_metadata(attr_label), 'integer') + end + + def date_time?(attr_label) + input_type?(attr_metadata(attr_label), 'date_time') + end + + def textarea?(attr_label) + input_type?(attr_metadata(attr_label), 'textarea') + end + + def enforce_values?(attr) + !attr["enforcedValues"].nil? + end + + def list?(attr_label) + input_type?(attr_metadata(attr_label), "list") + end + + def isOntology?(attr_label) + input_type?(attr_metadata(attr_label), 'isOntology') + end + + + def metadata_categories + submission_metadata.group_by{|x| x['category']}.transform_values{|x| x.map{|attr| attr['attribute']} } + end + def ontology_relation?(attr_label) + relations_attr = metadata_categories['relations'] + !attr_label.to_s.eql?('hasPriorVersion') && relations_attr.include?(attr_label.to_s) + end + + def open_to_add_metadata?(attr_key) + attrs = [:naturalLanguage, :hasLicense, :usedOntologyEngineeringTool, + :accrualPeriodicity, :includedInDataCatalog, :metadataVoc] + attrs = attrs + metadata_categories['relations'].map(&:to_sym).reject{|x| x.eql?(:hasPriorVersion)} + attrs = attrs + metadata_categories['object description properties'].map(&:to_sym) + attrs.include?(attr_key.to_sym) + end + + def attr_uri?(attr_label) + input_type?(attr_metadata(attr_label), "uri") + end + + def boolean?(attr_label) + input_type?(attr_metadata(attr_label), "boolean") + end + + def agent?(attr) + input_type?(attr_metadata(attr), "Agent") + end + + def display_attribute(metadata, value) + return 'N/A' if value.nil? || Array(value).empty? + + if agent?(metadata) + display_agent(value) + elsif metadata.eql?("naturalLanguage") + render LanguageFieldComponent.new(value: value) + elsif metadata.to_s.eql?("hasLicense") + render LicenseFieldComponent.new(value: value) + elsif metadata.to_s.eql?("endpoint") && (value.start_with?("http://sparql.") || value.start_with?("https://sparql.")) + link_to(value, :title => value, :target => "_blank", :style => "border-width:0;") do + image_tag('logos/sparql_logo.png', :title => value, :class => 'logo') + end + elsif date_time?(metadata) + render DateTimeFieldComponent.new(value: value) + elsif attr_uri?(metadata) + render LinkFieldComponent.new(value: value) + elsif input_type?(attr_metadata(metadata), 'contact') + display_contact(value) + else + render TextAreaFieldComponent.new(value: value.to_s) + end + end + +end \ No newline at end of file diff --git a/app/helpers/multi_languages_helper.rb b/app/helpers/multi_languages_helper.rb new file mode 100644 index 000000000..f1c0f8364 --- /dev/null +++ b/app/helpers/multi_languages_helper.rb @@ -0,0 +1,160 @@ +module MultiLanguagesHelper + def portal_lang + session[:locale] || 'en' + end + def request_lang + lang = params[:language] || params[:lang] + lang = portal_lang unless lang + lang.upcase + end + + def portal_language_help_text + "Indicate the language in which the interfaces should appear" + end + def portal_languages + { + en: { badge: nil, disabled: false }, + fr: { badge: 'beta', disabled: false }, + it: { badge: 'coming', disabled: true }, + de: { badge: 'coming', disabled: true } + } + end + + def portal_language_selector + languages = portal_languages + selected_language = portal_lang + selected_language = content_tag(:span, selected_language.upcase, data: { controller: 'tooltip' }, title: portal_language_help_text) + render DropdownButtonComponent.new do |d| + d.header { selected_language } + d.section(divide: false, selected_index: languages.find_index(selected_language)) do |s| + languages.each do |lang, metadata| + s.item do + 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 } } + + if metadata[:disabled] + link_options[:class] = 'disabled-link' + link_options[:disabled] = 'disabled' + end + + link_to(text, "/locale/#{lang}", link_options) + end + end + + end + end + end + + def search_language_help_text + content_tag(:div, style: 'width: 300px; text-align: center') do + "Indicate the language on which to perform the search, restricting text matching exclusively to terms with that language" + 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') + render Input::LanguageSelectorComponent.new(id: id, name: name, enable_all: true, + languages: search_languages, + 'data-select-input-searchable-value': false, + title: search_language_help_text) + + end + + def content_languages(submission = @submission || @submission_latest) + current_lang = request_lang.downcase + submission_lang = submission_languages(submission) + # Transform each language into a select option + submission_lang = submission_lang.map do |lang| + lang = lang.split('/').last.upcase + lang = ISO_639.find(lang.to_s.downcase) + next nil unless lang + [lang.alpha2, lang.english_name] + end.compact + + [submission_lang, current_lang] + end + def content_language_help_text + content_tag(:div, style: 'width: 350px;') do + concat content_tag(:div, "Indicate the language on which the content of this resource will be displayed.") + concat(content_tag(:div, class: "mt-1" ) do + content_tag(:span, "The available languages are specified by the resource admin.") + 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, + languages: languages, + selected: selected || 'all', + 'data-tooltip-interactive-value': true, + 'data-select-input-searchable-value': false, + title: content_language_help_text) + + 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 + end + + def sorted_labels(labels) + Array(labels).sort_by { |label| label['prefLabel'].is_a?(String) ? label['prefLabel'] : label['prefLabel'].last } + end + + def select_language_label(concept_label, platform_languages = %i[en fr]) + concept_value = nil + + concept = language_hash(concept_label) + + return ['@none', concept] if concept.is_a?(String) + + concept = concept.to_h + + platform_languages.each do |lang| + if concept[lang] + concept_value = [lang, concept[lang]] + break + end + end + + concept_value || concept.to_a.first + end + + def main_language_label(label) + select_language_label(label)&.last + end + + def display_in_multiple_languages(label) + label = language_hash(label) + + if label.nil? + 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 + end + end.join) + end + + def selected_language_label(label) + language_hash(label).values.first + end +end diff --git a/app/helpers/ontologies_helper.rb b/app/helpers/ontologies_helper.rb index b4fd475ca..f427c46ec 100644 --- a/app/helpers/ontologies_helper.rb +++ b/app/helpers/ontologies_helper.rb @@ -1,13 +1,61 @@ +require 'iso-639' module OntologiesHelper REST_URI = $REST_URL API_KEY = $API_KEY LANGUAGE_FILTERABLE_SECTIONS = %w[classes schemes collections instances] + + def ontology_retired?(submission) + submission[:status].to_s.eql?('retired') || submission[:deprecated].to_s.eql?('true') + end + def ontology_license_badge(acronym, submission = @submission_latest) + return if submission.nil? + + no_license = submission.hasLicense.blank? + render ChipButtonComponent.new(class: "text-nowrap chip_button_small #{no_license && 'disabled-link'}", type: no_license ? 'static' : 'clickable') do + if no_license + content_tag(:span) do + content_tag(:span, "No license", class: "mx-1") + inline_svg_tag('icons/law.svg', width: "15px") + end + else + link_to_modal(nil, "/ajax/submission/show_licenses/#{acronym}",data: { show_modal_title_value: "Additional license a access rights information"}) do + content_tag(:span, "View license", class: "mx-1") + inline_svg_tag('icons/law.svg') + end + end + + end + end + def ontology_retired_badge(submission, small: false, clickable: true) + return if submission.nil? || !ontology_retired?(submission) + text_color = submission[:status].to_s.eql?('retired') ? 'text-danger bg-danger-light' : 'text-warning bg-warning-light' + text_content = submission[:status].to_s.eql?('retired') ? 'Retired' : 'Deprecated' + style = "#{text_color} #{small && 'chip_button_small'}" + render ChipButtonComponent.new(class: "#{style} mr-1", text: text_content, type: clickable ? 'clickable' : 'static') + end + + def ontology_alternative_names(submission = @submission_latest) + alt_labels = (Array(submission&.alternative) + Array(submission&.hiddenLabel)) + return unless alt_labels.present? + + content_tag(:div, class: 'creation_text') do + concat("It can also be referred to as ") + concat(content_tag(:span, class: 'date_creation_text') do + if alt_labels.length > 1 + concat("#{alt_labels[0..-2].join(', ')} or #{alt_labels.last}.") + else + concat("#{alt_labels.first}.") + end + end) + end + end + def private_ontology_icon(is_private) + raw(content_tag(:i, '', class: 'fas fa-key', title: "Private Ontology")) if is_private + end def browse_filter_section_label(key) labels = { hasFormalityLevel: 'Formality levels', - isOfType: 'Generic Types', + isOfType: 'Ontology types', naturalLanguage: 'Natural languages' } @@ -58,229 +106,30 @@ def additional_details end # Display data catalog metadata under visits (in _metadata.html.haml) - def display_data_catalog(sub) - if !sub.send("includedInDataCatalog").nil? && sub.send("includedInDataCatalog").any? + def display_data_catalog(value) + if !value.nil? && value.any? # Buttons for data catalogs - return content_tag(:section, { :class => "ont-metadata-card ont-included-in-data-catalog-card" }) do - concat(content_tag(:div, { :class => "ont-section-toolbar" }) do - concat(content_tag(:header, "includedInDataCatalog", { :class => "pb-2 font-weight-bold" })) - end) - concat(content_tag(:div, { :class => "" }) do - sub.send("includedInDataCatalog").each do |catalog| - catalog_btn_label = catalog - $DATA_CATALOG_VALUES.each do |cat_uri, cat_label| - if catalog[cat_uri] - catalog_btn_label = cat_label - break - end - end - concat(content_tag(:a, catalog_btn_label, { :class => "btn btn-primary", :href => catalog, :target => "_blank" })) - end - end) + content_tag(:div, { :class => "" }) do + end else - "" + "" end end - # Display data catalog metadata under visits (in _metadata.html.haml) - def display_logo(sub) - logo_attributes = ["logo", "depiction"] - logo_html = "" - logo_attributes.each do |metadata| - if !sub.send(metadata).nil? - puts sub.send(metadata) - logo_html.concat(content_tag(:section, { :class => "ont-metadata-card ont-logo-depiction-card" }) do - concat(content_tag(:div, { :class => "ont-section-toolbar" }) do - concat(content_tag(:header, metadata.capitalize, { :class => "pb-2 font-weight-bold" })) - end) - concat(content_tag(:div, { :class => "" }) do - concat(content_tag(:a, { :href => sub.send(metadata), :title => sub.send(metadata), - :target => "_blank", :style => "border-width:0;" }) do - - concat(content_tag(:img, "", { :title => sub.send(metadata), - :style => "border-width:0;max-width: 100%;", :src => sub.send(metadata).to_s })) - end) - end) - end) - end - end - return logo_html - end - - # Add additional metadata as html for a submission - def additional_metadata(sub) - # Get the list of metadata attribute from the REST API - json_metadata = submission_metadata - metadata_list = {} - # Get extracted metadata and put them in a hash with their label, if one, as value - json_metadata.each do |metadata| - if metadata["extracted"] == true - metadata_list[metadata["attribute"]] = metadata["label"] - end - end - metadata_list = metadata_list.sort + def agent?(sub_metadata, attr) + metadata = sub_metadata.select { |x| x['@id'][attr] }.first + metadata && Array(metadata['enforce']).include?('Agent') + end - html = [] + def display_contact(contacts) + contacts.map do |c| + next unless c.member?(:name) && c.member?(:email) - metadata_not_displayed = ["status", "description", "documentation", "publication", "homepage", "openSearchDescription", "dataDump", "includedInDataCatalog", "logo", "depiction"] - - begin - - metadata_list.each do |metadata, label| - # Don't display documentation, publication, homepage, status and description, they are already in main details - if !metadata_not_displayed.include?(metadata) - # different html build if list or single value - - # METADATA ARRAY - if sub.send(metadata).kind_of?(Array) - if sub.send(metadata).any? - if metadata.eql?("naturalLanguage") - # Special treatment for naturalLanguage: we want the flags in a bootstrap box - # UK is gb: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - lang_codes = [] - - sub.send(metadata).each do |lang| - if (lang.to_s.eql?("en") || lang.to_s.eql?("eng") || lang.to_s.eql?("http://lexvo.org/id/iso639-3/eng")) - # We consider en and eng as english - lang_codes << "gb" - elsif lang.to_s.start_with?("http://lexvo.org") - lang_codes << $LEXVO_TO_FLAG[lang] - else - lang_codes << lang - end - end - - html << content_tag(:tr) do - concat(content_tag(:td, "Natural Language", " ")) - # Display naturalLanguage as flag - concat(content_tag(:td) do - concat(content_tag(:ul, { :class => "f32" }) do - lang_codes.each do |lang_code| - if lang_code.length == 2 - concat(content_tag(:li, "", { :class => "flag #{lang_code}", :style => "margin-right: 0.5em;" })) - else - concat(content_tag(:li, lang_code)) - end - end - end) - end) - end - - else - html << content_tag(:tr) do - if label.nil? - concat(content_tag(:td, metadata.gsub(/(?=[A-Z])/, " "))) - else - concat(content_tag(:td, label)) - end - - metadata_array = [] - sub.send(metadata).each do |metadata_value| - if metadata_value.to_s.start_with?("#{$REST_URL}/ontologies/") - # For URI that links to our ontologies we display a button with only the acronym. And redirect to the UI - # Warning! Redirection is done by removing "data." from the REST_URL. So might not work perfectly everywhere - if metadata_value.to_s.split("/").length < 6 - # for ontologies/ACRONYM we redirect to the UI url - metadata_array.push("#{metadata_value.to_s.split("/")[4..-1].join("/")}") - else - metadata_array.push("#{metadata_value.to_s.split("/")[4..-1].join("/")}") - end - - elsif metadata_value.to_s =~ /\A#{URI::regexp(['http', 'https'])}\z/ - # Don't create a link if it not an URI - metadata_array.push("#{metadata_value.to_s}") - else - metadata_array.push(metadata_value) - end - end - concat(content_tag(:td, raw(metadata_array.join(", ")))) - end - end - end - else - - # SINGLE METADATA - if !sub.send(metadata).nil? - html << content_tag(:tr) do - if label.nil? - concat(content_tag(:td, metadata.gsub(/(?=[A-Z])/, " "))) - else - concat(content_tag(:td, label)) - end - if (metadata.to_s.eql?("hasLicense")) - if (sub.send(metadata).to_s.start_with?("http://creativecommons.org/licenses") || sub.send(metadata).start_with?("https://creativecommons.org/licenses")) - concat(content_tag(:td) do - concat(content_tag(:a, { :rel => "license", :alt => "Creative Commons License", - :href => sub.send(metadata), :target => "_blank", :style => "border-width:0", :title => sub.send(metadata), - :src => "https://i.creativecommons.org/l/by/4.0/88x31.png" }) do - - concat(content_tag(:img, "", { :rel => "license", :alt => "Creative Commons License", :title => sub.send(metadata), - :style => "border-width:0", :src => "https://i.creativecommons.org/l/by/4.0/88x31.png" })) - end) - end) - - elsif (sub.send(metadata).to_s.start_with?("http://opensource.org/licenses") || sub.send(metadata).start_with?("https://opensource.org/licenses")) - concat(content_tag(:td) do - concat(content_tag(:a, { :rel => "license", :alt => "Open Source License", - :href => sub.send(metadata), :title => sub.send(metadata), :target => "_blank", :style => "border-width:0;", - :src => "https://opensource.org/files/osi_logo_bold_100X133_90ppi.png" }) do - - concat(content_tag(:img, "", { :rel => "license", :alt => "Open Source License", :title => sub.send(metadata), - :style => "height: 80px; border-width:0;", :src => "https://opensource.org/files/osi_logo_bold_100X133_90ppi.png" })) - end) - end) - - else - concat(content_tag(:td) do - concat(content_tag(:a, sub.send(metadata), { :rel => "license", :href => sub.send(metadata), :target => "_blank" })) - end) - end - - elsif (metadata.to_s.eql?("endpoint") && (sub.send(metadata).start_with?("http://sparql.") || sub.send(metadata).start_with?("https://sparql."))) - concat(content_tag(:td) do - concat(content_tag(:a, { :href => sub.send(metadata), :title => sub.send(metadata), - :target => "_blank", :style => "border-width:0;" }) do - - concat(image_tag('logos/sparql_logo.png', title: sub.send(metadata), class: 'logo')) - end) - end) - - elsif sub.send(metadata).to_s.start_with?("#{$REST_URL}/ontologies/") - # For URI that links to our ontologies we display a button with only the acronym. And redirect to the UI - # Warning! Redirection is done by removing "data." from the REST_URL. So might not work perfectly everywhere - if sub.send(metadata).to_s.split("/").length < 6 - # for ontologies/ACRONYM we redirect to the UI url - concat(content_tag(:td) do - concat(content_tag(:a, sub.send(metadata).to_s.split("/")[4..-1].join("/"), { :class => "btn btn-primary", - :href => sub.send(metadata).sub("data.", ""), :target => "_blank", :title => sub.send(metadata) })) - end) - else - concat(content_tag(:td) do - concat(content_tag(:a, sub.send(metadata).to_s.split("/")[4..-1].join("/"), { :class => "btn btn-primary", - :href => sub.send(metadata), :target => "_blank", :title => sub.send(metadata) })) - end) - end - - else - if sub.send(metadata).to_s =~ /\A#{URI::regexp(['http', 'https'])}\z/ - # Don't create a link if it not an URI - concat(content_tag(:td, raw("#{sub.send(metadata).to_s}"))) - else - concat(content_tag(:td, raw(sub.send(metadata).to_s))) - end - end - end - end - end - end - end - rescue => e - LOG.add :debug, "Unable to retrieve additional ontology metadata" - LOG.add :debug, "error: #{e}" - LOG.add :debug, "error message: #{e.message}" - end - html.join("") + formatted_name = c[:name].titleize + formatted_email = c[:email].downcase + "#{formatted_name} (#{formatted_email})" + end&.join(" and ") end def count_links(ont_acronym, page_name = 'summary', count = 0) @@ -301,29 +150,34 @@ def classes_link(ontology, count) # Creates a link based on the status of an ontology submission def download_link(submission, ontology = nil) ontology ||= @ontology + links = [] if ontology.summaryOnly if submission.homepage.nil? - link = 'N/A - metadata only' + links << { href: '', label: 'N/A - metadata only' } else uri = submission.homepage - link = "Home Page" + links << { href: uri, label: 'Home Page' } end else uri = submission.id + "/download?apikey=#{get_apikey}" - link = "#{submission.pretty_format}" + links << { href: uri, label: submission.pretty_format } latest = ontology.explore.latest_submission({ include_status: 'ready' }) if latest && latest.submissionId == submission.submissionId - link += " | CSV" + links << { href: "#{ontology.id}/download?apikey=#{get_apikey}&download_format=csv", label: "CSV" } if !latest.hasOntologyLanguage.eql?('UMLS') - link += " | RDF/XML" + links << { href: "#{ontology.id}/download?apikey=#{get_apikey}&download_format=rdf", label: "RDF/XML" } end end unless submission.diffFilePath.nil? uri = submission.id + "/download_diff?apikey=#{get_apikey}" - link = link + " | DIFF" + links << { href: uri, label: "DIFF" } end end - link + links + end + + def link?(string) + string.to_s.start_with?('http://') || string.to_s.start_with?('https://') end def mappings_link(ontology, count) @@ -348,8 +202,10 @@ def status_link(submission, latest = false, target = '') version_link + status_text end - def submission_status2string(sub) - return '' if sub.submissionStatus.nil? + + def submission_status2string(data) + return '' if data[:submissionStatus].nil? + # Massage the submission status into a UI string # submission status values, from: # https://github.com/ncbo/ontologies_linked_data/blob/master/lib/ontologies_linked_data/models/submission_status.rb @@ -357,7 +213,7 @@ def submission_status2string(sub) # Strip the URI prefix from the status codes (works even if they are not URIs) # The order of the codes must be assumed to be random, it is not an entirely # predictable sequence of ontology processing stages. - codes = sub.submissionStatus.map { |s| s.split('/').last } + codes = data[:submissionStatus].map { |s| s.split('/').last } errors = codes.select { |c| c.start_with? 'ERROR' }.map { |c| c.gsub("_", " ").split(/(\W)/).map(&:capitalize).join }.compact status = [] status.push('Parsed') if (codes.include? 'RDF') && (codes.include? 'RDF_LABELS') @@ -373,6 +229,38 @@ def submission_status2string(sub) '(' + status.join(', ') + ')' end + def status_string(data) + return '' unless data.present? && data[:submissionStatus].present? + + submission_status2string(data) + end + + def submission_status_ok?(status) + status.include?('Parsed') && !status.include?('Error') + end + + def submission_status_error?(status) + !status.include?('Parsed') && status.include?('Error') + end + + def submission_status_warning?(status) + status.include?('Parsed') && status.include?('Error') + end + + def submission_status_icons(status) + if submission_status_ok?(status) + "success-icon.svg" + elsif submission_status_error?(status) + 'error-icon.svg' + elsif status == '(Archived)' + 'archive.svg' + elsif submission_status_warning?(status) + "alert-triangle.svg" + else + "info.svg" + end + end + # Link for private/public/licensed ontologies def visibility_link(ontology) ont_url = "/ontologies/#{ontology.acronym}" # 'ontology' is NOT a submission here @@ -388,6 +276,24 @@ def visibility_link(ontology) "#{link_name}" end + def show_category_name(domain) + acronym = domain.split('/').last.upcase + category = LinkedData::Client::Models::Category.find_by_acronym(acronym).first + category ? category.name : acronym.titleize + end + + def show_group_name(domain) + acronym = domain.split('/').last.upcase + category = LinkedData::Client::Models::Group.find_by_acronym(acronym).first + category ? category.name : acronym.titleize + end + + def show_group_name(domain) + acronym = domain.split('/').last.upcase + category = LinkedData::Client::Models::Group.find_by_acronym(acronym).first + category ? category.name : acronym + end + def visits_data(ontology = nil) ontology ||= @ontology @@ -425,6 +331,14 @@ def current_section (params[:p]) ? params[:p] : 'summary' end + def link_to_section(section_title) + link_to(section_name(section_title), ontology_path(@ontology.acronym, p: section_title), + id: "ont-#{section_title}-tab", class: "nav-link #{selected_section?(section_title) ? 'active show' : ''}", + data: { action: 'click->ontology-viewer-tabs#selectTab', + toggle: "tab", target: "#ont_#{section_title}_content", 'bp-ont-page': section_title, + 'bp-ont-page-name': ontology_viewer_page_name(@ontology.name, @concept&.prefLabel || '', section_title) }) + end + def selected_section?(section_title) current_section.eql?(section_title) end @@ -450,7 +364,9 @@ def lazy_load_section(section_title, &block) if current_section.eql?(section_title) block.call else - render TurboFrameComponent.new(id: section_title, src: "/ontologies/#{@ontology.acronym}?p=#{section_title}", target: '_top', data: { "turbo-frame-target": "frame" }) + render TurboFrameComponent.new(id: section_title, src: "/ontologies/#{@ontology.acronym}?p=#{section_title}", + loading: Rails.env.development? ? "lazy" : "eager", + target: '_top', data: { "turbo-frame-target": "frame" }) end end @@ -465,17 +381,47 @@ def visits_chart_dataset(visits_data) }].to_json end + def submission_ready?(submission) + Array(submission&.submissionStatus).include?('RDF') + end + def sections_to_show sections = ['summary'] - unless @ontology.summaryOnly || @submission_latest.nil? - sections += %w[classes properties notes mappings] + if !@ontology.summaryOnly && submission_ready?(@submission_latest) + sections += ['classes'] + sections += %w[properties] sections += %w[schemes collections] if skos? sections += %w[instances] unless skos? - sections += %w[widgets] + sections += %w[notes mappings widgets] end sections end + + def not_ready_submission_alert(ontology: @ontology, submission: @submission) + if ontology.admin?(session[:user]) + status = status_string(submission) + type = nil + message = nil + if submission_status_error?(status) + type = 'danger' + message = "The ontology processing failed, with the current statuses: #{status}" + elsif submission_status_warning?(status) + message = "The ontology parsing succeeded, but some processing steps failed, here are the current statuses: #{status}" + type = 'warning' + + elsif !submission_ready?(submission) + type = 'info' + if submission.nil? + message = "Upload an ontology. Sections such as #{ontology_data_sections.join(', ')} will be available once done." + else + message = "The ontology is processing. Sections such as #{ontology_data_sections.join(', ')} will be available once processing is complete." + end + end + render Display::AlertComponent.new(message: message, type: type, button: Buttons::RegularButtonComponent.new(id:'regular-button', value: "Contact support", variant: "primary", href: "/feedback", color: type, size: "slim")) if type + end + end + def dispaly_complex_text(definitions) html = "" definitions.each do |definition| @@ -487,5 +433,210 @@ def dispaly_complex_text(definitions) end return html.html_safe end + + 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_to_modal(nil, link, class: "btn", id:'fair-details-link', + data: { show_modal_title_value: "Edit natural languages of #{ontology.acronym}", show_modal_size_value: 'modal-md' }) do + render ChipButtonComponent.new(type: 'clickable', class: 'admin-background chip_button_small' ) do + ("Click here to edit available languages" + content_tag(:i, "", class: "fas fa-lg fa-edit")).html_safe + end + end + end + def language_selector_tag(name) + content_language_selector(id: name, name: name) + end + + def language_selector_hidden_tag(section) + hidden_field_tag "language_selector_hidden_#{section}", '', + data: { controller: "language-change", 'language-change-section-value': section, action: "change->language-change#dispatchLangChangeEvent" } + end + + + + def display_complex_text(definitions) + html = "" + definitions.each do |definition| + if definition.is_a?(String) + html += '

    ' + definition + '

    ' + elsif definition.respond_to?(:uri) && definition.uri + html += render LinkFieldComponent.new(value: definition.uri) + else + html += display_in_multiple_languages(definition) + end + end + return html.html_safe + end + + def new_view_path(ont_id) + ont_id_esc = CGI.escape(ont_id) + if session[:user].nil? + "/login?redirect=#{escape("/ontologies/new?ontology[viewOf]=#{ont_id_esc}")}" + else + "/ontologies/new?ontology[viewOf]=#{ont_id_esc}" + end + end + + def new_element_link(title, link) + if session[:user].nil? + link = "/login?redirect=#{link}" + end + + link_to(link, title: title, class: "mx-1") do + inline_svg_tag("icons/plus.svg", width: '15px', height: '15px') + end + end + + def ontology_icon_links(links, submission_latest) + links.map do |icon, attr, label| + value = submission_latest.nil? ? nil : submission_latest.send(attr) + + link_options = { + style: "text-decoration: none; width: 30px; height: 30px" + } + + if Array(value).empty? + link_options[:class] = 'disabled-icon' + link_options[:disabled] = 'disabled' + title = label + else + title = label + '
    ' + link_to(Array(value).first) + end + + content_tag(:span, data: {controller:"tooltip" } , title: title) do + link_to(inline_svg("#{icon}.svg", width: "32", height: '32'), + Array(value).first || '', link_options) + end + end.join.html_safe + end + + def ontology_depiction_card + return if Array(@submission_latest&.depiction).empty? + + render Layout::CardComponent.new do + list_container(@submission_latest.depiction) do |depiction_url| + render Display::ImageComponent.new(src: depiction_url) + end + end + end + + def metadata_formats_buttons + render SummarySectionComponent.new(title: 'Download metadata (profile/syntax)', show_card: false) do + content_tag :div, data: { controller: 'metadata-downloader' } do + horizontal_list_container([ + ['NQuads', 'MOD/n-triple'], + ['JsonLd', 'MOD/json-ld'], + ['XML', 'MOD/rdf-xml'] + ]) do |format, label| + render ChipButtonComponent.new(type: 'clickable', 'data-action': "click->metadata-downloader#download#{format}") do + concat content_tag(:span, label) + concat content_tag(:span, inline_svg("summary/download.svg", width: '15px', height: '15px')) + end + end + end + end + + end + + def count_subscriptions(ontology_id) + users = LinkedData::Client::Models::User.all(include: 'subscription', display_context: false, display_links: false) + users.select { |u| u.subscription.find { |s| s.ontology.eql?(ontology_id) } }.count + end + + def new_submission_button + return unless @ontology.admin?(session[:user]) + render RoundedButtonComponent.new(link: new_ontology_submission_path(@ontology.acronym), icon: 'icons/plus.svg', + size: 'medium', title: 'Add new submission') + end + + def ontology_edit_button + return unless @ontology.admin?(session[:user]) + render RoundedButtonComponent.new(link: edit_ontology_path(@ontology.acronym), icon: 'edit.svg', + size: 'medium', + title: 'Edit metadata') + end + + def upload_ontology_button + if session[:user].nil? + render Buttons::RegularButtonComponent.new(id: "upload-ontology-button", value: t('home.ontology_upload_button'), variant: "secondary", state: "regular", href: "/login?redirect=/ontologies/new") do |btn| + btn.icon_left do + inline_svg_tag "upload.svg" + end + end + else + render Buttons::RegularButtonComponent.new(id: "upload-ontology-button", value: t('home.ontology_upload_button'), variant: "secondary", state: "regular", href: new_ontology_path) do |btn| + btn.icon_left do + inline_svg_tag "upload.svg" + end + end + end + end + + def submission_json_button + render RoundedButtonComponent.new(link: "#{(@submission_latest || @ontology).id}?display=all", + target: '_blank', + size: 'medium', + title: 'Go to API') + end + + + def projects_field(projects, ontology_acronym = @ontology.acronym) + render FieldContainerComponent.new do |f| + f.label do + concat "Projects using #{ontology_acronym}" + concat new_element_link('Create new project', new_project_path) + end + + if projects.empty? + empty_state_message("No projects using #{ontology_acronym}") + else + horizontal_list_container(projects) do |project| + render ChipButtonComponent.new(url: project_path(project.acronym), text: project.name, type: "clickable") + end + end + end + end + def ontology_import_code(submission = @submission_latest ) + # TODO remove or reuse somewhere elese + prefix = submission.preferredNamespacePrefix + namespace= submission.preferredNamespaceUri || submission.URI + return if prefix.blank? && namespace.blank? + + render ChipButtonComponent.new do + concat content_tag(:span , "@prefix ", style: 'color: #FA7070') + concat content_tag(:span , "#{prefix}: ", style: 'color: var(--primary-color);font-weight: 700;') + concat content_tag(:span , "<#{namespace}>", style: 'color:#9999a9;') + end + end + + def metadata_vocabulary_display(vocabularies) + vocabularies_data = attribute_enforced_values('metadataVoc') + horizontal_list_container(vocabularies) do |voc| + tooltip = vocabularies_data[voc] || nil + tooltip = "#{tooltip} (#{link_to(voc)})" if tooltip + label = prefix_property_url(voc, nil) || voc + + label = content_tag(:span, data: {controller:'tooltip'}, title: tooltip) do + render(ExternalLinkTextComponent.new(text: label)) + end + render ChipButtonComponent.new(url: voc, text: label, type: 'clickable') + end + end + + def summary_only? + @ontology&.summaryOnly || @submission&.isRemote&.eql?('3') + end + + def ontology_pull_location? + !(@submission.pullLocation.nil? || @submission.pullLocation.empty?) + end + + private + + def submission_languages(submission = @submission) + Array(submission&.naturalLanguage).map { |natural_language| natural_language["iso639"] && natural_language.split('/').last }.compact + end end diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index 799e52b13..3e024feec 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -1,11 +1,11 @@ module SchemesHelper def get_schemes(ontology) - ontology.explore.schemes + ontology.explore.schemes(language: request_lang) end def get_scheme(ontology, scheme_uri) - ontology.explore.schemes({ include: 'all' }, scheme_uri) + ontology.explore.schemes({ include: 'all', language: request_lang}, scheme_uri) end def get_scheme_label(scheme) @@ -24,36 +24,32 @@ def get_schemes_labels(schemes, main_uri) schemes_labels = [] schemes.each do |x| id = x['@id'] - label = get_scheme_label(x) + label = select_language_label(get_scheme_label(x)) if id.eql? main_uri - label = "#{label} (main)" unless label.empty? + label[1] = "#{label[1]} (main)" unless label[0].empty? selected_label = { 'prefLabel' => label, '@id' => id } else schemes_labels.append( { 'prefLabel' => label, '@id' => id }) end end - schemes_labels.sort_by! { |s| s['prefLabel']} - if selected_label - schemes_labels.unshift selected_label - end + schemes_labels = sorted_labels(schemes_labels) + schemes_labels.unshift selected_label if selected_label [schemes_labels, selected_label] end def concept_label_to_show(submission: @submission_latest) - submission&.hasOntologyLanguage == 'SKOS' ? 'Concepts' : 'Classes' + submission&.hasOntologyLanguage == 'SKOS' ? 'concepts' : 'classes' end def section_name(section) - if section.eql?('classes') - concept_label_to_show(submission: @submission_latest || @submission) - else - section.capitalize - end + section = concept_label_to_show(submission: @submission_latest || @submission) if section.eql?('classes') + section + #t("ontology_details.sections.#{section}" , section) end - def scheme_path(scheme_id = '') - "/ontologies/#{@ontology.acronym}/schemes/show_scheme?id=#{escape(scheme_id)}" + def scheme_path(scheme_id = '', language = '') + "/ontologies/#{@ontology.acronym}/schemes/show_scheme?id=#{escape(scheme_id)}&lang=#{language}" end def no_main_scheme? @@ -65,12 +61,12 @@ def no_schemes? end def no_main_scheme_alert - render AlertMessageComponent.new(id: 'main-scheme-empty-info') do + render Display::AlertComponent.new do 'no main scheme defined in the URI attribute' end end def no_schemes_alert - render AlertMessageComponent.new(id: 'schemes-empty-info') do + render Display::AlertComponent.new do "#{@ontology.acronym} does not contain schemes (skos:ConceptScheme)" end end @@ -84,21 +80,29 @@ def schemes_data def tree_link_to_schemes(schemes_labels, main_scheme_label, selected_scheme_id) out = '' - schemes_labels.sort_by { |s| [s['prefLabel']] }.each do |s| + + sorted_labels(schemes_labels).each do |s| next unless main_scheme_label.nil? || s['prefLabel'] != main_scheme_label['prefLabel'] - li = <<-EOS -
  • - - #{get_scheme_label(s)} - -
  • + out << <<-EOS +
  • + #{link_to_scheme(s, selected_scheme_id)} +
  • EOS - out << li end out end + def link_to_scheme(scheme, selected_scheme_id) + pref_label_lang, pref_label_html = get_scheme_label(scheme) + tooltip = pref_label_lang.to_s.eql?('@none') ? '' : "data-controller='tooltip' data-tooltip-position-value='right' title='#{pref_label_lang.upcase}'" + <<-EOS + + #{pref_label_html} + + EOS + end end diff --git a/app/helpers/submission_inputs_helper.rb b/app/helpers/submission_inputs_helper.rb new file mode 100644 index 000000000..90c52a5a4 --- /dev/null +++ b/app/helpers/submission_inputs_helper.rb @@ -0,0 +1,488 @@ +module SubmissionInputsHelper + + class SubmissionMetadataInput + include MetadataHelper + + def initialize(attribute_key:, attr_metadata:, submission: nil, label: nil) + @attribute_key = attribute_key + @attr_metadata = attr_metadata + @submission = submission + @label = label + end + + def attr + @attribute_key + end + + alias :attr_key :attr + + def attr_key + @attribute_key + end + + def name + "submission[#{@attribute_key}]" + end + + def values + @submission.send(@attr_metadata['attribute']) + rescue StandardError + nil + end + + def help_text + CGI.unescape_html(@attr_metadata['helpText']) if @attr_metadata['helpText'] + end + + def label + @label || @attr_metadata['label'] || @attr_metadata['attribute'].humanize + end + + def type?(type) + @attr_metadata['enforce'].include?(type) + end + + def metadata + @attr_metadata + end + + def required? + Array(@attr_metadata['enforce']).include?('existence') + end + end + + # @param attr_key String + def attribute_input(attr_key, long_text: false, label: nil, show_tooltip: true, max_date: nil) + attr = SubmissionMetadataInput.new(attribute_key: attr_key, submission: @submission, label: label, + attr_metadata: attr_metadata(attr_key)) + + + if attr.type?('Agent') + if attr.type?('list') + generate_list_agent_input(attr) + else + generate_agent_input(attr) + end + elsif attr.type?('integer') + generate_integer_input(attr) + elsif attr.type?('date_time') + if attr.type?('list') + generate_list_date_input(attr, max_date: max_date) + else + generate_date_input(attr, max_date: max_date) + end + elsif attr.type?('textarea') + generate_textarea_input(attr) + elsif enforce_values?(attr) + + if attr.type?('list') + generate_select_input(attr, multiple: true) + elsif attr.type?('boolean') + generate_boolean_input(attr) + else + generate_select_input(attr) + end + elsif attr.type?('isOntology') + generate_select_input(attr, multiple: attr['enforce'].include?('list')) + elsif attr.type?('uri') + generate_url_input(attr) + elsif attr.type?('boolean') + generate_boolean_input(attr) + else + # If input a simple text + name = attr.name + label = attr_header_label(attr, show_tooltip: show_tooltip) + if attr.type?('list') + generate_list_text_input(attr) + elsif attr.metadata['attribute'].to_s.eql?('URI') + url_input(name: name, label: label, value: @submission.URI) + elsif long_text + text_area_input(name: name, label: label, + value: attr.values) + else + text_input(name: name, label: label, + value: attr.values) + end + end + + end + + def ontology_name_input(ontology = @ontology) + text_input(name: 'ontology[name]', value: ontology.name) + end + + def ontology_acronym_input(ontology = @ontology, update: @is_update_ontology) + out = text_input(name: 'ontology[acronym]', value: ontology.acronym, disabled: update) + out += hidden_field_tag('ontology[acronym]', ontology.acronym) if update + out + end + + def ontology_administered_by_input(ontology = @ontology, users_list = @user_select_list) + unless users_list + users_list = LinkedData::Client::Models::User.all(include: "username").map { |u| [u.username, u.id] } + users_list.sort! { |a, b| a[1].downcase <=> b[1].downcase } + end + select_input(label: "Administrators", name: "ontology[administeredBy]", values: users_list, selected: ontology.administeredBy || session[:user].id, multiple: true) + end + + def ontology_categories_input(ontology = @ontology, categories = @categories) + categories ||= LinkedData::Client::Models::Category.all(display_links: false, display_context: false) + + render Input::InputFieldComponent.new(name: '', label: 'Categories') do + content_tag(:div, class: 'upload-ontology-chips-container') do + hidden_field_tag('ontology[hasDomain][]') + + categories.map do |category| + category_chip_component(id: category[:acronym], name: "ontology[hasDomain][]", + object: category, value: category[:id], + checked: ontology.hasDomain&.any? { |x| x.eql?(category[:id]) }) + end.join.html_safe + end + end + end + + def has_ontology_language_input(submission = @submission) + render Layout::RevealComponent.new(init_show: submission.hasOntologyLanguage&.eql?('SKOS'), show_condition: 'SKOS') do |c| + c.button do + attribute_input("hasOntologyLanguage") + end + content_tag(:div, class: "upload-ontology-desc") do + content_tag(:div) do + "SKOS vocabularies submitted to BioPortal must contain a minimum of one concept scheme and top concept assertion. Please + refer to the NCBO wiki for a more #{link_to(ExternalLinkTextComponent.new(text: 'detailed explanation').call, "#seethewiki")} with examples.".html_safe + end + end + end + end + + def ontology_groups_input(ontology = @ontology, groups = @groups) + groups ||= LinkedData::Client::Models::Group.all(display_links: false, display_context: false) + + render Input::InputFieldComponent.new(name: '', label: 'Groups') do + content_tag(:div, class: 'upload-ontology-chips-container') do + hidden_field_tag('ontology[group][]') + + groups.map do |group| + group_chip_component(name: "ontology[group][]", id: group[:acronym], + object: group, value: group[:id], + checked: ontology.group&.any? { |x| x.eql?(group[:id]) }) + end.join.html_safe + end + end + end + + def ontology_visibility_input(ontology = @ontology) + unless @user_select_list + @user_select_list = LinkedData::Client::Models::User.all(include: "username").map { |u| [u.username, u.id] } + @user_select_list.sort! { |a, b| a[1].downcase <=> b[1].downcase } + end + + render(Layout::RevealComponent.new(init_show: ontology.viewingRestriction&.eql?('private'), show_condition: 'private')) do |c| + c.button do + select_input(label: "Visibility", name: "ontology[viewingRestriction]", required: true, + values: %w[public private], + selected: ontology.viewingRestriction) + end + content_tag(:div, class: 'upload-ontology-input-field-container') do + select_input(label: "Add or remove accounts that are allowed to see this ontology in #{portal_name}.", name: "ontology[acl]", values: @user_select_list, selected: ontology.acl, multiple: true) + end + end + end + + def ontology_view_of_input(ontology = @ontology) + render Layout::RevealComponent.new(init_show: ontology.view?) do |c| + c.button do + content_tag(:span, class: 'd-flex') do + switch_input(id: 'ontology_isView', name: 'ontology[isView]', label: 'Is this ontology a view of another ontology?', checked: ontology.view?, style: 'font-size: 14px;') + end + end + + content_tag(:div) do + render partial: "shared/ontology_picker_single", locals: { placeholder: "", field_name: "viewOf", selected: ontology.viewOf } + end + end + end + + def contact_input(label: '', name: 'Contact', show_help: true) + attr = SubmissionMetadataInput.new(attribute_key: 'contact', attr_metadata: attr_metadata('contact')) + render Input::InputFieldComponent.new(name: '', label: attr_header_label(attr, label, show_tooltip: show_help), + error_message: attribute_error(:contact)) do + + render NestedFormInputsComponent.new(object_name: 'contact', default_empty_row: true) do |c| + c.header do + content_tag(:div, "#{name} Name", class: 'w-50') + content_tag(:div, "#{name} Email", class: 'w-50') + end + + c.template do + content_tag(:div, class: 'd-flex my-1') do + out = content_tag(:div, class: ' w-50 mr-2') do + text_input(label: '', name: 'submission[contact][NEW_RECORD][name]', value: '', error_message: '') + end + out + content_tag(:div, class: ' w-50') do + text_input(label: '', name: 'submission[contact][NEW_RECORD][email]', value: '', error_message: '') + end + end + end + + Array(@submission.contact).each_with_index do |contact, i| + c.row do + content_tag(:div, class: 'd-flex my-1') do + out = content_tag(:div, class: 'w-50 mr-2') do + text_input(label: '', name: "submission[contact][#{i}][name]", value: contact['name'], error_message: '') + end + out + content_tag(:div, class: 'w-50') do + text_input(label: '', name: "submission[contact][#{i}][email]", value: contact['email'], error_message: '') + end + end + end + end + end + end + end + + # @param attr_key string + def attr_label(attr_key, label = nil, attr_metadata: attr_metadata(attr_key), show_tooltip: true) + + data = SubmissionMetadataInput.new(attribute_key: attr_key.to_s, attr_metadata: attr_metadata) + if show_tooltip + attr_header_label(data, label, show_tooltip: show_tooltip) + else + label || data.label + end + end + + private + + def agent_type(attr) + if input_type?(attr, 'is_person') + 'person' + elsif input_type?(attr, 'is_organization') + 'organization' + else + '' + end + end + + def generate_integer_input(attr) + # TODO to update to use a component + number_field object_name, attr.metadata['attribute'].to_s.to_sym, value: @submission.send(attr.metadata['attribute']), class: 'metadataInput form-control' + end + + def generate_agent_input(attr) + attr_key = attr.metadata['attribute'].to_s + agent = attr.values + random_id = rand(100_000..999_999).to_s + render Input::InputFieldComponent.new(name: '', label: attr_header_label(attr), error_message: attribute_error(attr.metadata['attribute'])) do + render TurboFrameComponent.new(id: "submission_#{attr_key}_#{random_id}") do + if agent + render partial: 'agents/agent_show', locals: { agent_id: random_id, + agent: agent, + name_prefix: attr.name, + parent_id: "submission_#{attr_key}", + edit_on_modal: false, deletable: true } + else + render AgentSearchInputComponent.new(id: random_id, agent_type: agent_type(attr.metadata), + parent_id: "submission_#{attr_key}", + edit_on_modal: false, + name_prefix: attr.name, + deletable: true) + end + end + end + end + + def generate_list_agent_input(attr) + render Input::InputFieldComponent.new(name: '', error_message: attribute_error(attr.metadata['attribute'])) do + render NestedAgentSearchInputComponent.new(label: attr_header_label(attr), + agents: attr.values, + agent_type: agent_type(attr.metadata), + name_prefix: attr.name, + parent_id: attr.attr) + end + + end + + def generate_list_date_input(attr, max_date: nil) + generate_list_field_input(attr, attr.name, attr_header_label(attr), attr.values) do |value, row_name, id| + date_input(label: '', name: row_name, + value: value, + max_date: max_date) + end + + end + + def generate_date_input(attr, max_date: nil) + date_input(label: attr_header_label(attr), name: attr.name, + value: attr.values, + max_date: max_date) + end + + def generate_textarea_input(attr) + text_input(name: attr.name, + value: attr.values) + end + + def generate_select_input(attr, multiple: false) + name = attr.name + label = attr_header_label(attr) + metadata_values, select_values = selected_values(attr, enforced_values(attr)) + + if !multiple && !attr.required? + select_values << ['', ''] + metadata_values = '' if metadata_values.nil? + end + + select_input(name: name, label: label, values: select_values, + selected: metadata_values, multiple: multiple, required: attr.required?, + open_to_add: open_to_add_metadata?(attr.attr_key)) + end + + def generate_list_field_input(attr, name, label, values, &block) + render Input::InputFieldComponent.new(name: '', error_message: attribute_error(attr.attr)) do + render NestedFormInputsComponent.new do |c| + c.header do + label + end + c.template do + block.call('', "#{name}[NEW_RECORD]", attr.attr.to_s + '_' + @ontology.acronym) + end + + c.empty_state do + hidden_field_tag "#{name}[#{Array(values).size}]" + end + + Array(values).each_with_index do |metadata_val, i| + c.row do + block.call(metadata_val, "#{name}[#{i}]", "submission_#{attr.attr.to_s}" + '_' + @ontology.acronym) + end + end + end + end + + end + + def generate_url_input(attr) + label = attr_header_label(attr) + values = attr.values + name = attr.name + + is_relation = ontology_relation?(attr.attr_key) + if attr.type?('list') + if is_relation + generate_ontology_select_input(name, label, values, true) + else + generate_list_field_input(attr, name, label, values) do |value, row_name, id| + url_input(label: '', name: row_name, value: value) + end + end + else + if is_relation + generate_ontology_select_input(name, label, values, false) + else + url_input(label: label, name: name, value: values) + end + end + end + + def generate_ontology_select_input(name, label, selected, multiple) + unless @ontology_acronyms + @ontology_acronyms = LinkedData::Client::Models::Ontology.all(include: 'acronym,name', display_links: false, display_context: false, include_views: true) + .map { |x| ["#{x.name} (#{x.acronym})", x.id.to_s] } + @ontology_acronyms << ['', ''] + end + + input = '' + + input = hidden_field_tag("#{name}[]") if multiple + + input + select_input(id: name, name: name, + label: label, values: @ontology_acronyms + Array(selected), + selected: selected, multiple: multiple, + open_to_add: true) + end + + def generate_list_text_input(attr) + label = attr_header_label(attr) + values = attr.values || [''] + name = attr.name + generate_list_field_input(attr, name, label, values) do |value, row_name, id| + text_input(label: '', name: row_name, value: value) + end + end + + def generate_boolean_input(attr) + value = attr.values + value = value.to_s unless value.nil? + name = attr.name + content_tag(:div, class: 'd-flex') do + switch_input(id: name, name: name, label: attr_header_label(attr), checked: value.eql?('true'), value: value, + boolean_switch: true, + style: 'font-size: 14px;') + end + end + + def enforce_values?(attr) + !attr.metadata['enforcedValues'].nil? + end + + def enforced_values(attr) + attr.metadata['enforcedValues'].collect { |k, v| [v || k, k] } + end + + def selected_values(attr, enforced_values) + metadata_values = attr.values + select_values = enforced_values + + if metadata_values.kind_of?(Array) + metadata_values.map do |metadata| + select_values << metadata unless select_values.flatten.include?(metadata) + end + elsif !select_values.flatten.include?(metadata_values) && !metadata_values.to_s.empty? + select_values << metadata_values + end + [metadata_values, select_values] + end + + private + + def attr_header_label(attr, label = nil, show_tooltip: true) + label ||= attr.label + return '' if label.nil? || label.empty? + + content_tag(:div) do + tooltip_span = render(Display::InfoTooltipComponent.new(text: attribute_help_text(attr))) + html = content_tag(:span, label) + html += content_tag(:span, '*', class: "text-danger") if attr.required? + html += content_tag(:span, tooltip_span, class: 'ml-1') if show_tooltip + html + end + end + + def attribute_help_text(attr) + label = attr.label + help = attr.help_text + required = attr.required? + attr = attr.metadata + attribute = !attr['namespace'].nil? ? "#{attr['namespace']}:#{attr['attribute']}" : "bioportal:#{attr['attribute']}" + + title = content_tag(:span, "#{label} (#{attribute})") + title += content_tag(:span, 'required', class: 'badge badge-danger mx-1') if required + + render SummarySectionComponent.new(title: title, show_card: false) do + help_text = '' + unless attr['metadataMappings'].nil? + help_text += render(FieldContainerComponent.new(label: 'Equivalents', value: attr['metadataMappings'].join(', '))) + end + + unless attr['enforce'].nil? || attr['enforce'].empty? + help_text += render(FieldContainerComponent.new(label: 'Validators', value: attr['enforce'].map do |x| + content_tag(:span, x.humanize, class: 'badge badge-primary mx-1') + end.join.html_safe)) + end + + unless attr['helpText'].nil? + help_text += render(FieldContainerComponent.new(label: 'Help text ', value: help.html_safe)) + end + + help_text + end + end +end \ No newline at end of file diff --git a/app/helpers/submissions_helper.rb b/app/helpers/submissions_helper.rb index bf2a48bb4..ba4aa9da0 100644 --- a/app/helpers/submissions_helper.rb +++ b/app/helpers/submissions_helper.rb @@ -1,8 +1,28 @@ module SubmissionsHelper + def metadata_help_link + content_tag(:div, class: 'edit-ontology-desc') do + html = content_tag(:div) do + content_tag(:div, 'Please fill in basic general information about your ontology.') + + content_tag(:span, 'AgroPortal will automatically extract metadata properties declared for the owl:Ontology object in the source file, ') + + content_tag(:span, style: 'width: 10px; height: 10px') do + link_to(render(ExternalLinkTextComponent.new(text: 'see guidelines and recommendations for metadata here:')), Rails.configuration.settings.links[:metadata_help], target: "_blank") + end + end + + html.html_safe + end + end + def ontology_submission_id_label(acronym, submission_id) [acronym, submission_id].join('#') end + + def submission_metadata_selector(id: 'search_metadata', name: 'search[metadata]', label: 'Filter properties to show') + select_input(id: id, name: name, label: label, values: submission_editable_properties.sort, multiple: true, + data: { placeholder: 'Start typing to select properties' }) + end + def ontology_and_submission_id(value) value.split('#') end @@ -11,62 +31,49 @@ def render_submission_attribute(attribute, submission = @submission, ontology = render partial: 'ontologies_metadata_curator/attribute_inline_editable', locals: { attribute: attribute, submission: submission, ontology: ontology } end - def render_submission_attribute_inline(attribute, submission = @submission, acronym) - render partial:"ontologies_metadata_curator/attribute_inline", locals:{attribute: attribute, submission: submission, acronym: acronym} - end - def attribute_input_frame_id(acronym, submission_id, attribute) "submission[#{acronym}_#{submission_id}]#{attribute.capitalize}_from_group_input" end - def display_submission_attributes(acronym, attributes, submissionId: nil, required: false, show_sections: false, inline_save: false) + def edit_submission_property_link(acronym, submission_id, attribute, container_id = nil, &block) + link = "/ontologies/#{acronym}/submissions/#{submission_id}/edit_properties?properties=#{attribute}&inline_save=true" + if container_id + link += "&container_id=#{container_id}" + else + link += "&container_id=#{attribute_input_frame_id(acronym, submission_id, attribute)}" + end + link_to link, data: { turbo: true }, class: 'btn btn-sm btn-light' do + capture(&block) + end + end + + def display_submission_attributes(acronym, attributes, submissionId: nil, inline_save: false) @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(acronym).first @selected_attributes = attributes - @required_only = required - @hide_sections = !show_sections @inline_save = inline_save - display_properties = @selected_attributes && !@selected_attributes.empty? ? (equivalent_properties(@selected_attributes) + [:ontology, :submissionId]).join(',') : 'all' - if submissionId - @submission = @ontology.explore.submissions({ display: display_properties }, submissionId) + if @selected_attributes && !@selected_attributes.empty? + display_properties = (equivalent_properties(@selected_attributes) + [:ontology, :submissionId]).join(',') else - @submission = @ontology.explore.latest_submission({ display: display_properties }) + display_properties = 'all' end - end - def metadata_section(id, label, collapsed: true, parent_id: nil, &block) - if @hide_sections - content_tag(:div) do - capture(&block) - end + if submissionId + @submission = @ontology.explore.submissions({ display: display_properties }, submissionId) else - collapsed = false unless @selected_attributes.nil? - render CollapsableBlockComponent.new(id: id, parent_id: (parent_id || "#{id}-card"), title: label, collapsed: collapsed) do - capture(&block) - end - end - end - - def attribute_container(attr, required: false, &block) - if show_attribute?(attr, required) - content_tag(:div) do - capture(&block) + @submission = @ontology.explore.latest_submission({ display: display_properties }) end end - end def inline_save? !@inline_save.nil? && @inline_save end def selected_attribute?(attr) - @selected_attributes.nil? || @selected_attributes.empty? || @selected_attributes.include?(attr.to_s) || equivalent_properties(@selected_attributes).include?(attr.to_s) - end + return true if @selected_attributes.nil? || @selected_attributes.empty? || @selected_attributes.include?(attr.to_s) + return true if equivalent_properties(@selected_attributes).include?(attr.to_s) - def show_attribute?(attr, required) - selected = selected_attribute?(attr) - required_only = @required_only && required || !@required_only - selected && required_only + equivalent_properties(attr.to_s).any? { |x| @selected_attributes.include?(x) } end def save_button @@ -90,36 +97,22 @@ def cancel_button(href) end end - def attribute_form_group_container(attr, label: '', required: false, &block) - attribute_container(attr, required: required) do - render FormGroupComponent.new(object: @submission, name: object_name, method: attr, label: label, required: required) do |c| - if inline_save? - c.submit do - html = '' - html += save_button - html += cancel_button(cancel_link(attribute: attr)) - html.html_safe - end + def attribute_form_group_container(attr, &block) + render(TurboFrameComponent.new(id: "#{object_name}#{attr}_from_group_input")) do + tag.div(class: 'd-flex w-100 mb-3') do + html = tag.div(class: 'flex-grow-1 mr-1') do + capture(&block) end - capture(c, &block) - end - end - end - - def attribute_text_field_container(attr, label: '', required: false, inline: true, &block) - attribute_container(attr, required: required) do - render TextFieldComponent.new(object: @submission, name: object_name, label: label, method: attr, required: required, inline: inline) do |c| if inline_save? - c.submit do + html += tag.div(class: 'd-flex') do html = '' html += save_button html += cancel_button(cancel_link(attribute: attr)) html.html_safe end end - - capture(c, &block) if block_given? + html end end end @@ -134,122 +127,34 @@ def format_equivalent end def location_equivalent - %w[summaryOnly pullLocation] + %w[summaryOnly pullLocation uploadFilePath] end def equivalent_property(attr) equivalents = submission_properties found = equivalents.select { |x| x.is_a?(Array) && x[0].eql?(attr.to_sym) } - found.empty? ? attr.to_sym: found.first[1] + found.empty? ? attr.to_sym : found.first[1] end def equivalent_properties(attr_labels) labels = Array(attr_labels) - labels.map { |x| equivalent_property(x) }.flatten end def submission_properties - out = [ - [:format, format_equivalent], - :version, - :status, - [:location, location_equivalent], - :URI, - :deprecated, - :hasOntologySyntax, - :hasFormalityLevel, - :isOfType, - :naturalLanguage, - :description, - :homepage, - :documentation, - :publication, - :usedOntologyEngineeringTool, - :abstract, :notes, :keywords, :alternative, :identifier, - :knownUsage, - :designedForOntologyTask, - :hasDomain, - :coverage, - :example, - :conformsToKnowledgeRepresentationParadigm, - :usedOntologyEngineeringMethodology, - :accrualMethod, - :accrualPeriodicity, - :accrualPolicy, - :competencyQuestion, - :versionIRI, - :source, - :isFormatOf, - :hasFormat, - :includedInDataCatalog, - :depiction, - :logo, - :associatedMedia, - :released, - :modificationDate, - :valid, - :curatedOn, - :publisher, - :hasLicense, - :morePermissions, - :copyrightHolder, - :contact, - :hasContributor, - :hasCreator, - :audience, - :toDoList, - :useGuidelines, - :repository, - :bugDatabase, - :mailingList, - :award, - :wasGeneratedBy, - :wasInvalidatedBy, - :curatedBy, - :endorsedBy, - :fundedBy, - :translator, - :useImports, - :hasPriorVersion, - :isAlignedTo, - :ontologyRelatedTo, - :isBackwardCompatibleWith, - :isIncompatibleWith, - :comesFromTheSameDomain, - :similarTo, - :explanationEvolution, - :generalizes, - :hasDisparateModelling, - :hasPart, - :usedBy, - :workTranslation, - :translationOfWork, - :preferredNamespacePrefix, - :preferredNamespaceUri, - :keyClasses, - :endpoint, - :dataDump, - :csvDump, - :openSearchDescription, - :uriLookupEndpoint, - :uriRegexPattern, - :metadataVoc, - :exampleIdentifier, - :numberOfClasses, - :numberOfIndividuals, - :numberOfProperties, - :entities, - :numberOfAxioms - ] - out.uniq + format_equivalents = format_equivalent + location_equivalents = location_equivalent + equivalents = location_equivalents + format_equivalents + out = submission_metadata.map { |x| x['attribute'] }.reject { |x| equivalents.include?(x) } + out << [:format, format_equivalent] + out << [:location, location_equivalent] + + out end def submission_editable_properties - properties = submission_properties - properties.map do |x| if x.is_a? Array [x[0].to_s.underscore.humanize, x[0]] @@ -257,263 +162,104 @@ def submission_editable_properties [x.to_s.underscore.humanize, x] end end - end - def extractable_metadatum_tooltip(options = {}) - help_tooltip(options[:content], {}, 'fas fa-file-export', 'extractable-metadatum', options[:text]).html_safe - end def attribute_infos(attr_label) - @metadata.select{ |attr_hash| attr_hash["attribute"].to_s.eql?(attr_label) }.first + submission_metadata.select{ |attr_hash| attr_hash["attribute"].to_s.eql?(attr_label) }.first end - def attribute_help_text(attr) + def object_name(acronym = @ontology.acronym, submissionId = @submission.submissionId) + # TO REMOVE or Update + 'submission' + end - if !attr["namespace"].nil? - help_text = "<strong>#{attr["namespace"]}:#{attr["attribute"]}</strong>" - else - help_text = "<strong>bioportal:#{attr["attribute"]}</strong>" - end + def agent_attributes + submission_metadata.select { |x| x["enforce"].include?('Agent') }.map { |x| x["attribute"] } + end - if (attr["metadataMappings"] != nil) - help_text << " (#{attr["metadataMappings"].join(", ")})" - end + def render_submission_inputs(frame_id) + output = "" - if (!attr["enforce"].nil? && attr["enforce"].include?("uri")) - help_text << "<br>This metadata should be an <strong>URI</strong>" + if selected_attribute?('acronym') + output += ontology_acronym_input(update: true) end - if (attr["helpText"] != nil) - help_text << "<br><br>#{attr["helpText"]}" + if selected_attribute?('name') + output += ontology_name_input end - help_text - end - - # Generate the HTML label for every attributes - def generate_attribute_label(attr_label, label_tag_sym: :label) - # Get the attribute hash corresponding to the given attribute - attr = attribute_infos(attr_label) - return attr_label if attr.nil? - label_html = if !attr["extracted"].nil? && attr["extracted"] == true - extractable_metadatum_tooltip({ content: 'Extractable metadatum' }) - end.to_s.html_safe - - label = attr["label"].nil? ? attr_label.underscore.humanize : attr["label"] - - if label_tag_sym.eql? :label - label_html << label_tag("submission_#{attr_label}", label , { class: 'form-label' }) - else - label_html << content_tag(label_tag_sym, label, {class: 'form-label'}) + if selected_attribute?('hasOntologyLanguage') + output += has_ontology_language_input end - # Generate tooltip - help_text = attribute_help_text(attr) - label_html << help_tooltip(help_text, {:id => "tooltip#{attr["attribute"]}"}).html_safe - label_html - end - - def object_name(acronym= @ontology.acronym, submissionId= @submission.submissionId) - "submission[#{acronym}_#{submissionId}]" - end - - def attribute_input_name(attr_label) - object_name_val = object_name - name = "#{object_name_val}[#{attr_label}]" - [object_name_val, name] - end - - def generate_integer_input(attr) - number_field object_name, attr["attribute"].to_s.to_sym, value: @submission.send(attr["attribute"]), class: 'metadataInput form-control' - end + if selected_attribute?('categories') + output += ontology_categories_input + end - def generate_date_input(attr) - field_id = [:submission, attr["attribute"].to_s, @ontology.acronym].join('_') - date_value = @submission.send(attr["attribute"]).presence - data_flat_picker = { controller: "flatpickr", flatpickr_date_format: "Y-m-d", flatpickr_alt_input: "true", flatpickr_alt_format: "F j, Y" } - content_tag(:div, class: 'input-group') do - [ - date_field(object_name, attr["attribute"].to_s.to_sym, value: date_value, id: field_id, data: data_flat_picker, class: "not-disabled") - ].join.html_safe + if selected_attribute?('groups') + output += ontology_groups_input end - end - def generate_textarea_input(attr) - text_area(object_name, attr["attribute"].to_s.to_sym, rows: 3, value: @submission.send(attr["attribute"]), class: 'metadataInput form-control') - end - def generate_select_input(attr, name, select_values, metadata_values, multiple: false) - id = attr["attribute"].to_s + "_" + @ontology.acronym - render SelectInputComponent.new(id: id, name: name, values: select_values , selected: metadata_values , multiple: multiple) - end + if selected_attribute?('administeredBy') + output += ontology_administered_by_input + end - def generate_list_field_input(attr, name, values, field_func) - render NestedFormInputsComponent.new do |c| - c.template do - method(field_func).call("#{name}[NEW_RECORD]", '', :id => attr["attribute"].to_s + "_" + @ontology.acronym, class: "metadataInput form-control my-1") + if selected_attribute?('location') + output += attribute_form_group_container('location') do + render partial: 'ontologies/submission_location_form' end + end - c.empty_state do - hidden_field_tag "#{name}[#{values.size}]" + if selected_attribute?('contact') + output += attribute_form_group_container('contact') do + @submission.contact = [] unless @submission.contact && @submission.contact.size > 0 + contact_input(label: 'Contacts', name: '') end + end - values.each_with_index do |metadata_val, i| - c.row do - method(field_func).call("#{name}[#{i}]", metadata_val, :id => "submission_#{attr["attribute"].to_s}" + "_" + @ontology.acronym, class: "metadataInput my-1 form-control") - end + if selected_attribute?('viewingRestriction') + output += attribute_form_group_container('viewingRestriction') do + ontology_visibility_input end end - end - - def generate_url_input(attr, name, values) - generate_list_field_input(attr, name, values, :url_field_tag) - end - - def generate_list_text_input(attr, name, values) - generate_list_field_input(attr, name, values, :text_field_tag) - end - - def generate_boolean_input(attr, name) - value = attribute_values(attr) - value = value.to_s unless value.nil? - - render SwitchInputComponent.new(id: name, name: name, label: "", checked: value.eql?('true') , value: value, boolean_switch: true) - end - - def input_type?(attr, type) - attr["enforce"].include?(type) - end - def enforce_values?(attr) - !attr["enforcedValues"].nil? - end - def attribute_values(attr) - begin - @submission.send(attr["attribute"]) - rescue - nil + if selected_attribute?('viewOf') + output += attribute_form_group_container('viewOf') do + ontology_view_of_input + end end - end - # Generate the HTML input for every attributes. - def generate_attribute_input(attr_label, options = {}) - input_html = ''.html_safe - - # Get the attribute hash corresponding to the given attribute - attr = @metadata.select { |attr_hash| attr_hash["attribute"].to_s.eql?(attr_label) }.first - - object_name, name = attribute_input_name(attr["attribute"]) - - if input_type?(attr, 'integer') - generate_integer_input(attr) - elsif input_type?(attr, 'date_time') - generate_date_input(attr) - elsif input_type?(attr, 'textarea') - generate_textarea_input(attr) - elsif enforce_values?(attr) - metadata_values, select_values = selected_values(attr, enforced_values(attr)) - if input_type?(attr, "list") - input_html << generate_select_input(attr, name, select_values, metadata_values, multiple: true) - else - select_values << ["", ""] - select_values << %w[Other other] - - metadata_values = "" if metadata_values.nil? - - input_html << generate_select_input(attr, name, select_values, metadata_values) - end + reject_metadata = %w[abstract description uploadFilePath contact pullLocation hasOntologyLanguage] + label = inline_save? ? '' : nil - return input_html - elsif input_type?(attr, 'isOntology') - metadata_values, select_values = selected_values(attr, ontologies_for_select.dup) - input_html << generate_select_input(attr, name, select_values, metadata_values, multiple: attr["enforce"].include?("list")) - return input_html - elsif input_type?(attr, "uri") - uri_values = attribute_values(attr) || [''] - if input_type?(attr, "list") - input_html << generate_url_input(attr, name, uri_values) - else - input_html << text_field(object_name, attr["attribute"].to_s.to_sym, value: Array(uri_values).first, class: "metadataInput form-control") + if selected_attribute?('abstract') + output += attribute_form_group_container('abstract') do + raw attribute_input('abstract',long_text: true, label: label) end - return input_html - elsif input_type?(attr, "boolean") - input_html << generate_boolean_input(attr, name) - else - # If input a simple text - values = attribute_values(attr) || [''] - if input_type?(attr, "list") - input_html << generate_list_text_input(attr, name, values) - else - # if single value text - # TODO: For some reason @submission.send("URI") FAILS... I don't know why... so I need to call it manually - if attr["attribute"].to_s.eql?("URI") - input_html << text_field(object_name, attr["attribute"].to_s.to_sym, value: @submission.URI, class: "metadataInput form-control") - else - input_html << text_field(object_name, attr["attribute"].to_s.to_sym, value: @submission.send(attr["attribute"]), class: "metadataInput form-control") - end - end - input_html end - end - - def generate_attribute_text(attr_label , label) - attr = attribute_infos(attr_label) - label_html = "
    #{label}" - # Generate tooltip - help_text = attribute_help_text(attr) - label_html << help_tooltip(help_text, {:id => "tooltip#{attr["attribute"]}"} ).html_safe - label_html << '
    ' - label_html.html_safe - end - def ontologies_for_select - @ontologies_for_select ||= LinkedData::Client::Models::Ontology.all.collect do |onto| - ["#{onto.name} (#{onto.acronym})", onto.id] + if selected_attribute?('description') + output += attribute_form_group_container('description') do + raw attribute_input('description',long_text: true, label: label) + end end - end - def form_group_attribute(attr, options = {}, &block) - attribute_form_group_container(attr, required: !options[:required].nil?) do |c| - c.label do - generate_attribute_label(attr) - end - c.input do - raw generate_attribute_input(attr, options) - end - if block_given? - c.help do - capture(&block) - end + submission_metadata.reject { |attr| reject_metadata.include?(attr['attribute']) || !selected_attribute?(attr['attribute']) }.each do |attr| + output += attribute_form_group_container(attr['attribute']) do + raw attribute_input(attr['attribute'], label: label) end end - end - private - def enforced_values(attr) - attr["enforcedValues"].collect { |k, v| [v, k] } - end - def selected_values(attr, enforced_values) - metadata_values = attribute_values(attr) - select_values = enforced_values - if metadata_values.kind_of?(Array) - metadata_values.map do |metadata| - unless select_values.flatten.include?(metadata) - select_values << metadata - end - end - else - if !select_values.flatten.include?(metadata_values) && !metadata_values.to_s.empty? - select_values << metadata_values - end + render TurboFrameComponent.new(id: frame_id) do + output.html_safe end - [metadata_values, select_values] end - end \ No newline at end of file diff --git a/app/helpers/turbo_helper.rb b/app/helpers/turbo_helper.rb index 3e1f71155..c1b0303b4 100644 --- a/app/helpers/turbo_helper.rb +++ b/app/helpers/turbo_helper.rb @@ -9,7 +9,7 @@ def alerts_container_id(id = nil) def alert(id: nil, type: 'success', &block) turbo_stream.prepend(id ||alerts_container_id) do - AlertMessageComponent.new(type: type).render_in(view_context, &block) + Display::AlertComponent.new(type: type).render_in(view_context, &block) end end diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index a63383b8d..8d4ea6942 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -58,9 +58,6 @@ application.register("ontology-viewer-tabs", OntologyViewerTabsController) import OntoportalAutocompleteController from "./ontoportal_autocomplete_controller" application.register("ontoportal-autocomplete", OntoportalAutocompleteController) -import PlatformLanguageController from "./platform_language_controller" -application.register("platform-language", PlatformLanguageController) - import ShowFilterCountController from "./show_filter_count_controller" application.register("show-filter-count", ShowFilterCountController) diff --git a/app/javascript/controllers/platform_language_controller.js b/app/javascript/controllers/platform_language_controller.js deleted file mode 100644 index 682d8e901..000000000 --- a/app/javascript/controllers/platform_language_controller.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Controller } from "@hotwired/stimulus" -import { Turbo } from "@hotwired/turbo-rails"; -import { getCookie } from "../mixins/cookie"; - -// Connects to data-controller="platform-language" -// this controller is used to change the language of the whole platform -export default class extends Controller { - - connect() { - const locale = getCookie('locale'); - - const option = document.querySelector(`#language-select option[value="${locale}"]`); - option && (option.selected = true); - - } - - handleLangChanged(event) { - const userPreferedLanguage = event.target.value; - Turbo.visit(`/locale/${userPreferedLanguage}`, { action: "replace" }); - } -} diff --git a/app/mailers/notifier.rb b/app/mailers/notifier.rb index 80b229003..b44241e8e 100644 --- a/app/mailers/notifier.rb +++ b/app/mailers/notifier.rb @@ -6,7 +6,7 @@ def error(error, current_user = nil, request_ip = nil, current_url = nil) @current_user = current_user @request_ip = request_ip @current_url = current_url - + mail(to: "#{$SUPPORT_EMAIL}", from: "#{$SUPPORT_EMAIL}", subject: "[#{$SITE}] Exception Mailer: #{@error_message}") end @@ -23,8 +23,4 @@ def feedback(name, email, comment, location, tags) :subject => "[#{$SITE}] Feedback from #{name}") end - def signup(user) - - end - end diff --git a/app/services/issue_creator_service.rb b/app/services/issue_creator_service.rb index 6af9a2eae..19d81ce47 100644 --- a/app/services/issue_creator_service.rb +++ b/app/services/issue_creator_service.rb @@ -18,7 +18,7 @@ def call id } } - GRAPHQL + GRAPHQL createIssueMutation = GitHub::Client.parse <<-'GRAPHQL' mutation ($repositoryId: ID!, $title: String!, $body: String) { diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index d8527a23f..b92443631 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -13,27 +13,14 @@ %div.row %div.col - %ul.nav.nav-tabs{id: "admin-tabs", role: "tablist"} - %li.nav-item - =link_to("Site Administration", "#site-admin", id: "site-admin-tab", class: "nav-link active", role: "tab", data: { toggle: "tab" }, aria: { controls: "site-admin", selected: "true" }) - %li.nav-item - =link_to("Ontology Administration", "#ontology-admin", id: "ontology-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab" }, aria: { controls: "ontology-admin", selected: "false" }) - %li.nav-item - =link_to("Licensing", "#licensing", id: "licensing-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab", href: admin_licenses_path() }, aria: { controls: "licensing", selected: "false" }) - %li.nav-item - =link_to("Users", "#users", id: "users-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab", href: users_path() }, aria: { controls: "users", selected: "false" }) - %li.nav-item - =link_to("Metadata Administration", "#ontologies_metadata_curator", id: "ontologies_metadata_curator-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab"}, aria: { controls: "ontologies_metadata_curator", selected: "false" }) - %li.nav-item - =link_to("Groups", "#groups", id: "groups-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab", href: "groups" }, aria: { controls: "groups", selected: "false" }) - %li.nav-item - =link_to("Categories", "#categories", id: "categories-admin-tab", class: "nav-link", role: "tab", data: { toggle: "tab", href: "categories" }, aria: { controls: "categories", selected: "false" }) - %div#adminTabContent.tab-content - - -# Site Administration tab - %div.tab-pane.active.show.fade{id: "site-admin", role: "tabpanel", aria: { labelledby: "site-admin-tab" }} - - -# Clear caches + - sections = ['Site Administration','Ontology Administration', 'Licensing', 'Users', 'Metadata Administration', 'Groups', 'Categories', 'Persons & Organizations'] + = render TabsContainerComponent.new do |t| + - sections.each do |section_title| + - t.item(title: section_title, + path: '', + selected: section_title.eql?(sections.first), + page_name: '') + - t.item_content do %div#site-admin-clear-caches.my-5 %div.site-admin-page-header CACHE MANAGEMENT @@ -95,18 +82,18 @@ %table#adminUsers.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} - t.item_content do = render partial: 'ontologies_metadata_curator/metadata_tab' - - -# Groups tab - %div.tab-pane.fade{id: "groups", role: "tabpanel", aria: { labelledby: "groups-admin-tab" }} + - t.item_content do %div.ontologies_list_container.mt-3 %table#adminGroups.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} - - -# Categories tab - %div.tab-pane.fade{id: "categories", role: "tabpanel", aria: { labelledby: "categories-admin-tab" }} + - t.item_content do %div.ontologies_list_container.mt-3 %table#adminCategories.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} + - t.item_content do + %div.ontologies_list_container.mt-3 + %div.mx-auto.w-75 + = render TurboFrameComponent.new(id: 'agents-list', src: '/agents', loading: 'lazy') - - - - \ No newline at end of file + -# Groups tab + %div.tab-pane.fade{id: "groups", role: "tabpanel", aria: { labelledby: "groups-admin-tab" }} + %div.ontologies_list_container.mt-3 + %table#adminGroups.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} diff --git a/app/views/agents/_agent_show.html.haml b/app/views/agents/_agent_show.html.haml new file mode 100644 index 000000000..80b5fffb6 --- /dev/null +++ b/app/views/agents/_agent_show.html.haml @@ -0,0 +1,18 @@ +- deletable ||= false +- frame_id ||= agent_frame_id(agent, parent_id) +- agent_field_name = agent_id ? "#{name_prefix}[#{agent_id}]" : name_prefix += render TurboFrameComponent.new(id: frame_id) do + %div.d-flex + - return agent if agent.is_a? String + - if agent.id + = hidden_field_tag agent_field_name(:id, agent_field_name), agent.id + + = text_field_tag '', display_agent(agent, link: false), class: "form-control", disabled: true + + - if current_user_admin? || agent.creator.eql?(current_user&.id.to_s) + - if edit_on_modal + = link_to_agent_edit_modal(agent, parent_id) + - else + = link_to_agent_edit(agent, parent_id, name_prefix, deletable: deletable) + - if deletable + = link_to_search_agent(agent_id, parent_id, name_prefix ,agent.agentType, deletable) diff --git a/app/views/agents/_agent_usage.html.haml b/app/views/agents/_agent_usage.html.haml new file mode 100644 index 000000000..50336070e --- /dev/null +++ b/app/views/agents/_agent_usage.html.haml @@ -0,0 +1,12 @@ += render_in_modal do + = agent_alert_container(@agent, params[:parent_id]) + - usages = agent_usages(@agent) + - attributes_metadata = agents_metadata + = form_with url: "/agents/#{@agent.id.split('/').last}/usages", method: 'post', data:{turbo: true} do + %div.form.w-100 + - attributes_metadata.each do |attr_metadata| + - attr = attr_metadata.metadata['attribute'] + - selected = usages.select{|x, v| v.any?{|uri| uri[attr]}}.keys.map{|x| x.to_s.split('/')[-3]} + = select_input(id: attr, name: attr, label: attr_header_label(attr_metadata), values: @ontology_acronyms, selected: selected, multiple: true) + %div.mt-2.d-flex.justify-content-end + = submit_tag 'Save', class: "btn btn-primary mr-sm-2 group-form-accept" \ No newline at end of file diff --git a/app/views/agents/_form.html.haml b/app/views/agents/_form.html.haml new file mode 100644 index 000000000..df1a87750 --- /dev/null +++ b/app/views/agents/_form.html.haml @@ -0,0 +1,90 @@ += agent_alert_container(@agent, params[:parent_id]) +%table.form.w-100{data:{ controller: 'form-options-display', 'form-options-display-hidden-class-value': 'd-none'}} + %colgroup + %col + %col{style: "width: 100%"} + + = hidden_field_tag :parent_id, params[:parent_id] if params[:parent_id] + = hidden_field_tag :name_prefix, params[:name_prefix] if params[:name_prefix] + = hidden_field_tag :deletable, deletable if deletable + = hidden_field_tag agent_field_name(:id, name_prefix), agent.id if agent.id + + - if affiliation?(agent) && params[:parent_id] + = hidden_field_tag agent_field_name(:agentType, name_prefix), agent.agentType + - else + %tr + %th + Type + %span.asterik * + %td.top + %div.d-flex + %div.custom-control.custom-radio.mx-1 + = radio_button_tag agent_field_name(:agentType, name_prefix), :person, !agent.agentType.eql?('organization'), class: 'custom-control-input', 'data-action': "change->form-options-display#showOption1" + = label_tag :agentType_person, "Person", class: 'custom-control-label' + %div.custom-control.custom-radio.mx-1 + = radio_button_tag agent_field_name(:agentType, name_prefix), :organization, agent.agentType.eql?('organization'), class: 'custom-control-input', 'data-action': "change->form-options-display#showOption2" + = label_tag :agentType_organization, "Organization", class: 'custom-control-label' + %tr + %th + Name + %span.asterik * + %td.top + = text_field_tag agent_field_name(:name, name_prefix), agent.name, class: "form-control" + %tr{'data-form-options-display-target':"option1", class: affiliation?(agent) && 'd-none'} + %th + Email + %span.asterik + %td.top + = email_field_tag agent_field_name(:email, name_prefix), agent.email, class: "form-control" + %tr{'data-form-options-display-target':"option2", class: !affiliation?(agent) && 'd-none'} + %th + Acronym + %span.asterik + %td.top + = text_field_tag agent_field_name(:acronym, name_prefix), agent.acronym,class: "form-control" + %tr{'data-form-options-display-target':"option2", class: !affiliation?(agent) && 'd-none'} + %th + Homepage + %td.top + = url_field_tag agent_field_name(:homepage, name_prefix), agent.homepage, class: "form-control" + %tr.d-none + %th + Creator + %td.top + = text_field_tag agent_field_name(:creator, name_prefix), agent.creator, class: "form-control" + %tr + %th + Identifiers + %td.top + %div.agents-identifiers + = render NestedFormInputsComponent.new do |c| + - c.template do + %div.d-flex{id: 'NEW_RECORD'} + %div.w-100 + = hidden_field_tag agent_identifier_name('NEW_RECORD' , :creator, name_prefix), session[:user].id + = text_field_tag agent_identifier_name('NEW_RECORD' , :notation, name_prefix), '', class: "form-control" + %div{style:'width: 15%'} + - values = %w[ORCID ROR ISNI GRID] + = render SelectInputComponent.new(id: "agent_identifiers_schemaAgency_NEW_RECORD", name: agent_identifier_name('NEW_RECORD', :schemaAgency, name_prefix), values: values, selected: 'ORCID') + - c.empty_state do + = hidden_field_tag agent_field_name('', name_prefix+"[#{Array(agent.identifiers).size}]") + - Array(agent.identifiers).each_with_index do |identifier, i| + - c.row do + %div.d-flex{id: identifier.id} + %div.w-100 + = hidden_field_tag agent_identifier_name(i.to_s.upcase, :creator, name_prefix), session[:user].id + = text_field_tag agent_identifier_name(i.to_s.upcase, :notation, name_prefix), identifier.notation, class: "form-control" + %div{style:'width: 15%'} + - values = %w[ORCID ROR ISNI GRID] + = render SelectInputComponent.new(id: "#{name_prefix}_identifiers_schemaAgency_#{i.to_s.upcase}", name: agent_identifier_name(i.to_s.upcase, :schemaAgency, name_prefix), values: values, selected: identifier.schemaAgency) + - if show_affiliations + %tr + %th + Affiliations + %td.top.agents-affiliations.agents-inputs + = render NestedAgentSearchInputComponent.new(agents: agent.affiliations, agent_type: 'organization', + name_prefix: '[affiliations]', + parent_id: agent_id(agent), + show_affiliations: false) +%div.mt-2 + = submit_tag 'Save', class: "btn btn-primary mr-sm-2 group-form-accept" \ No newline at end of file diff --git a/app/views/agents/_show_line.html.haml b/app/views/agents/_show_line.html.haml new file mode 100644 index 000000000..840539d6c --- /dev/null +++ b/app/views/agents/_show_line.html.haml @@ -0,0 +1,26 @@ +%tr.human{:id => agent_table_line_id(agent_id(agent))} + %td + %div{style: 'width: 250px'} + %div.text-truncate{title: agent.name} + = agent.name + %td + = raw display_identifiers(agent.identifiers) + %td + = raw agent.affiliations.map{|i| display_agent(i)}.join(', ') + %td + = agent.agentType + %td{:class => 'delete_mappings_column'} + - if agent.id && !agent.id.empty? && session[:user] && session[:user].admin? + %div.d-flex{style: 'width: 250px'} + %span.mx-1 + - count = agent_usages_count(agent) + = link_to_modal(nil, "/agents/#{agent.id.split('/').last}/usages", style: 'width: 120px', class: "btn btn-sm btn-#{count.zero? ? 'danger' : 'light'}", data: { show_modal_title_value: "Agent \"#{agent.name}\" usages" }) do + = count.zero? ? "Not used" : "See usages (#{count})" + %span.mx-1 + = link_to_agent_edit_modal(agent) + %span + - if count.zero? + = button_to "Delete", CGI.unescape(agent_path(agent.id.split('/').last)), method: :delete, class: 'btn btn-link', form: {data: { turbo: true, turbo_confirm: "Are you sure?", turbo_frame: '_top'}} + - else + %span{data: { controller: 'tooltip' }, title: "Can't delete this #{agent.agentType} because still used"} + = link_to "Delete", "", class: 'btn btn-link disabled' \ No newline at end of file diff --git a/app/views/agents/_table_list.html.haml b/app/views/agents/_table_list.html.haml new file mode 100644 index 000000000..6fe4555cd --- /dev/null +++ b/app/views/agents/_table_list.html.haml @@ -0,0 +1,18 @@ += render_alerts_container(AgentsController) +%table#adminAgents.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} + %thead + %tr + %th Name + %th Identifiers + %th Affiliations + %th Type + %th{:class => 'delete_mappings_column'} Actions + %tbody#agents_table_content + - agents.each do |agent| + = render partial: 'agents/show_line' , locals: {agent: agent} + %tr.empty-state + %td.text-center{:colspan => "6"} There are currently no agents. +:javascript + $.fn.dataTable.ext.errMode = 'none'; + + $("#adminAgents").dataTable() \ No newline at end of file diff --git a/app/views/agents/edit.html.haml b/app/views/agents/edit.html.haml new file mode 100644 index 000000000..baa575ba5 --- /dev/null +++ b/app/views/agents/edit.html.haml @@ -0,0 +1,3 @@ += render TurboFrameComponent.new(id: agent_frame_id(@agent, params[:parent_id])) do + = form_with url: agent_path(agent_id(@agent)), method: 'post', data:{turbo: true} do + = render partial: 'form', locals: {agent: @agent, name_prefix: '', show_affiliations: @show_affiliations, deletable: @deletable} diff --git a/app/views/agents/index.html.haml b/app/views/agents/index.html.haml new file mode 100644 index 000000000..83edbd239 --- /dev/null +++ b/app/views/agents/index.html.haml @@ -0,0 +1,9 @@ += turbo_frame_tag 'agents-list' do + = link_to_modal("Create New Agent", + new_agent_path, + id: "new_agent_btn", + role: "button", + class: "btn btn-default mb-3", + data: { show_modal_title_value: "Create a new agent", show_modal_size_value: 'modal-xl' }, + ) + = render partial: 'table_list', locals: {agents: @agents} \ No newline at end of file diff --git a/app/views/agents/new.html.haml b/app/views/agents/new.html.haml new file mode 100644 index 000000000..61ce888fd --- /dev/null +++ b/app/views/agents/new.html.haml @@ -0,0 +1,3 @@ += render TurboFrameComponent.new(id: agent_frame_id(@agent, params[:parent_id])) do + = form_with url: agents_path, method: 'post', data:{turbo: true} do + = render partial: 'form', locals: {agent: @agent, name_prefix: '', show_affiliations: @show_affiliations, deletable: @deletable} \ No newline at end of file diff --git a/app/views/agents/show.html.haml b/app/views/agents/show.html.haml new file mode 100644 index 000000000..01767c527 --- /dev/null +++ b/app/views/agents/show.html.haml @@ -0,0 +1,5 @@ += render TurboFrameComponent.new(id: agent_id_frame_id(@agent_id, params[:parent_id])) do + = render partial: 'agents/agent_show', locals: {agent_id: @agent_id, agent: @agent, + name_prefix: @name_prefix, + parent_id: params[:parent_id], + edit_on_modal: @edit_on_modal, deletable: @deletable} \ No newline at end of file diff --git a/app/views/annotator/index.html.haml b/app/views/annotator/index.html.haml index ca9fa67bb..3082d3d00 100644 --- a/app/views/annotator/index.html.haml +++ b/app/views/annotator/index.html.haml @@ -1,172 +1,180 @@ -- @title = "Annotator" +- @title= t('annotator.title') %head = javascript_include_tag "bp_annotator" - -%div.container-fluid.flex-grow-1 - %div.row - %div.col - %h2.mt-3 Annotator - %p - = t('annotator.index.intro', site: $SITE).html_safe - = link_to(help_path(anchor: "Annotator_Tab"), id: "annotator-help", target: "_blank") do - %i.fa.fa-question-circle.fa-lg{"aria-hidden": "true"} - - %div.row - %div.col - %form - %div.form-group - = hidden_field_tag :annotation_sample_text, t('annotator.index.sample_text') - = text_area_tag("annotation_text", nil, rows: 10, class: "form-control", placeholder: "Enter or paste text to be annotated", "aria-describedby": "annotateTextHelpBlock") - %small#annotateTextHelpBlock.form-text - %a#insert_text_link{href: "javascript:void(0);"} insert sample text - - %a#advancedOptionsLink{:href => "javascript:void(0);"} Show advanced options >> - - %div#advanced-options-container.mt-4 +%div.container + %div.container-fluid.flex-grow-1 + %div.row + %div.col + %h2.mt-3= t('annotator.title') + %p + = t('annotator.index.intro', site: $SITE).html_safe + = link_to(help_path(anchor: "Annotator_Tab"), id: "annotator-help", target: "_blank") do + %i.fa.fa-question-circle.fa-lg{"aria-hidden": "true"} + + %div.row + %div.col + %form %div.form-group - %div.custom-control.custom-checkbox.custom-control-inline - = check_box_tag("longest_only", "all", false, class: "custom-control-input") - %label.custom-control-label{for: "longest_only"} Match longest only + = hidden_field_tag :annotation_sample_text, t('annotator.index.sample_text') + = text_area_tag("annotation_text", @text, rows: 10, class: "form-control", placeholder: t('annotator.annotate_text_prompt'), "aria-describedby": "annotateTextHelpBlock") + %small#annotateTextHelpBlock.form-text + %a#insert_text_link{href: "javascript:void(0);"}= t('nbco_annotatosplus.insert_sample_text') + + %a#advancedOptionsLink{:href => "javascript:void(0);"}= t('nbco_annotatosplus.show_advanced_options') + ">>" - %div.custom-control.custom-checkbox.custom-control-inline - = check_box_tag("whole_word_only", "all", false, class: "custom-control-input") - %label.custom-control-label{for: "whole_word_only"} Match partial words + %div#advanced-options-container.mt-4 + %div.form-group + %div.custom-control.custom-checkbox.custom-control-inline + = check_box_tag("longest_only", "all", false, class: "custom-control-input") + %label.custom-control-label{for: "longest_only"}= t('annotator.filters.match_longest_only') - %div.custom-control.custom-checkbox.custom-control-inline - = check_box_tag("mappings", "all", false, id: "mappings_all", class: "custom-control-input") - %label.custom-control-label{for: "mappings_all"} Include mappings + %div.custom-control.custom-checkbox.custom-control-inline + = check_box_tag("whole_word_only", "all", false, class: "custom-control-input") + %label.custom-control-label{for: "whole_word_only"}= t('annotator.filters.match_partial_words') - %div.custom-control.custom-checkbox.custom-control-inline - = check_box_tag("exclude_numbers", "all", false, class: "custom-control-input") - %label.custom-control-label{for: "exclude_numbers"} Exclude numbers + %div.custom-control.custom-checkbox.custom-control-inline + = check_box_tag("mappings", "all", false, id: "mappings_all", class: "custom-control-input") + %label.custom-control-label{for: "mappings_all"}= t('annotator.filters.include_mappings') - %div.custom-control.custom-checkbox.custom-control-inline - = check_box_tag("exclude_synonyms", "all", false, class: "custom-control-input") - %label.custom-control-label{for: "exclude_synonyms"} Exclude synonyms + %div.custom-control.custom-checkbox.custom-control-inline + = check_box_tag("exclude_numbers", "all", false, class: "custom-control-input") + %label.custom-control-label{for: "exclude_numbers"}= t('annotator.filters.exclude_numbers') - %div.form-group - %label{for: "ontology_ontologyId"} Select ontologies - = select_tag("ontology_ontologyId", options_from_collection_for_select(@annotator_ontologies, :acronym, lambda { |ont| "#{ont.name} (#{ont.acronym})" }), | - multiple: true, class: "form-control", "aria-describedby": "selectOntologiesHelpBlock") | - %small#selectOntologiesHelpBlock.form-text.text-muted Start typing to select ontologies or leave blank to use all + %div.custom-control.custom-checkbox.custom-control-inline + = check_box_tag("exclude_synonyms", "all", false, class: "custom-control-input") + %label.custom-control-label{for: "exclude_synonyms"}= t('annotator.filters.exclude_synonyms') - - if @sem_type_ont %div.form-group - %label{for: "semantic_types"} Select UMLS semantic types - = select_tag("semantic_types", options_for_select(@semantic_types_for_select), multiple: "true", class: "form-control", "aria-describedby": "selectSemanticTypesHelpBlock") - %small#selectSemanticTypesHelpBlock.form-text.text-muted Start typing to select UMLS semantic types or leave blank to use all + %label{for: "ontology_ontologyId"}= t('annotator.select', name: t('ontology')) + = select_tag("ontology_ontologyId", options_from_collection_for_select(@annotator_ontologies, :acronym, lambda { |ont| "#{ont.name} (#{ont.acronym})" }), | + multiple: true, class: "form-control", "aria-describedby": "selectOntologiesHelpBlock") | + %small#selectOntologiesHelpBlock.form-text.text-muted= t('annotator.start_typing_to_select', type: t('ontology')) + + - if @sem_type_ont + %div.form-group + %label{for: "semantic_types"}= t('annotator.select', name: t('annotator.umls.semantic_types')) + = select_tag("semantic_types", options_for_select(@semantic_types_for_select), multiple: "true", class: "form-control", "aria-describedby": "selectSemanticTypesHelpBlock") + %small#selectSemanticTypesHelpBlock.form-text.text-muted= t('annotator.start_typing_to_select', type: t('annotator.umls.semantic_types')) + + %div.form-group + %label{for: "semantic_groups"}= t('annotator.select', name: t('annotator.umls.semantic_groups')) + = select_tag("semantic_groups", options_for_select(@semantic_groups_for_select), multiple: "true", class: "form-control", "aria-describedby": "selectSemanticGroupsHelpBlock") + %small#selectSemanticGroupsHelpBlock.form-text.text-muted= t('annotator.start_typing_to_select', type: t('annotator.umls.semantic_groups')) %div.form-group - %label{for: "semantic_groups"} Select UMLS semantic groups - = select_tag("semantic_groups", options_for_select(@semantic_groups_for_select), multiple: "true", class: "form-control", "aria-describedby": "selectSemanticGroupsHelpBlock") - %small#selectSemanticGroupsHelpBlock.form-text.text-muted Start typing to select UMLS semantic groups or leave blank to use all + %label{for: "class_hierarchy_max_level"}= t('annotator.filters.max_hierarchy_level') + - options = [["none", 0], *(1..10).map {|i| [i, i]}, ["all", 999]] + = select_tag("class_hierarchy_max_level", options_for_select(options, 0), class: "form-control") - %div.form-group - %label{for: "class_hierarchy_max_level"} Include ancestors up to level - - options = [["none", 0], *(1..10).map {|i| [i, i]}, ["all", 999]] - = select_tag("class_hierarchy_max_level", options_for_select(options, 0), class: "form-control") + %div.form-group + %label{for: "score"}= t('annotator.filters.score') + - options = [["none", ""], ["old", "old"], ["cvalue", "cvalue"], ["cvalueh", "cvalueh"]] + = select_tag(:score, options_for_select(options, 0), class: "form-control", "aria-describedby": "includeScoreHelpBlock") + %small#includeScoreHelpBlock.form-text.text-muted= t('annotator.filters.score_help') - %div.form-group - %label{for: "score"} Include score - - options = [["none", ""], ["old", "old"], ["cvalue", "cvalue"], ["cvalueh", "cvalueh"]] - = select_tag(:score, options_for_select(options, 0), class: "form-control", "aria-describedby": "includeScoreHelpBlock") - %small#includeScoreHelpBlock.form-text.text-muted Score annotations following the previous 2009 NCBO measure (old) or the C-Value measure (cvalue). If hierarchy expansion is used, then prefer cvalueh. + %div.form-group + %label{for: "score_threshold"}= t('annotator.filters.score_threshold') + = number_field_tag(:score_threshold, 0, id: "score_threshold", class: "form-control", "aria-describedby": "scoreThresholdHelpBlock") + %small#scoreThresholdHelpBlock.form-text.text-muted= t('annotator.filters.score_threshold_help') - %div.form-group - %label{for: "score_threshold"} Filter by score threshold - = number_field_tag(:score_threshold, 0, id: "score_threshold", class: "form-control", "aria-describedby": "scoreThresholdHelpBlock") - %small#scoreThresholdHelpBlock.form-text.text-muted Specify the minimum score value for annotations + %div.form-group + %label{for: "confidence_threshold"}= t('annotator.filters.confidence_threshold') + = number_field_tag(:confidence_threshold, 0, min: 0, max: 100, id: "confidence_threshold", class: "form-control", "aria-describedby": "confidenceThresholdHelpBlock") + %small#confidenceThresholdHelpBlock.form-text.text-muted= t('annotator.filters.confidence_threshold_help') - %div.form-group - %label{for: "confidence_threshold"} Filter confidence threshold - = number_field_tag(:confidence_threshold, 0, min: 0, max: 100, id: "confidence_threshold", class: "form-control", "aria-describedby": "confidenceThresholdHelpBlock") - %small#confidenceThresholdHelpBlock.form-text.text-muted Specify the minimum position in the scoring distribution (between 1 and 100) + - if @recognizers.length > 1 + %div.form-group + %label{for: "recognizer"}= t('annotator.filters.recognizer') + - default_recognizer = @recognizers.include?("mgrep") ? "mgrep" : @recognizers.first + = select_tag("recognizer", options_for_select(@recognizers.map {|r| [r, r]}, default_recognizer), class: "form-control") - - if @recognizers.length > 1 %div.form-group - %label{for: "recognizer"} Entity recognizer - - default_recognizer = @recognizers.include?("mgrep") ? "mgrep" : @recognizers.first - = select_tag("recognizer", options_for_select(@recognizers.map {|r| [r, r]}, default_recognizer), class: "form-control") - - %div.form-group - %div.custom-control.custom-checkbox - = check_box_tag("fast_context", :all, false, class: "custom-control-input") - %label.custom-control-label{for: "fast_context"} FastContext - %small#fast_contextnHelp.form-text.text-muted= t('annotator.index.fast_context.tooltip') - %div.form-group - %div.custom-control.custom-checkbox - = check_box_tag("lemmatize", false, false, class: "custom-control-input") - %label.custom-control-label{for: "lemmatize"} Lemmatize - %small#lemmatize.form-text.text-muted Enable Lemmatize to lemmatize the submitted text and use a lemmatized dictionary for the annotations - %div.my-4 - = button_tag("Get annotations", type: "button", id: "annotator_button", class: "btn btn-primary btn-lg") - %span.annotator_spinner - %img{src: asset_path('spinners/spinner_000000_16px.gif')}/ - %span#annotator_error.annotator_error - - %div.row - %div.col - #annotations_container - #result_counts - %h4 Annotations - #filter_list{:style => "font-size: 9pt; color: gray; display: none; clear: both; margin: -15px 0 5px"} - %span#filter_title> Results are filtered by - \  - %span#filter_names - #results_error{:style => "color: red; margin-bottom: 7px;"} - %table#annotations.zebra{:style => "min-width: 700px; width: 100%;"} - %thead - %tr - %th - Class - %span.popup_container - %span.bp_popup_link_container - %a#filter_classes.bp_popup_link{:href => "javascript:void(0);"} filter - %div#classes_filter_list.bp_popup_list - %th - Ontology - %span.popup_container - %span.bp_popup_link_container - %a#filter_ontologies.bp_popup_link{:href => "javascript:void(0);"} filter - %div#ontology_filter_list.bp_popup_list - %th{class: "match_type"} - Type - %span.popup_container - %span.bp_popup_link_container - %a#filter_match_type.bp_popup_link{:href => "javascript:void(0);"} filter - %div#match_type_filter_list.bp_popup_list - %th UMLS Sem Type - %th{class: "match_context"} Context - %th - Matched Class - %span.popup_container - %span.bp_popup_link_container - %a#filter_matched_classes.bp_popup_link{:href => "javascript:void(0);"} filter - %div#matched_classes_filter_list.bp_popup_list - %th - Matched Ontology - %span.popup_container - %span.bp_popup_link_container - %a#filter_matched_ontologies.bp_popup_link{:href => "javascript:void(0);"} filter - %div#matched_ontology_filter_list.bp_popup_list - %th Score - %th Negation - %th Experiencer - %th Temporality - %th Certainty - %tbody - %div.my-3 - %b Format results as - %div.my-3 - %span#download_links_tabdelimited.link_button.ui_button - %span#download_links_json.link_button.ui_button - %span#download_links_rdf.link_button.ui_button - %span#download_links_text.link_button.ui_button - -# %span#download_links_xml.link_button.ui_button - %div.mt-3 - Reproduce these results using the - %span#annotator_parameters - %div.mb-4 - Additional parameters explained at - = link_to('Annotator API documentation', "#{$REST_URL}/documentation#nav_annotator", target: "_blank") + %div.custom-control.custom-checkbox + = check_box_tag("fast_context", :all, false, class: "custom-control-input") + %label.custom-control-label{for: "fast_context"}= t('annotator.fast_context') + %small#fast_contextnHelp.form-text.text-muted= t('annotator.index.fast_context.tooltip') + %div.form-group + %div.custom-control.custom-checkbox + = check_box_tag("lemmatize", false, false, class: "custom-control-input") + %label.custom-control-label{for: "lemmatize"}= t('annotator.lemmatize') + %small#lemmatize.form-text.text-muted= t('annotator.index.lemmatize.tooltip') + %div.my-4 + = button_tag(t('annotator.get_annotator'), type: "button", id: "annotator_button", class: "btn btn-primary btn-lg") + %span.annotator_spinner + %img{src: asset_path('spinners/spinner_000000_16px.gif')}/ + %span#annotator_error.annotator_error + + %div.row + %div.col + #annotations_container + #result_counts + %h4= t('annotator.annotations_result') + #filter_list{:style => "font-size: 9pt; color: gray; display: none; clear: both; margin: -15px 0 5px"} + %span#filter_title> #{t('annotator.results_filtered_by')}:  + \  + %span#filter_names + #results_error{:style => "color: red; margin-bottom: 7px;"} + %table#annotations.zebra{:style => "min-width: 700px; width: 100%;"} + %thead + %tr + %th + = t('class') + %span.popup_container + %span.bp_popup_link_container + %a#filter_classes.bp_popup_link{:href => "javascript:void(0);"}= t('filter') + %div#classes_filter_list.bp_popup_list + %th + = t('ontology') + %span.popup_container + %span.bp_popup_link_container + %a#filter_ontologies.bp_popup_link{:href => "javascript:void(0);"}= t('filter') + %div#ontology_filter_list.bp_popup_list + %th{class: "match_type"} + = t('type') + %span.popup_container + %span.bp_popup_link_container + %a#filter_match_type.bp_popup_link{:href => "javascript:void(0);"}= t('filter') + %div#match_type_filter_list.bp_popup_list + %th= t('umls_sem_type') + %th{class: "match_context"}= t('context') + %th + = t('matched_class') + %span.popup_container + %span.bp_popup_link_container + %a#filter_matched_classes.bp_popup_link{:href => "javascript:void(0);"}= t('filter') + %div#matched_classes_filter_list.bp_popup_list + %th + = t('matched_ontology') + %span.popup_container + %span.bp_popup_link_container + %a#filter_matched_ontologies.bp_popup_link{:href => "javascript:void(0);"}= t('filter') + %div#matched_ontology_filter_list.bp_popup_list + %th= t('score') + %th= t('negation') + %th= t('experiencer') + %th= t('temporality') + %th= t('certainty') + %tbody + %div.my-3 + %b= t('format_results') + %div.my-3 + %span#download_links_tabdelimited.link_button.ui_button + %span#download_links_json.link_button.ui_button + %span#download_links_rdf.link_button.ui_button + %span#download_links_text.link_button.ui_button + -# %span#download_links_xml.link_button.ui_button + %div.mt-3 + = link_to(t('reproduce_results')) + %span#annotator_parameters + %div.mb-4 + = t('additional_parameters') + = link_to('Annotator API documentation', "#{$REST_URL}/documentation#nav_annotator", target: "_blank") + +:javascript + window.addEventListener("load", function() { + if(document.getElementById("annotation_text").value != ''){ + document.getElementById("annotator_button").click() + window.scrollBy(0, 1080); + } + }); \ No newline at end of file diff --git a/app/views/annotatorplus/index.html.haml b/app/views/annotatorplus/index.html.haml index 01cbdbbac..b7507e62f 100644 --- a/app/views/annotatorplus/index.html.haml +++ b/app/views/annotatorplus/index.html.haml @@ -16,39 +16,39 @@ %div.col %form %div.form-group - = text_area_tag("annotation_text", nil, rows: 10, class: "form-control", placeholder: "Enter or paste text to be annotated", "aria-describedby": "annotateTextHelpBlock") + = text_area_tag("annotation_text", nil, rows: 10, class: "form-control", placeholder: t('annotator.annotate_text_prompt'), "aria-describedby": "annotateTextHelpBlock") %small#annotateTextHelpBlock.form-text - %a#insert_text_link{href: "javascript:void(0);"} insert sample text + %a#insert_text_link{href: "javascript:void(0);"}= t('nbco_annotatosplus.insert_sample_text') - %a#advancedOptionsLink{:href => "javascript:void(0);"} Show advanced options >> + %a#advancedOptionsLink{:href => "javascript:void(0);"}= t('nbco_annotatosplus.show_advanced_options') + ">>" %div#advanced-options-container.mt-4 %div.form-group %div.custom-control.custom-checkbox.custom-control-inline = check_box_tag("longest_only", "all", false, class: "custom-control-input") - %label.custom-control-label{for: "longest_only"} Match longest only + %label.custom-control-label{for: "longest_only"}= t('nbco_annotatosplus.match_longest_only') %div.custom-control.custom-checkbox.custom-control-inline = check_box_tag("whole_word_only", "all", false, class: "custom-control-input") - %label.custom-control-label{for: "whole_word_only"} Match partial words + %label.custom-control-label{for: "whole_word_only"}= t('nbco_annotatosplus.match_partial_words') %div.custom-control.custom-checkbox.custom-control-inline = check_box_tag("mappings", "all", false, id: "mappings_all", class: "custom-control-input") - %label.custom-control-label{for: "mappings_all"} Include mappings + %label.custom-control-label{for: "mappings_all"}= t('nbco_annotatosplus.include_mappings') %div.custom-control.custom-checkbox.custom-control-inline = check_box_tag("exclude_numbers", "all", false, class: "custom-control-input") - %label.custom-control-label{for: "exclude_numbers"} Exclude numbers + %label.custom-control-label{for: "exclude_numbers"}= t('nbco_annotatosplus.exclude_numbers') %div.custom-control.custom-checkbox.custom-control-inline = check_box_tag("exclude_synonyms", "all", false, class: "custom-control-input") - %label.custom-control-label{for: "exclude_synonyms"} Exclude synonyms + %label.custom-control-label{for: "exclude_synonyms"}= t('nbco_annotatosplus.exclude_synonyms') %div.form-group %label{for: "ontology_ontologyId"} Select ontologies = select_tag("ontology_ontologyId", options_from_collection_for_select(@annotator_ontologies, :acronym, lambda { |ont| "#{ont.name} (#{ont.acronym})" }), | multiple: true, class: "form-control", "aria-describedby": "selectOntologiesHelpBlock") | - %small#selectOntologiesHelpBlock.form-text.text-muted Start typing to select ontologies or leave blank to use all + %small#selectOntologiesHelpBlock.form-text.text-muted= t('nbco_annotatosplus.select_ontologies') - if @sem_type_ont %div.form-group diff --git a/app/views/collections/_collection.html.haml b/app/views/collections/_collection.html.haml index 580c0b74e..914c1a8de 100644 --- a/app/views/collections/_collection.html.haml +++ b/app/views/collections/_collection.html.haml @@ -6,6 +6,8 @@ exclude_keys: %w[member]) do |c| - c.header(stripped: true) do |t| - t.add_row({th: 'ID'}, {td: collection["@id"]}) - - t.add_row({th: 'Preferred Name'}, {td: get_collection_label(collection)}) - - t.add_row({th: 'Members count'}, {td: collection["memberCount"]}) - - t.add_row({th: 'Type'}, {td: collection["@type"]}) \ No newline at end of file + - t.add_row({th: 'Preferred Name'}, {td: display_in_multiple_languages(get_collection_label(collection))}) + - t.add_row({th: 'Members count'}) do |r| + - r.td do + = link_to collection["memberCount"], "/ontologies/" + @ontology.acronym + "/?p=classes&sub_menu=list&concept_collections=" + collection["@id"], 'data-turbo-frame':'_top' + - t.add_row({th: 'Type'}, {td: collection["@type"]}) diff --git a/app/views/collections/_list_view.html.haml b/app/views/collections/_list_view.html.haml index 73fe49b21..bfe256748 100644 --- a/app/views/collections/_list_view.html.haml +++ b/app/views/collections/_list_view.html.haml @@ -5,12 +5,9 @@ no collections detected - else %div - %ul.simpleTree{data:{controller: 'simple-tree history', 'simple-tree': { 'auto-click-value': "true" }, action: 'clicked->history#updateURL'}} + %ul.simpleTree{data:{controller: 'simple-tree', 'simple-tree': { 'auto-click-value': "true" }, action: 'clicked->history#updateURL'}} %li.root %ul - - collections_labels.sort_by{|s| [s["prefLabel"]]}.each do |s| + - sort_collections_label(collections_labels).each do |c| %li.doc - %a{id: s["@id"], href: collection_path(s["@id"]), - data: { turbo: "true", 'turbo-frame': 'collection', 'collectionid': s["@id"]}, - class: selected_collection_id.eql?(s["@id"]) ? "active" : nil } - = get_collection_label(s) \ No newline at end of file + = raw link_to_collection(c, selected_collection_id) \ No newline at end of file diff --git a/app/views/concepts/_details.html.haml b/app/views/concepts/_details.html.haml index 68b48e108..deb946f82 100644 --- a/app/views/concepts/_details.html.haml +++ b/app/views/concepts/_details.html.haml @@ -1,66 +1,61 @@ = turbo_frame_tag 'concept_details' do - schemes_keys = %w[hasTopConcept topConceptOf] - label_xl_set = %w[skos-xl#prefLabel skos-xl#altLabel skos-xl#hiddenLabel] + + = render ConceptDetailsComponent.new(id:'concept-details', acronym: @ontology.acronym, - 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']) do |c| - - c.header do - %tr - %td{nowrap: ""} ID - %td - %p= @concept.id - %tr - %td{nowrap: ""} Preferred Name - %td - %p= @concept.prefLabel({:use_html => true}).html_safe + 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:@concept.id}) + - t.add_row({th: t('ontology_details.concept.preferred_name')}) do |h| + - h.td do + = display_in_multiple_languages(@concept.prefLabel) + - unless @concept.synonym.nil? || @concept.synonym.empty? - %tr - %td{nowrap: ""} Synonyms - %td - - for synonym in @concept.synonym - %p= synonym - %td - %div.synonym-change-request - = add_synonym_button - = remove_synonym_button + - t.add_row({th: t('ontology_details.concept.synonyms')}) do |h| + - h.td do + %div.d-flex + %div + = display_in_multiple_languages(@concept.synonym) + %div.synonym-change-request + = add_synonym_button + = remove_synonym_button + + - unless @concept.definition.nil? || @concept.definition.empty? - %tr - %td{nowrap: ""} Definitions - %td - = dispaly_complex_text(@concept.definition) + - t.add_row({th: t('ontology_details.concept.definitions')}, {td: display_in_multiple_languages(@concept.definition)}) + - if @concept.obsolete? - %tr - %td{nowrap: ""} Obsolete - %td - %p true + - t.add_row({th: t('ontology_details.concept.obsolete')}, {td: 'true'}) + - if skos? - unless @concept.memberOf.nil? || @concept.memberOf.empty? - %tr - %td{nowrap: ""} Member of - %td - %div.my-1 - - @concept.memberOf.each do |v| + - t.add_row({th: t('ontology_details.concept.member_of')}) do |h| + - h.td do + - @concept.memberOf.each do |v| + %span.m-1 = raw get_link_for_collection_ajax(v, @ontology.acronym, '_blank') - unless @concept.inScheme.nil? || @concept.inScheme.empty? - %tr - %td{nowrap: ""} In Schemes - %td - %div.my-1 - - @concept.inScheme.each do |v| + - t.add_row({th: t('ontology_details.concept.in_schemes')}) do |h| + - h.td do + - @concept.inScheme.each do |v| + %span.m-1 = raw get_link_for_scheme_ajax(v, @ontology.acronym, '_blank') - %tr - %td{nowrap: ""} Type - %td - %p= @concept.type - - - scheme_set = c.properties_set_by_keys(schemes_keys, c.concept_properties) - - label_xl_set = c.properties_set_by_keys(label_xl_set, c.concept_properties) - - c.section do - = c.render_properties(scheme_set, c.concept_properties) do |v| - - get_link_for_scheme_ajax(v, @ontology.acronym, '_blank') - - c.section do - = c.render_properties(label_xl_set, c.concept_properties) do |v| - - get_link_for_label_xl_ajax(v, @ontology.acronym, @concept.id, modal: params[:modal].nil? || params[:modal]&.eql?('true')) \ No newline at end of file + + - t.add_row({th: t('ontology_details.concept.type')} , {td: @concept.type}) + + + - c.add_sections(schemes_keys) do |v| + - get_link_for_scheme_ajax(v, @ontology.acronym, '_blank') + + - c.add_sections(label_xl_set) do |v| + - get_link_for_label_xl_ajax(v, @ontology.acronym, @concept.id) + + + + diff --git a/app/views/concepts/_list.html.haml b/app/views/concepts/_list.html.haml index d3cebdcd5..34f4e9679 100644 --- a/app/views/concepts/_list.html.haml +++ b/app/views/concepts/_list.html.haml @@ -3,7 +3,7 @@ next_url: concept_list_url(@page.nextPage, request_collection_id, @ontology.acronym), current_page: @page.page, next_page: @page.nextPage, auto_click: @auto_click) do |c| - - concepts = c.collection.sort_by{|concept| [concept.prefLabel.capitalize || concept.id]} + - concepts = c.collection.sort_by{|concept| [concept.prefLabel&.capitalize || concept.id]} - if concepts && !concepts.empty? = raw tree_link_to_concept(child: concepts.shift, ontology_acronym: @ontology.acronym, active_style: c.auto_click? ? 'active' : '') - concepts.each do |concept| diff --git a/app/views/concepts/_perma_link_modal.html.haml b/app/views/concepts/_perma_link_modal.html.haml new file mode 100644 index 000000000..4e43ba6dd --- /dev/null +++ b/app/views/concepts/_perma_link_modal.html.haml @@ -0,0 +1,12 @@ +-# Modal dialog for getting a permanent link to a class (must reside in a top-level position in the document to display properly). +%div#classPermalinkModal{class: "modal fade", tabindex: "-1", role: "dialog", aria: {labelledby: "classPermalinkLabel", hidden: "true"}} + %div.modal-dialog.modal-dialog-centered.modal-lg{role: "document"} + %div.modal-content + %div.modal-header + %h5#classPermalinkLabel.modal-title Permanent link to this class + %button.close{type: "button", "data-dismiss": "modal", "aria-label": "Close"} + %span{"aria-hidden": "true"} × + %div.modal-body + = text_field_tag("purl_input", nil, class: "form-control") + %div.modal-footer + %button.btn.btn-secondary{"data-dismiss": "modal"} Close \ No newline at end of file diff --git a/app/views/concepts/_show.html.haml b/app/views/concepts/_show.html.haml index 71f02f9a3..7511d3c15 100644 --- a/app/views/concepts/_show.html.haml +++ b/app/views/concepts/_show.html.haml @@ -1,70 +1,73 @@ -= render TurboFrameComponent.new(id: 'concept_show', - data: {controller:'labels-ajax-container', 'action': 'turbo:before-fetch-request->labels-ajax-container#abortAll', 'labels-ajax-container-label-ajax-outlet': '#concept_show a[data-controller="label-ajax"]'}) do - / When we have an ontology with a flat hierarchy, we initially disable the tabs because we don't have a class to display += render TurboFrameComponent.new(id: 'concept_show', data: {controller:'labels-ajax-container', 'action': 'turbo:before-fetch-request->labels-ajax-container#abortAll', 'labels-ajax-container-label-ajax-outlet': '#concept_show a[data-controller="label-ajax"]'}) do - if @concept.id.eql?("bp_fake_root") - %div{:style => "padding: 100px 0; font-size: larger; font-weight: bold; text-align: center;"} + = render Display::AlertComponent.new do Use the "Jump To" to find a class and display details, visualization, notes, and mappings - else - .cls-info-container - %ul.nav.nav-tabs.tabs - %li#details_top.nav-item - %a.nav-link.active.py-1{:href => "#details" , data:{toggle: 'tab', target: '#details_content'}} Details - - unless skos? - %li#instances_top.nav-item - %a.nav-link.py-1{:href => "#instances" , data:{toggle: 'tab', target: '#instances_content'}} - Instances - ( - %span#instances_count - ) - %li#visualization_top.nav-item - %a.nav-link.py-1{:href => "#visualization", data:{toggle: 'tab', target: '#visualization_content'}} Visualization - %li#notes_top.nav-item - %a.nav-link.py-1{:href => "#notes", data:{toggle: 'tab', target: '#notes_content'}} - Notes - %span#note_count_wrapper - ( - %span#note_count= @notes.length - ) - %li#mappings_top.nav-item - %a.nav-link.py-1{:href => "#mappings", data:{toggle: 'tab', target: '#mappings_content'}} - #{concept_label_to_show(submission: @submission)} Mappings ( - %span#mapping_count= 'loading' - ) + = render TabsContainerComponent.new(type:'outline') do |c| - %li#restlink_top.nav-item - %a.nav-link.py-1{:href => "#{@ontology.id}/classes/#{escape(@concept.id)}?display=all", :style => "margin-bottom: 1em; margin-left: 1em;", :target => "_blank"} Access #{concept_label_to_show(submission: @submission)} JSON - - if @enable_ontolobridge - %li#request_term_top.nav-item - %a.nav-link.py-1{:href => "#request_term", data:{toggle: 'tab', target: '#request_term'}} - New Term Requests + - c.pinned_right do - if $PURL_ENABLED - = link_to("#classPermalinkModal", class: "class-permalink nav-link", title: "Get a permanent link to this class", aria: {label: "Get a permanent link to this class"}, data: {toggle: "modal", current_purl: "#{@current_purl}"}) do - %i{class: "fas fa-link", aria: {hidden: "true"}} - #contents.tab-content - #details_content.tab-pane.active.show - = render :partial =>'/concepts/details' - - unless skos? - #instances_content.tab-pane - = render :partial =>'instances/instances' , locals: {id: "class-instances-data-table"} - #visualization_content.tab-pane - = render :partial =>'/concepts/biomixer' - #notes_content.tab-pane - = render :partial =>'/notes/list' - #mappings_content.tab-pane - = render TurboFrameComponent.new(id:'concept_mappings', - src:"/ajax/mappings/get_concept_table?ontologyid=#{@ontology.acronym}&conceptid=#{CGI.escape(@concept.id)}") - - if @enable_ontolobridge - #request_term_content.tab-pane - = render :partial =>'/concepts/request_term' + %div.mx-1 + = link_to("#classPermalinkModal", class: "class-permalink nav-link", title: "Get a permanent link to this class", aria: {label: "Get a permanent link to this class"}, data: {toggle: "modal", current_purl: "#{@current_purl}"}) do + %i{class: "fas fa-link", aria: {hidden: "true"}} + %div + = render RoundedButtonComponent.new(link: "#{@ontology.id}/classes/#{escape(@concept.id)}?display=all&apikey=#{get_apikey}", target:'_blank') + + - c.item(title: 'Details', path: '#details', selected: true) + + - unless skos? + - c.item(id: 'instances', path: '#instances') do + Instances + ( + %span#instances_count + ) + + - c.item(title: 'Visualization', path: '#visualization') + + - c.item(id: 'notes', path: '#notes') do + Notes + %span#note_count_wrapper + ( + %span#note_count= @notes.length + ) + + - c.item(id: 'mappings', path: '#mappings') do + #{concept_label_to_show(submission: @submission).humanize} Mappings ( + %span#mapping_count= 'loading' + ) + + - if @enable_ontolobridge + - c.item(title: ' New Term Requests', path: '#request_term') + + - c.item_content do + = render :partial =>'/concepts/details' + + - unless skos? + - c.item_content do + = render :partial =>'instances/instances' , locals: {id: "class-instances-data-table"} + - c.item_content do + = render :partial =>'/concepts/biomixer' + + - c.item_content do + = render :partial =>'/notes/list' + + - c.item_content do + = render TurboFrameComponent.new(id:'concept_mappings', + src:"/ajax/mappings/get_concept_table?ontologyid=#{@ontology.acronym}&conceptid=#{CGI.escape(@concept.id)}") + + - if @enable_ontolobridge + - c.item_content do + = render :partial =>'/concepts/request_term' + :javascript - jQuery(document).ready(function(){ + jQuery(document).ready(function(){ - jQuery("#classPermalinkModal").on("shown.bs.modal", function (e) { - var currentPurl = jQuery("a.class-permalink").data("current-purl"); - jQuery("#purl_input").val(currentPurl); - }) + jQuery("#classPermalinkModal").on("shown.bs.modal", function (e) { + var currentPurl = jQuery("a.class-permalink").data("current-purl"); + jQuery("#purl_input").val(currentPurl); + }) - jQuery("#purl_input").on("click", function () { - jQuery(this).select(); - }); - }); + jQuery("#purl_input").on("click", function () { + jQuery(this).select(); + }); + }); diff --git a/app/views/errors/internal_server_error.html.haml b/app/views/errors/internal_server_error.html.haml index 3a9b4c7df..19cb03a69 100644 --- a/app/views/errors/internal_server_error.html.haml +++ b/app/views/errors/internal_server_error.html.haml @@ -1,6 +1,6 @@ -.d-flex.align-items-center.flex-column.position-relative +.d-flex.align-items-center.flex-column.position-relative.error-page = inline_svg("errors/agroportal.svg") - .position-absolute.text-center{style: "top: 425px; font-size: 30px; font-weight: 800; color: #263238;"} + %h1.position-absolute.text-center{style: "top: 425px; font-size: 30px; font-weight: 800; color: #263238;"} %div We're sorry but something has gone wrong. %div diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 5aaf6ea2c..b41c3a0f6 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,6 +1,6 @@ -.d-flex.align-items-center.flex-column.position-relative +.d-flex.align-items-center.flex-column.position-relative.error-page = inline_svg("errors/agroportal.svg") - .position-absolute{style: "top: 442px; font-size: 30px; font-weight: 800; color: #263238;"} + %h1.position-absolute{style: "top: 442px; font-size: 30px; font-weight: 800; color: #263238;"} = @error_message || "Page not found" .buttons.d-flex.justify-content-center.mt-3 %div{style: "width: 216px;"} diff --git a/app/views/fair_score/_fair_service_header.html.haml b/app/views/fair_score/_fair_service_header.html.haml index f4550d08b..c4dd1e462 100644 --- a/app/views/fair_score/_fair_service_header.html.haml +++ b/app/views/fair_score/_fair_service_header.html.haml @@ -1,10 +1,6 @@ %span %span - FAIR Scores - %span.badge.badge-pill.badge-secondary - beta + = beta_badge %span - = link_to("https://github.com/agroportal/fairness", target: "_blank", "aria-label": "View fair scores definitions", title: "View fair scores definitions") do - %i.fas.fa-lg.fa-question-circle{"aria-hidden": "true", style: "margin-left: 0.5rem"} - = link_to(get_fairness_service_url,id: "fairness-service-url", target: "_blank", "aria-label": "Get the json version", title: "Get the json version") do - %i.fas.fa-lg.fa-code{"aria-hidden": "true", style: "margin-left: 0.5rem" } \ No newline at end of file + = render Display::InfoTooltipComponent.new(text: t("view_fair_scores_definitions")) + = fairness_link \ No newline at end of file diff --git a/app/views/home/_fair_score_home.html.haml b/app/views/home/_fair_score_home.html.haml deleted file mode 100644 index 988ef5cf0..000000000 --- a/app/views/home/_fair_score_home.html.haml +++ /dev/null @@ -1,38 +0,0 @@ -%div.col{data:{controller:"fair-score-home"}} - %div.card.mb-3 - %div.card-header.d-flex.justify-content-between - %span - = render partial: "fair_score/fair_service_header" - %span - %h2.p-2.badge.badge-secondary - Average - %span.badge.badge-light - %span#fair-score-average - 0 (0%) - %h2.p-2.badge.badge-secondary - Min - %span.badge.badge-light - %span#fair-score-min - 0 (0%) - %h2.p-2.badge.badge-secondary - Max - %span.badge.badge-light - %span#fair-score-max - 0 (0%) - %h2.p-2.badge.badge-secondary - Median - %span.badge.badge-light - %span#fair-score-median - 0 (0%) - %div.card-body - %div.p-3 - %div.row#detailedFilter - = render :partial => "shared/ontology_picker", locals: {sel_text: ""} - %div.mt-2.text-center{style: "min-height: 260px"} - %div.row#fair-score-charts-container - %div.col-md-6.col-sm - = render partial: "shared/fair_score_radar" , locals: {data: nil } - %a.btn.btn-link{href:"/landscape#fairness_assessment"} - See details - %div.col-md-6.col-sm - = render partial: "shared/fair_score_bars", locals: {data: nil} \ No newline at end of file diff --git a/app/views/home/about.html.erb b/app/views/home/about.html.erb deleted file mode 100644 index 8646f642a..000000000 --- a/app/views/home/about.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= raw t('about') -%> diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index b3454de47..4b59add41 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1,12 +1,12 @@ -- @title = t(".title", organization: "#{$ORG_SITE}") +- @title = t("home.index.title", site: "#{$SITE}") - unless $FRONT_NOTICE.nil? || $FRONT_NOTICE.empty? || cookies[:front_page_notice_closed].eql?("true") :javascript function close_message(){ - var exdate = new Date(); - exdate.setDate(exdate.getDate() + 7); - document.cookie="front_page_notice_closed=true; expires="+exdate.toGMTString(); - jQuery("#notice_message").hide(); + var exdate = new Date(); + exdate.setDate(exdate.getDate() + 7); + document.cookie="front_page_notice_closed=true; expires="+exdate.toGMTString(); + jQuery("#notice_message").hide(); } %p#notice_message{:style => "padding: 10px; margin: 10px; border:1px solid #EFEFEF; background-color: #F9F9F9;"} @@ -14,128 +14,181 @@ \   %a{:href => "#", :onclick => "close_message(); return false;", :style => "font-size: small; color: darkGray;"} [close] -%div.container.pb-4.pb-md-5 - %div.row - %div.col - %div.px-2.py-2.pt-md-5.border-bottom.text-center - %h2 - = t(".welcome", site: "#{$SITE}") - %small.text-muted - = t(".tagline") - %div.row.search.pt-4 - %div.col - %div.card-deck - -# Search for a class across ontologies - %div.card - %div.card-header Search for a class - %div.card-body - = form_tag("/search", method: "get") do - %div.input-group.mb-3 - - placeholder = t(".query_placeholder") - = text_field_tag("query", nil, class: "form-control", placeholder: placeholder) - %div.input-group-append - = button_tag(type: "submit", class: "btn btn-primary", aria: {label: "Search for a class"}) do - %i{class: "fas fa-search fa-lg", aria: {hidden: "true"}} - %a{:href => "/search?opt=advanced"} Advanced Search - -# Search for an ontology - %div.card - %div.card-header Find an ontology - %div.card-body - %input#find_ontology_id{:type => "hidden"} - %div.input-group.mb-3 - = text_field_tag("ontology", nil, id: "find_ontology", class: "form-control", data: {ontologynames: @ontology_names}, placeholder: t(".find_ontology_placeholder")) - %div.input-group-append - = button_tag(class: "btn btn-primary", onclick: "jumpToValueOntology()", aria: {label: "Find an ontology"}) do - %i{class: "fas fa-search fa-lg", aria: {hidden: "true"}} - %div.dropdown - = button_tag("Browse Ontologies", type: "button", id: "ontologyGroupsDropdown", class: "btn btn-info dropdown-toggle", data: {toggle: "dropdown", offset: "0,10"}, aria: {haspopup: "true", expanded: "false"}) - %div.dropdown-menu{"aria-labelledby": "ontologyGroupsDropdown"} - = link_to("All", ontologies_path(), class: "dropdown-item") - %div.dropdown-divider - %h6.dropdown-header Browse by group - - @groups.each do |group| - = link_to(group[:name], ontologies_path(filter: group[:acronym]), class: "dropdown-item") - %div.row.pt-3.statistics - %div.col - %div.card-deck - -# Ontology visits - %div.card - %div.card-header Ontology Visits #{"in full #{$SITE} " if at_slice?} (#{@analytics.date.strftime("%B %Y")}) - = content_tag(:div, nil, id: "ontology-visits-chart", class: "card-body", data: {ontnames: @anal_ont_names, ontnumbers: @anal_ont_numbers}) do - %canvas#myChart - = link_to("More", visits_path()) - -# Ontology statistics - %div.card - %div.card-header #{$SITE} Statistics #{"in full #{$SITE}" if at_slice?} - %ul.list-group.list-group-flush - %li.list-group-item - %div.d-flex.justify-content-between.align-items-center - %span Ontologies - %span= number_with_delimiter(@ont_count) - %li.list-group-item - %div.d-flex.justify-content-between.align-items-center - %span Classes - %span= number_with_delimiter(@cls_count, :delimiter => ",") - %li.list-group-item - %div.d-flex.justify-content-between.align-items-center - %span Individuals - %span= number_with_delimiter(@individuals_count, :delimiter => ",") - %li.list-group-item - %div.d-flex.justify-content-between.align-items-center - %span Projects - %span= LinkedData::Client::Models::Project.all.length - %li.list-group-item - %div.d-flex.justify-content-between.align-items-center - %span Users - %span= LinkedData::Client::Models::User.all.length - - if fairness_service_enabled? - %div#fair-home.row.mt-3 - = render partial: "fair_score_home" - %div.row.pt-3.extra - %div.col - %div.card-deck - -# Latest Notes - %div.card - %div.card-header Latest Notes - %ul.list-group.list-group-flush - - if @last_notes.nil? || @last_notes.empty? - %li.list-group-item - %span No recent notes have been submitted - - else - - for note in @last_notes - %li.list-group-item - - begin - = link_to "#{note[:subject]} (#{note[:ont_name]})", note_path(CGI.escape(note[:id])) - %br/ - %span{:style => "color: #AAAAAA"} - = "#{time_ago_in_words(note[:created])} ago" - by #{note[:author]} - - if note[:body] - %p - = truncate(strip_tags(note[:body]), :length => 100) - \  - - rescue - - if !$ENABLE_SLICES.nil? && $ENABLE_SLICES == true && !at_slice? - -# Slices - %div.card - %div.card-header Slices - %ul.list-group.list-group-flush - - LinkedData::Client::Models::Slice.all.each_with_index do |slice, index| - - break if index == 10 - - slice_name = "#{slice.name} (#{slice.acronym})" - - slice_link = "https://#{slice.acronym}.#{$UI_URL.sub("https://", "").sub("http://", "")}" - %li.list-group-item - = link_to slice_name, slice_link +.home-header-container + .home-header-background + .home-bubbles + .home-random-bubbles + %img{:src => asset_path("home-random-bubbles.svg")}/ + .home-bubble.home-bubble-one + %h5 + = @anal_ont_names[0] + %p + = @anal_ont_numbers[0].to_s + " " + t('visits') + .home-bubble.home-bubble-two + %h5 + = @anal_ont_names[1] + %p + = @anal_ont_numbers[1].to_s + " " + t('visits') + .home-bubble.home-bubble-three + %h5 + = @anal_ont_names[2] + %p + = @anal_ont_numbers[2].to_s + " " + t('visits') + %a.home-bubble.home-bubble-four{:href => "/visits"} + %h5 ... + .home-header-title-container + .home-header-title + %h4 + = t('.welcome', site: $SITE) + %p + = t('home.index.tagline') + = render OntologySearchInputComponent.new + +.home-body-container + .home-section + %h4= t('home.ontology_upload') + %hr.home-section-line/ + %p= t('home.ontology_upload_desc') + %p= t('home.ontology_upload_benefits', site: $SITE) + .home-upload-benifits + - @upload_benefits.each do |benefit| + %div + %img{:src => asset_path("check-outlined.svg")}/ + %p + = benefit + %div.home-upload-ontology-button + = upload_ontology_button + + .home-section + %h4 + = t('home.recommender_annotator') + %hr.home-section-line/ + .home-card + %form{action: "/annotator_recommender_form", method: "post"} + %textarea.home-recommendations-and-annotations{rows: "6" , placeholder: t('home.paste_text_prompt'), name: "text"} + %input.d-none{type: "submit", name: "submit_button" ,value: "annotator", id: "annotator_submit"}/ + %input.d-none{type: "submit" , name: "submit_button" ,value: "recommender", id: "recommender_submit"}/ + .home-services-buttons + .home-get-recommendations{onclick: "submitRecommender()"} + %p= t('home.get_recommendations') + = inline_svg_tag "play.svg", class: "home-play-icon" + .home-services-buttons + .home-get-annotations{onclick: "submitAnnotator()"} + %p= t('home.get_annotations') + %img{:src => asset_path("play-white.svg")}/ + .home-section + .home-section-sub-sections-container + .home-sub-section-left + %div.align-items + %h4.margin-items= t('home.fairness') + = render Display::InfoTooltipComponent.new(text: "You are seing the average scores for all the public ontologies in AgroPortal. FAIR scores are computed with the O'FAIRe methodology. More details here: https://github.com/agroportal/fairness") + = fairness_link + %hr.home-section-line/ + .home-card.home-fair-scores + %div.fair-scores-badge-section + %h2.p-2.badge.badge-secondary.fair-scores-badge-margin + Average + %span.badge.badge-light + %span#fair-score-average + 0 (0%) + %h2.p-2.badge.badge-secondary.fair-scores-badge-margin + Min + %span.badge.badge-light + %span#fair-score-min + 0 (0%) + %h2.p-2.badge.badge-secondary.fair-scores-badge-margin + Max + %span.badge.badge-light + %span#fair-score-max + 0 (0%) + - if fairness_service_enabled? + %div#fair-home{data:{controller:"fair-score-home"}} + = render partial: "shared/fair_score_bars", locals: {data: nil} + %a{:href => "/landscape#fairness_assessment"} + %div.home-fair-details + %p= t('home.fair_details') + + .home-sub-section-right + %h4= t('home.twitter_news') + %hr.home-section-line + .home-card.home-twitter-news + %a.twitter-timeline{"data-height" => "360", :href => "https://twitter.com/lagroportal?ref_src=twsrc%5Etfw"} Tweets by lagroportal + %script{:async => "", :charset => "utf-8", :src => "https://platform.twitter.com/widgets.js"} + + .home-section + %h4 + = t('home.agroportal_figures', site: portal_name) + %hr.home-section-line/ + .home-statistics-container + .home-statistics + .home-statistics-item + %hr/ + %div + %h4 + = format_number_abbreviated(@ont_count) + %p Ontologies + .home-statistics-item + %hr/ + %div + %h4 + = format_number_abbreviated(@cls_count) + %p Classes + .home-statistics-item + %hr/ + %div + %h4 + = format_number_abbreviated(@individuals_count) + %p Individuals + .home-statistics-item + %hr/ + %div + %h4 + = format_number_abbreviated(@prop_count) + %p Properties + .home-statistics-item + %hr/ + %div + %h4 + = format_number_abbreviated(@projects_count) + %p Projects + .home-statistics-item + %hr/ + %div + %h4 + = format_number_abbreviated(@map_count) + %p Mappings + .home-statistics-item + %hr/ + %div + %h4 + = format_number_abbreviated(@users_count) + %p Users + .home-section + .home-statistics-container + .home-support-title + %p= t('home.supported_by') + %hr.home-section-line/ + .home-support-items + - $HOME_PAGE_LOGOS[:supported_by].each do |logo| + %a{href:logo[:url], target: "_blanc"} + %img{src: asset_path(logo[:img_src])} + .home-support-title + %p= t('home.with_collaboration') + %hr.home-section-line/ + .home-support-items + - $HOME_PAGE_LOGOS[:with_the_collaboration_of].each do |logo| + %a{href:logo[:url], target: "_blanc"} + %img{src: asset_path(logo[:img_src]), class: 'custom-logo-class'} + + + +:javascript + function submitAnnotator(){ + document.getElementById("annotator_submit").click() + } + function submitRecommender(){ + document.getElementById("recommender_submit").click() + } - - $HOME_PAGE_LOGOS.to_a.each do |home_page_logos_row| - - unless home_page_logos_row[:links].to_a.empty? - %div.row.pt-3 - %div.col - %div.card-deck - %div.card - %div.card-header= home_page_logos_row[:title] - %div.logos - - home_page_logos_row[:links].to_a.each do |logo| - = link_to(image_tag(logo[:img_src]), logo[:url], target: logo[:target]) diff --git a/app/views/instances/_instance_details.html.haml b/app/views/instances/_instance_details.html.haml index bf33b84d0..fd16b9a55 100644 --- a/app/views/instances/_instance_details.html.haml +++ b/app/views/instances/_instance_details.html.haml @@ -2,7 +2,7 @@ - ontology_acronym = params[:ontology_id] - filter_properties = ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] %h4 - Details of #{link_to_instance(@instance,ontology_acronym)} of type : #{@instance.types.map {|cls| link_to_class(ontology_acronym,cls)}.join(', ').html_safe} + Details of #{link_to_instance(@instance,ontology_acronym)} of type: #{@instance.types.map {|cls| link_to_class(ontology_acronym,cls)}.join(', ').html_safe} %table.zebra{style:'width: 80vw'} %thead %tr diff --git a/app/views/label_xl/show.html.haml b/app/views/label_xl/show.html.haml index f038b63cc..a99e254e4 100644 --- a/app/views/label_xl/show.html.haml +++ b/app/views/label_xl/show.html.haml @@ -7,5 +7,5 @@ exclude_keys: []) do |c| - c.header(stripped: true) do |t| - t.add_row({th: 'ID'}, {td: @label_xl["@id"]}) - - t.add_row({th: 'Preferred Name'}, {td: get_label_xl_label(@label_xl)}) + - t.add_row({th: 'Preferred Name'}, {td: display_in_multiple_languages(get_label_xl_label(@label_xl))}) - t.add_row({th: 'Type'}, {td: @label_xl["@type"]}) diff --git a/app/views/landscape/index.html.haml b/app/views/landscape/index.html.haml index 0a059e9c7..a9b105e24 100644 --- a/app/views/landscape/index.html.haml +++ b/app/views/landscape/index.html.haml @@ -6,87 +6,86 @@ - @title = "Landscape" %div{:class => "container theme-showcase", :role => "main", :style => "width: 80%; display: block;", :align => "center"} %h1{:style => "font-size: 45px;"} - = $SITE - Landscape - %p Visualize data retrieved from the ontologies stored in the portal + = t("landscape.title", site: $SITE) + %p= t("landscape.intro") %div{:class => "panel panel-success", :style => "margin-top: 2em;"} %div{:class => "panel-heading"} - %h3{:class => "panel-title"} Groups and categories + %h3{:class => "panel-title"}= t("landscape.groups_and_categories") %div{:class => "panel-body"} %div{:class => "row", :style => "width: 80%;"} %div{:class => "col-md-6"} - %h2 Ontologies by group + %h2= t("landscape.ontologies_by", type: t("landscape.group")) %canvas#groupsCanvas %div{:class => "col-md-6"} - %h2 Ontologies by category + %h2= t("landscape.ontologies_by", type: t("landscape.category")) %canvas#domainCanvas %div{:class => "row", :style => "width: 80%; margin-top: 3em;"} %div{:class => "col-md-7"} - %h2 Ontologies count in each data catalog + %h2= t("landscape.ontologies_count_by_catalog") %canvas#dataCatalogCanvas %div{:class => "col-md-5"} - %h2 Ontologies by size + %h2= t("landscape.ontologies_by", type: t("landscape.size")) %canvas#sizeSlicesCanvas %hr{:style => "margin-top: 5em; margin-bottom: 5em;"} - %h1 Properties use - %p The proportion of properties usage among stored ontologies. + %h1= t("landscape.properties_use") + %p= t("landscape.properties_usage_proportion") %div#pieChartDiv %div{:class => "row"} %div{:class => "col-md-4"} - %h2 Ontologies natural languages + %h2= t("landscape.ontologies_languages") %div#naturalLanguagePieChartDiv %div{:class => "col-md-4"} - %h2 Licenses used by the ontologies + %h2= t("landscape.ontologies_licenses") %div#licensePieChartDiv %div{:class => "col-md-4"} - %h2 Most used tools to build ontologies + %h2= t("landscape.ontology_tools") %div#toolCloudChart{:style => "width: 100%; height: 500px;"} %div#chartTooltip{:style => "width: auto; display: none;"} - %span#chartTooltipValue none + %span#chartTooltipValue= t("none") - %button#propertiesBtn{:style => "margin-top: 1em;", :type => "button", :class => "btn btn-success", :onclick => "toggleDiv('properties')"} More properties charts + %button#propertiesBtn{:style => "margin-top: 1em;", :type => "button", :class => "btn btn-success", :onclick => "toggleDiv('properties')"}= t("landscape.more_properties_charts") %div#propertiesDiv{:class => "panel panel-info", :style => "margin-top: 2em;"} %div{:class => "panel-heading"} - %h3{:class => "panel-title"} Pie charts for properties used for the ontologies + %h3{:class => "panel-title"}= t("landscape.ontology_properties_pie_charts") %div{:class => "panel-body"} %div{:class => "row"} %div{:class => "col-md-3"} - %h2 prefLabel property URIs used for OWL ontologies + %h2= t("landscape.owl_ontology_preflabel_uris") %div#prefLabelPropertyPieChartDiv %div{:class => "col-md-3"} - %h2 synonym property URIs used for OWL ontologies + %h2= t("landscape.owl_ontology_synonym_uris") %div#synonymPropertyPieChartDiv %div{:class => "col-md-3"} - %h2 definition property URIs used for OWL ontologies + %h2= t("landscape.owl_ontology_definition_uris") %div#definitionPropertyPieChartDiv %div{:class => "col-md-3"} - %h2 author property URIs used for OWL ontologies + %h2= t("landscape.owl_ontology_author_uris") %div#authorPropertyPieChartDiv %hr{:style => "margin-top: 5em; margin-bottom: 5em;"} %div{:class => "panel panel-success", :style => "margin-top: 2em;"} %div{:class => "panel-heading"} - %h3{:class => "panel-title"} Ontologies types + %h3{:class => "panel-title"}= t("landscape.ontologies_properties") %div{:class => "panel-body"} %div{:class => "row"} %div{:class => "col-md-4"} - %h2 Format used + %h2= t("landscape.ontologies_formats") %canvas#formatCanvas %div{:class => "col-md-4"} - %h2 Ontology types + %h2= t("landscape.ontologies_properties") %canvas#isOfTypeCanvas %div{:class => "col-md-4"} - %h2 Ontology formality levels + %h2= t("landscape.ontology_formality_levels") %canvas#formalityLevelCanvas @@ -94,16 +93,16 @@ %div{:class => "panel panel-success", :style => "margin-top: 2em;"} %div{:class => "panel-heading"} - %h3{:class => "panel-title"} Contributors to ontologies development + %h3{:class => "panel-title"}= t("landscape.ontologies_contributors") %div{:class => "panel-body"} %div{:class => "row"} %div{:class => "col-md-6"} - %h1 Most active people - %p Most mentioned people as contact, creator, contributor, curator. + %h1= t("landscape.most_active_people") + %p= t("landscape.most_mentioned_people") %div#peopleCloudChart{:style => "width: 100%; height: 350px; margin-top: 2em; border-right: 1px solid #ccc;"} %div{:class => "col-md-6"} - %h1 Most active organizations - %p Organizations that fund and endorse the greatest number of ontologies. + %h1= t("landscape.most_active_organizations") + %p= t("landscape.funding_endorsing_organizations") %div#orgCloudChart{:style => "width: 100%; height: 350px; margin-top: 2em;"} %hr{:style => "margin-top: 4em; margin-bottom: 4em;"} @@ -111,23 +110,22 @@ %div{:class => "panel panel-success", :style => "margin-top: 2em;"} %div{:class => "panel-heading"} %h3{:class => "panel-title"} - Ontologies activity on - = $SITE + = t("landscape.ontologies_activity_on", site: $SITE) %div{:class => "panel-body"} %div{:class => "row"} %div{:class => "col-md-6"} - %h1 Most active people as reviewer - %p People that posted notes, review and projects + %h1= t("landscape.most_active_people_as_reviewer") + %p= t("landscape.most_mentioned_people_as_reviewer") %div#notesPeopleCloudChart{:style => "width: 100%; height: 350px; margin-top: 2em; border-right: 1px solid #ccc;"} %div{:class => "col-md-6"} - %h1 Most active ontologies - %p Ontologies that have notes, review and projects + %h1= t("landscape.most_active_ontologies") + %p= t("landscape.ontologies_with_notes_reviews_projects") %div#notesOntologiesCloudChart{:style => "width: 100%; height: 350px; margin-top: 2em;"} %hr{:style => "margin-top: 5em; margin-bottom: 5em;"} - %h1 Ontology relations network - %p{:style => "margin-bottom: 1em;"} Relations between ontologies stored in the portal + %h1= t("landscape.ontology_relations_network") + %p{:style => "margin-bottom: 1em;"}= t("landscape.relations_between_stored_ontologies") %div{:style => "display: flex;"} %div{:style => "text-align: left; width: 23em;"} @@ -135,7 +133,7 @@ = check_box_tag "selectedRelations[]", relation, true, :id => relation = label_tag relation, relation %br - %button{:type => "button", :class => "btn btn-success", :onclick => "buildNetwork(ontologyRelationsArray)", :style => "margin-bottom: 1em;"} Filter Network + %button{:type => "button", :class => "btn btn-success", :onclick => "buildNetwork(ontologyRelationsArray)", :style => "margin-bottom: 1em;"}= t("landscape.filter_network") %div{:style => "width: 100%;"} %div#networkContainer{:style => "height: 80vh; width: 100%; border:1px solid #cecece;"} @@ -144,13 +142,13 @@ -if fairness_service_enabled? %hr{:style => "margin-top: 5em; margin-bottom: 5em;"} %h1 - Ontology FAIRness Evaluator (O’FAIRe) + = t("landscape.ontology_fairness_evaluator") %div#fairness_assessment.p-2 = render partial: "fair_score_landscape" %hr{:style => "margin-top: 5em; margin-bottom: 5em;"} %div - %h1 Average metrics + %h1= t("landscape.average_metrics") %table.minimal.align-right{width: "30%", style: "margin-bottom: 3em;"} %tbody - for metric in @metrics_average diff --git a/app/views/layouts/_footer.html.haml b/app/views/layouts/_footer.html.haml index 2cdd9b7c0..8cc227599 100644 --- a/app/views/layouts/_footer.html.haml +++ b/app/views/layouts/_footer.html.haml @@ -15,7 +15,7 @@ .footer-social-media-links - $FOOTER_LINKS[:social].each do |link| %a{:href => link[:link], :target => "_blank"} - %img{:src => "#{asset_path(link[:logo])}"}/ + = inline_svg(link[:logo], width: "32px", height: "32px") .footer-nav-links - $FOOTER_LINKS[:sections].each do |key, section_links| %div diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index 0530d3209..b7a03c84e 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -1,5 +1,5 @@ - + lang="<%=I18n.locale%>"> @@ -16,17 +16,14 @@ <%if @title.nil?%><%=$ORG_SITE%><%else%><%="#{@title} | #{$ORG_SITE}"%><%end%> - - <%= stylesheet_link_tag "https://use.fontawesome.com/releases/v5.2.0/css/all.css", integrity: "sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ", crossorigin: "anonymous" %> <%= stylesheet_link_tag "application" %> <%= javascript_include_tag "vendor" %> - <%= javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.min.js" %> <%= javascript_include_tag "//ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.min.js" %> - - :javascript - $(document).ready(function(){ - jQuery(document).data().bp.ontPropertiesTab = {}; - - jQuery(document).data().bp.ontPropertiesTab.init = function() { - var source = jQuery("#property-details").html(); - var propDetailsTemp = Handlebars.compile(source); - - $("#propTree").NCBOPropertyTree({ - ontology: "#{@ontology.acronym}", - ncboUIURL: jQuery(document).data().bp.config.ui_url, - width: "100%", - onInit: function() { - var ontPropTree = $("#propTree").data().NCBOPropertyTree; - var data = $("#propTree").find("a:first").data(); - if (data) { - ontPropTree.selectClass(data.id); - data.id = decodeURIComponent(data.id); - jQuery("#prop_contents").html(propDetailsTemp(data)); - } - } - }).on("afterSelect", function (e, classId, label, node) { - var data = node.data(); - data.id = decodeURIComponent(data.id); - jQuery("#prop_contents").html(propDetailsTemp(data)); - }); - } - - jQuery(document).data().bp.ontPropertiesTab.init(); - - }) += 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{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' diff --git a/app/views/ontologies/sections/visualize.html.haml b/app/views/ontologies/sections/visualize.html.haml index 83b470c2e..0da9daab7 100644 --- a/app/views/ontologies/sections/visualize.html.haml +++ b/app/views/ontologies/sections/visualize.html.haml @@ -1,31 +1,31 @@ -= turbo_frame_tag 'classes' do - - unless @error - - @title = "#{@ontology.name} - #{@concept.prefLabel}" += render TurboFrameComponent.new(id: "classes", data: {"turbo-frame-target": "frame"} ) do + - unless @error || @concept.nil? + - @title = "#{@ontology.acronym} - #{@concept.prefLabel}" - @new_term_request_ontologies - @enable_ontolobridge = !$NEW_TERM_REQUEST_ONTOLOGIES.nil? && $NEW_TERM_REQUEST_ONTOLOGIES.include?(@ontology.acronym) %div.tooltip %div#bd_content.bd_content.explore{data:{controller: 'container-splitter'}} - %div.sidebar.d-flex.flex-column.p-1.mr-1{data:{'container-splitter-target': 'container'}} + %div.sidebar{data:{'container-splitter-target': 'container'}} = render partial: 'ontologies/concepts_browsers/concepts_browser' - %div#concept_content.d-flex.flex-column.card.p-1.ml-2{data:{'container-splitter-target': 'container'}} + %div#concept_content.d-flex.flex-column{data:{'container-splitter-target': 'container'}} = render partial: 'concepts/show' - form_for(:search, :url => {:controller =>'search',:action=>'fetch_results'},:html=>{:id=>'search_form'}) do |f| %input{:name => "search[ontologies][]", :type => "hidden", :value => @ontology.acronym}/ = hidden_field :search, :search_type, :value=>"contains" = hidden_field :search, :keyword, :value=>"",:id=>"search_keyword" - - else # found an error - = "

    #{@error}

    " - + - else + = render Display::AlertComponent.new(type: 'danger') do + = @concept.nil? ? 'No class/concept found' : @error :javascript - $(document).ready(function() { - const ontology_acronym = "#{@ontology.acronym}" - let data = #{raw @instance_details.to_json} || null + $(document).ready(function() { + const ontology_acronym = "#{@ontology.acronym}" + let data = #{raw @instance_details.to_json} || null - if(data && Object.keys(data).length !== 0){ - $.facebox({ajax: `/ontologies/${ontology_acronym}/instances/${encodeURIComponent(data["@id"])}`}) - } - }) + if(data && Object.keys(data).length !== 0){ + $.facebox({ajax: `/ontologies/${ontology_acronym}/instances/${encodeURIComponent(data["@id"])}`}) + } + }) diff --git a/app/views/ontologies/submit_success.html.haml b/app/views/ontologies/submit_success.html.haml index b77274868..7e042d164 100644 --- a/app/views/ontologies/submit_success.html.haml +++ b/app/views/ontologies/submit_success.html.haml @@ -16,7 +16,7 @@ We will now put your ontology in the queue to be processed. Please keep in mind that it may take up to several hours before #{$SITE} users will be able to explore and search your ontology. %p - When your ontology is ready for viewing, it will be available here: + When your ontology is ready for viewing, you will receive an email notiifcation and it will be available here: = link_to ontology_url(@ontology.acronym), ontology_path(@ontology.acronym) %p If you have any questions or problems, please email the #{$SITE} support team at: diff --git a/app/views/ontologies_metadata_curator/_attribute_inline.html.haml b/app/views/ontologies_metadata_curator/_attribute_inline.html.haml index a58abd501..3a3f8aa53 100644 --- a/app/views/ontologies_metadata_curator/_attribute_inline.html.haml +++ b/app/views/ontologies_metadata_curator/_attribute_inline.html.haml @@ -1,27 +1,35 @@ -%div - - if @errors - %div - = render AlertMessageComponent.new(id:acronym+'-'+attribute+'-error', type: 'danger') do - = @errors.map{|e| e[:error]}.join(',') -%div - - if attribute == "contact" - = raw submission.contact.map {|c| [c["name"], c["email"]].join(", ") if c.member?(:name) && c.member?(:email)}.join("
    ") - - elsif attribute == "naturalLanguage" - - lang_codes = [] - - submission.send(attribute).each do |lang| - - if (lang.to_s.eql?("en") || lang.to_s.eql?("eng") || lang.to_s.eql?("http://lexvo.org/id/iso639-3/eng")) - - lang_codes << "gb" - - elsif lang.to_s.start_with?("http://lexvo.org") - - lang_codes << $LEXVO_TO_FLAG[lang] - - else - - lang_codes << lang - %ul{:class => "f32"} - - lang_codes.each do |lang_code| - %li{:class => "flag " + lang_code, :style => "margin-right: 0.5em;"} - - elsif attribute == "ontology" - = acronym - - else - - if submission.instance_values[attribute.to_s].class == String && submission.instance_values[attribute.to_s][0..3] == "http" - %a{:href=> ""+ submission.instance_values[attribute.to_s] +""}= submission.instance_values[attribute.to_s] - - else - = submission.instance_values[attribute.to_s] \ No newline at end of file += render TurboFrameComponent.new(id: id) do + %div.d-flex.justify-content-between.align-items-center + %div.d-flex.justify-content-between + %div + - acronym = ontology.acronym + - equivalents = equivalent_properties(attribute) + - equivalents.each do |attr| + %div.d-flex + - if equivalents.size > 1 + %span= "#{attr}: " + %div + %div + = error_message_alert + %div + - if attribute == "contact" + = raw submission.contact.map {|c| [c["name"], c["email"]].join(", ") if c.member?(:name) && c.member?(:email)}.join("
    ") + - elsif attribute == "naturalLanguage" + - submission.send(attribute).each do |lang| + = render LanguageFieldComponent.new(value: lang) + - elsif attribute == "ontology" + = acronym + - else + - if submission.instance_values[attribute.to_s].is_a?(String) && %w[http https].include?(submission.instance_values[attribute.to_s][0..3]) + %a{:href=> ""+ submission.instance_values[attribute.to_s] +""}= submission.instance_values[attribute.to_s] + - else + - Array(submission.instance_values[attribute.to_s]).each do |value| + - if value.is_a?(LinkedData::Client::Models::Agent) + %div + = display_agent(value, link: false) + - else + %p + = value + %div + = edit_submission_property_link(ontology.acronym, submission.submissionId, attribute) do + %i.far.fa-edit \ No newline at end of file diff --git a/app/views/ontologies_metadata_curator/_attribute_inline_editable.html.haml b/app/views/ontologies_metadata_curator/_attribute_inline_editable.html.haml index b2fdeedcb..55d4b5ec4 100644 --- a/app/views/ontologies_metadata_curator/_attribute_inline_editable.html.haml +++ b/app/views/ontologies_metadata_curator/_attribute_inline_editable.html.haml @@ -6,19 +6,5 @@ = hidden_field object_name(acronym, submission_id), :ontology, value: acronym = hidden_field object_name(acronym, submission_id), :id, value: submission_id = hidden_field_tag :attribute, attribute - = render TurboFrameComponent.new(id: id) do - %div.d-flex.justify-content-between.align-items-center - %div.d-flex.justify-content-between - %div - - equivalents = equivalent_properties(attribute) - - equivalents.each do |attr| - %div.d-flex - - if equivalents.size > 1 - %span= "#{attr}: " - %div - = render_submission_attribute_inline(attr.to_s, submission, acronym) - - - %div - %a.btn.btn-sm.btn-light{href: "ontologies_metadata_curator/#{ontology.acronym}/submissions/#{submission_id}?properties=#{attribute}&inline_save=true", data: {turbo: true}} - %i.far.fa-edit + = render partial: "ontologies_metadata_curator/attribute_inline", locals: { id: id, attribute: attribute, + submission: submission, ontology: ontology } diff --git a/app/views/ontologies_metadata_curator/_form_edit.html.haml b/app/views/ontologies_metadata_curator/_form_edit.html.haml index 533cbde3b..06df06d03 100644 --- a/app/views/ontologies_metadata_curator/_form_edit.html.haml +++ b/app/views/ontologies_metadata_curator/_form_edit.html.haml @@ -23,9 +23,9 @@ %a.nav-link{id: "pills-"+onto+sub_i+"-tab", "data-toggle": "pill", href: "#pills-"+onto+sub_i, class: index.zero? ? 'active' : ''} = ontology_submission_id_label(onto, sub_i) %div#change_all_warning_container - = render AlertMessageComponent.new(id:'change_all_warning', type: 'warning') do + = render Display::AlertComponent.new(type: 'warning') do %strong Apply the change for all - will update the current displayed content to all the following submssions: + will update the current displayed content to all the following submissions: - @selected_ontologies.map{|x| ontology_submission_id_label(*x)}.each do |onto| %span.badge.badge-primary = onto diff --git a/app/views/ontologies_metadata_curator/_metadata_tab.html.haml b/app/views/ontologies_metadata_curator/_metadata_tab.html.haml index d0c2abf76..30c4a514c 100644 --- a/app/views/ontologies_metadata_curator/_metadata_tab.html.haml +++ b/app/views/ontologies_metadata_curator/_metadata_tab.html.haml @@ -3,11 +3,11 @@ = form_tag("/ontologies_metadata_curator/result", method: "post", data: { turbo: true, turbo_frame: 'selection_metadata_form'}) do %div %div.mx-1.pt-3 - = render FormGroupComponent.new(label:' Ontologies', inline: false) do - = render :partial => "shared/ontology_picker", locals: {sel_text: '', selected_ontologies: @ontologies_ids} + - init_ontology_picker(nil, @ontologies_ids) + = select_input(label: 'Ontologies', name: "ontology[ontologyId]", values: @onts_for_select, selected: @ontologies_ids, multiple: true, data: {placeholder: t("select_ontologies")}) %div.d-flex.align-items-center.mb-5 %div.mx-1{style: 'width: 65%'} - = render MetadataSelectorComponent.new(label: 'Metadata Properties', values: submission_editable_properties , selected: @metadata_sel) + = submission_metadata_selector %div.mx-1.mt-3{style: 'width: 15%'} = render SwitchInputComponent.new(id:"show_submissions", name: "show_submissions", label: "Include all submissions") %div.mt-3.flex-shrink-0 diff --git a/app/views/ontologies_metadata_curator/_metadata_table.html.haml b/app/views/ontologies_metadata_curator/_metadata_table.html.haml index 73850816d..22669a256 100644 --- a/app/views/ontologies_metadata_curator/_metadata_table.html.haml +++ b/app/views/ontologies_metadata_curator/_metadata_table.html.haml @@ -18,7 +18,7 @@ %th %div = render SwitchInputComponent.new(id: meta, name: 'selected_metadata[]', value: meta) do - %h6{style:'margin-top: 0.2rem'}=generate_attribute_label(meta, label_tag_sym: :span) + %h6{style:'margin-top: 0.2rem'}=attr_label(meta, attr_metadata: attr_metadata(meta)) %tbody - @submissions.each do |submission| diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index b4112b506..128890008 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -1,11 +1,13 @@ - @title = "Editing Project #{@project.name}" -%h1{:style => "padding: 10px;"} - Editing project -%p{:style => "padding-left: 10px;"} - / TODO_REV: Enable project delete - / = link_to("Delete Project", @project, :confirm => "Are you sure?", :method => :delete) -= form_for(:project, url: project_path(@project.acronym), html: {method: :put}) do |f| - = render partial: 'form', locals: { f: f } +%div.container + + %h1{:style => "padding: 10px;"} + Editing project %p{:style => "padding-left: 10px;"} - = submit_tag "Cancel" - = f.submit "Update Project", :class => "blueButton" + / TODO_REV: Enable project delete + / = link_to("Delete Project", @project, :confirm => "Are you sure?", :method => :delete) + = form_for(:project, url: project_path(@project.acronym), html: {method: :put}) do |f| + = render partial: 'form', locals: { f: f } + %p{:style => "padding-left: 10px;"} + = submit_tag "Cancel" + = f.submit "Update Project", :class => "blueButton" diff --git a/app/views/projects/index.html.haml b/app/views/projects/index.html.haml index 8d89ca399..e17084d21 100644 --- a/app/views/projects/index.html.haml +++ b/app/views/projects/index.html.haml @@ -1,78 +1,78 @@ -- @title = "Project Listing" +- @title = t("projects.title") +%div.container + #DescriptionDialog{:style => "display: none", :title => t("projects.project_description")} + %p= t("projects.description_text") + %h1.tab_header= t("projects.self") + %p.tab_description + = t("projects.index.intro", site: "#{$SITE}") + = link_to(help_path(anchor: "Projects_Tab"), id: "projects-help", aria: {label: t("projects.view_projects_help")}) do + %i.fas.fa-question-circle.fa-lg{aria: {hidden: "true"}, style: "margin-left: .25em"} -#DescriptionDialog{:style => "display: none", :title => "project description"} - %p Description text -%h1.tab_header Projects -%p.tab_description - = t(".intro", site: "#{$SITE}") - = link_to(help_path(anchor: "Projects_Tab"), id: "projects-help", aria: {label: "View projects help"}) do - %i.fas.fa-question-circle.fa-lg{aria: {hidden: "true"}, style: "margin-left: .25em"} - -%div{:style => "padding:10px;"} - %span - - if session[:user].nil? - = button_to "Create New Project", "/login", :method => :get - - else - = button_to "Create New Project", new_project_path, :method => :get - %br/ - %br/ - %table#projects.zebra{:cellpadding => "0", :cellspacing => "0"} - %thead - %tr - %th Project - %th Description - %th Contacts - %th Institutions - %th Ontologies - - if current_user_admin? - %th User - %th Created - %tbody - - for project in @projects + %div{:style => "padding:10px;"} + %span + - if session[:user].nil? + = button_to t("projects.create_new_project"), "/login", :method => :get + - else + = button_to t("projects.create_new_project"), new_project_path, :method => :get + %br/ + %br/ + %table#projects.zebra{:cellpadding => "0", :cellspacing => "0"} + %thead %tr - / Project name, home page, and controls for editors - %td{:style => "vertical-align:top;", :width => "30%"} - %strong= link_to(project.name, project_path(project.acronym)) - %br/ - %span{:style => "font-size:75%; vertical-align:bottom;"} - = link_to("Home Page", project.homePage, target: "_blank", rel: "nofollow") - %span.ui-icon.ui-icon-extlink{:style => "display: inline-block; vertical-align: text-bottom;"} - \   - - if session[:user] && (project.creator == session[:user].id || session[:user].admin?) - = link_to("Edit", edit_project_path(project.acronym)) - / TODO_REV: Enable delete project for admins - - if current_user_admin? -    - = link_to("Delete (admin only)", project_path(project.acronym), :method => :delete, :confirm => "Are you sure?") - / Project description (may be truncated with a dialog) - %td{:style => "vertical-align:top;", :width => "50%"} - - if ! project.description.nil? - - descLength = 250 - - if project.description.length < descLength - = project.description - - else - - descShort = smart_truncate(project.description, :words => 20) - %span{:style => "cursor:help;", :title => project.description} - = descShort - / Contacts - %td{:style => "vertical-align:top;", :width => "10%"} - = raw project.contacts - / Institutions - %td{:style => "vertical-align:top;", :width => "8%"} - = raw project.institution - / Ontologies - %td{:style => "vertical-align:top;", :width => "2%"} - - ontologyCount = 0 - - ontologyLabels = "" - - for ontology in project.ontologyUsed - - ontologyLabels += @ontologies_hash[ontology].name + "\n" rescue next - - ontologyCount += 1 - - if ontologyCount > 0 - %span{:style => "cursor:help;text-decoration: none; border-bottom:1px dotted;", :title => ontologyLabels}= ontologyCount - - else - = ontologyCount + %th= t("projects.self") + %th= t("projects.description") + %th= t("projects.contacts") + %th= t("projects.institutions") + %th= t("projects.ontologies") - if current_user_admin? - %td - = project.creator.map {|c| c.split("/").last}.join(", ") - %td - = project.created + %th= t("projects.creator") + %th= t("projects.created") + %tbody + - for project in @projects + %tr + / Project name, home page, and controls for editors + %td{:style => "vertical-align:top;", :width => "30%"} + %strong= link_to(project.name, project_path(project.acronym)) + %br/ + %span{:style => "font-size:75%; vertical-align:bottom;"} + = link_to(t('projects.home_page'), project.homePage, target: "_blank", rel: "nofollow") + %span.ui-icon.ui-icon-extlink{:style => "display: inline-block; vertical-align: text-bottom;"} + \   + - if session[:user] && (project.creator == session[:user].id || session[:user].admin?) + = link_to(t("projects.edit"), edit_project_path(project.acronym)) + / TODO_REV: Enable delete project for admins + - if current_user_admin? +    + = link_to(t("projects.delete_admin_only"), project_path(project.acronym), :method => :delete, :confirm => t("projects.delete_confirm")) + / Project description (may be truncated with a dialog) + %td{:style => "vertical-align:top;", :width => "50%"} + - if ! project.description.nil? + - descLength = 250 + - if project.description.length < descLength + = project.description + - else + - descShort = smart_truncate(project.description, :words => 20) + %span{:style => "cursor:help;", :title => project.description} + = descShort + / Contacts + %td{:style => "vertical-align:top;", :width => "10%"} + = raw project.contacts + / Institutions + %td{:style => "vertical-align:top;", :width => "8%"} + = raw project.institution + / Ontologies + %td{:style => "vertical-align:top;", :width => "2%"} + - ontologyCount = 0 + - ontologyLabels = "" + - for ontology in project.ontologyUsed + - ontologyLabels += @ontologies_hash[ontology].name + "\n" rescue next + - ontologyCount += 1 + - if ontologyCount > 0 + %span{:style => "cursor:help;text-decoration: none; border-bottom:1px dotted;", :title => ontologyLabels}= ontologyCount + - else + = ontologyCount + - if current_user_admin? + %td + = project.creator.map {|c| c.split("/").last}.join(", ") + %td + = project.created diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index fa369c0fe..941fbcd6a 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,27 +1,30 @@ - @title = "Project #{@project.name}" -%div{:style => "padding: 1em;"} - %h1{:style => "font-size: xx-large;"}= @project.name - - if session[:user] && (@project.creator.include?(session[:user].id) || session[:user].admin?) - = link_to "Edit Project", edit_project_path(@project.acronym) - %br/ - %br/ - %p{:style => "margin-bottom: 5px;"} - %strong Description: - = @project.description - %p{:style => "margin-bottom: 5px;"} - %strong Institution: - = @project.institution - %p{:style => "margin-bottom: 5px;"} - %strong Contacts: - = @project.contacts - %p - %strong Home Page: - - if @project.homePage - = link_to @project.homePage, @project.homePage, rel: "nofollow" - %h2{:style => "padding-top: 1em;"} Ontologies Used - - if @ontologies_used.empty? - No ontologies are currently associated with this project - %table.zebra{:cellpadding => "0", :cellspacing => "0", :width => "70%"} - %ul - - for ontology in @ontologies_used - %li= link_to(ontology["name"], ontology_path(ontology["acronym"])) \ No newline at end of file + +%div.container + + %div{:style => "padding: 1em;"} + %h1{:style => "font-size: xx-large;"}= @project.name + - if session[:user] && (@project.creator.include?(session[:user].id) || session[:user].admin?) + = link_to "Edit Project", edit_project_path(@project.acronym) + %br/ + %br/ + %p{:style => "margin-bottom: 5px;"} + %strong Description: + = @project.description + %p{:style => "margin-bottom: 5px;"} + %strong Institution: + = @project.institution + %p{:style => "margin-bottom: 5px;"} + %strong Contacts: + = @project.contacts + %p + %strong Home Page: + - if @project.homePage + = link_to @project.homePage, @project.homePage, rel: "nofollow" + %h2{:style => "padding-top: 1em;"} Ontologies Used + - if @ontologies_used.empty? + No ontologies are currently associated with this project + %table.zebra{:cellpadding => "0", :cellspacing => "0", :width => "70%"} + %ul + - for ontology in @ontologies_used + %li= link_to(ontology["name"], ontology_path(ontology["acronym"])) \ No newline at end of file diff --git a/app/views/properties/_properties_tree.html.haml b/app/views/properties/_properties_tree.html.haml new file mode 100644 index 000000000..71fec912f --- /dev/null +++ b/app/views/properties/_properties_tree.html.haml @@ -0,0 +1,25 @@ +%div#propTree + +:javascript + // TODO migrate this backend rendered component + $(document).ready(() => { + $('#propTree').NCBOPropertyTree({ + ontology: '#{@ontology.acronym}', + ncboUIURL: '', + width: '100%', + onInit: function () { + let activeElem = $('#propTree a')[0] + if (activeElem) { + activeElem.click() + } + } + }).on('afterSelect', function (e, classId, label, node) { + e.target.dispatchEvent(new CustomEvent('change', { + bubbles: true, + detail: { + data: {id: decodeURIComponent(node.data().id)} + } + })) + }) + }) + \ No newline at end of file diff --git a/app/views/properties/_show.html.haml b/app/views/properties/_show.html.haml new file mode 100644 index 000000000..f315b56ed --- /dev/null +++ b/app/views/properties/_show.html.haml @@ -0,0 +1,14 @@ += render TurboFrameComponent.new(id: 'property', data: {"turbo-frame-target": "frame"}) do + - if !@property + = render LoaderComponent.new + - else + = render ConceptDetailsComponent.new(id:'property-details', acronym: @acronym, + 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: 'ID'}, {td: c.concept_properties[:id][:values]}) if c.concept_properties[:id][:values].present? + - t.add_row({th: 'Labels'}, {td: c.concept_properties[:prefLabel][:values]}) if c.concept_properties[:prefLabel][:values].present? + - t.add_row({th: 'Definitions'}, {td: c.concept_properties[:definition][:values].join(', ')}) if c.concept_properties[:definition][:values].present? + - t.add_row({th: 'Parent'}, {td: c.concept_properties[:parents][:values].join(', ')}) if c.concept_properties[:parents][:values].present? diff --git a/app/views/recommender/index.html.haml b/app/views/recommender/index.html.haml index 7abac68a6..beaed22e0 100644 --- a/app/views/recommender/index.html.haml +++ b/app/views/recommender/index.html.haml @@ -1,108 +1,121 @@ -- @title = "Recommender" +- @title= t('recommender.title') +%div.container + %h2.mt-5= t('recommender.ontology_recommender') + %p + = t('recommender.intro').html_safe + = link_to(Rails.configuration.settings.links[:help_recommender], id: "recommender-help", + "aria-label": "View ontology recommender help") do + %i.fas.fa-question-circle.fa-lg{"aria-hidden": "true"} -%h2.mt-5 Ontology Recommender -%p - = t('recommender.intro').html_safe - = link_to(Rails.configuration.settings.links[:help_recommender], id: "recommender-help", - "aria-label": "View ontology recommender help") do - %i.fas.fa-question-circle.fa-lg{"aria-hidden": "true"} -%form - = hidden_field_tag :recommender_sample_text, t('recommender.sample_text') - = hidden_field_tag :recommender_sample_keywords, t('recommender.sample_keywords') - -# Specify input format - %h5 Input - %div.form-check.form-check-inline - %input#radioItText.form-check-input{name: "input_type", type: "radio", value: "1", checked: "checked"} - %label.form-check-label{for: "radioItText"} Text - %div.form-check.form-check-inline - %input#radioItKeywords.form-check-input{name: "input_type", type: "radio", value: "2"} - %label.form-check-label{for: "radioItKeywords"} Keywords (separated by commas) - - -# Specify output format - %h5.pt-3 Output - %div.form-check.form-check-inline - %input#radioOtSingle.form-check-input{name: "output_type", type: "radio", value: "1", checked: "checked"} - %label.form-check-label{for: "radioOtSingle"} Ontologies - %div.form-check.form-check-inline - %input#radioOtSets.form-check-input{name: "output_type", type: "radio", value: "2"} - %label.form-check-label{for: "radioOtSets"} Ontology sets - - -# Input text or keywords - %div.form-group.mt-4 - = text_area_tag("inputText", nil, rows: 10, class: "form-control default", placeholder: "Paste a paragraph of text or some keywords to use in calculating ontology recommendations", aria: {describedby: "inputTextHelpBlock"}) - %div.card#inputTextHighlighted - %div.card-body - %small#inputTextHelpBlock.form-text - %a#insertInputLink{href: "javascript:void(0);"} insert sample input + %form + = hidden_field_tag :recommender_sample_text, "Melanoma is a malignant tumor of melanocytes which are found predominantly in skin but also in the bowel and the eye." + = hidden_field_tag :recommender_sample_keywords,"Backpain, White blood cell, Carcinoma, Cavity of stomach, Ductal Carcinoma in Situ, Adjuvant chemotherapy, Axillary lymph node staging, Mastectomy, tamoxifen, serotonin reuptake inhibitors, Invasive Breast Cancer, hormone receptor positive breast cancer, ovarian ablation, premenopausal women, surgical management, biopsy of breast tumor, Fine needle aspiration, entinel lymph node, breast preservation, adjuvant radiation therapy, prechemotherapy, Inflammatory Breast Cancer, ovarian failure, Bone scan, lumpectomy, brain metastases, pericardial effusion, aromatase inhibitor, postmenopausal, Palliative care, Guidelines, Stage IV breast cancer disease, Trastuzumab, Breast MRI examination" + + -# Specify input format + %h5= t('input') + %div.form-check.form-check-inline + %input#radioItText.form-check-input{name: "input_type", type: "radio", value: "1", checked: "checked"} + %label.form-check-label{for: "radioItText"}= t('text') + %div.form-check.form-check-inline + %input#radioItKeywords.form-check-input{name: "input_type", type: "radio", value: "2"} + %label.form-check-label{for: "radioItKeywords"}= t('keywords') + " ( " + t('keywords_separated_by_commas') + " ) " + + -# Specify output format + %h5.pt-3= t('output') + %div.form-check.form-check-inline + %input#radioOtSingle.form-check-input{name: "output_type", type: "radio", value: "1", checked: "checked"} + %label.form-check-label{for: "radioOtSingle"}= t('ontologies.self') + %div.form-check.form-check-inline + %input#radioOtSets.form-check-input{name: "output_type", type: "radio", value: "2"} + %label.form-check-label{for: "radioOtSets"}= t('ontology_sets') + + -# Input text or keywords + %div.form-group.mt-4 + = text_area_tag("inputText", @text, rows: 10, class: "form-control default", placeholder: t('recommender.paste_text_recommendations'), aria: {describedby: "inputTextHelpBlock"}) + %div.card#inputTextHighlighted + %div.card-body + %small#inputTextHelpBlock.form-text + %a#insertInputLink{href: "javascript:void(0);"}= t('insert_sample_text') - %a#advancedOptionsLink{:href => "javascript:void(0);"} Show advanced options >> + %a#advancedOptionsLink{:href => "javascript:void(0);"}= t('show_advanced_options') + " >>" - -# Advanced options - %div#advancedOptions.optionsBox - -# Specify weights - %h6 Weights configuration - %div.form-row - %div.form-group.col-md-2 - %label{for: "input_wc"} Coverage - = number_field_tag("input_wc", "0.55", min: "0", step: "1", class: "form-control") - %div.form-group.col-md-2 - %label{for: "input_wa"} Acceptance - = number_field_tag("input_wa", "0.15", min: "0", step: "1", class: "form-control") - %div.form-group.col-md-2 - %label{for: "input_wd"} Knowledge detail - = number_field_tag("input_wd", "0.15", min: "0", step: "1", class: "form-control") - %div.form-group.col-md-2 - %label{for: "input_ws"} Specialization - = number_field_tag("input_ws", "0.15", min: "0", step: "1", class: "form-control") - -# Specify ontology set size - %h6 Maximum ontologies per set - %div.form-row - %div.form-group.col-md-2 - = number_field_tag("input_max_ontologies", "3", in: 2...5, class: "form-control") - -# Specify ontologies - %div#ontologyPicker - = render(partial: "shared/ontology_picker") + -# Advanced options + %div#advancedOptions.optionsBox + -# Specify weights + %h6= t('weights_configuration') + %div.form-row + %div.form-group.col-md-2 + %label{for: "input_wc"}= t('coverage') + = number_field_tag("input_wc", "0.55", min: "0", step: "1", class: "form-control") + %div.form-group.col-md-2 + %label{for: "input_wa"}= t('acceptance') + = number_field_tag("input_wa", "0.15", min: "0", step: "1", class: "form-control") + %div.form-group.col-md-2 + %label{for: "input_wd"}= t('knowledge_detail') + = number_field_tag("input_wd", "0.15", min: "0", step: "1", class: "form-control") + %div.form-group.col-md-2 + %label{for: "input_ws"}= t('specialization') + = number_field_tag("input_ws", "0.15", min: "0", step: "1", class: "form-control") + -# Specify ontology set size + %h6= t('max_ontologies_per_set') + %div.form-row + %div.form-group.col-md-2 + = number_field_tag("input_max_ontologies", "3", in: 2...5, class: "form-control") + -# Specify ontologies + %div#ontologyPicker + = render(partial: "shared/ontology_picker") - %div.my-4 - = submit_tag("Get Recommendations", id: "recommenderButton", type: "button", class: "btn btn-primary") - = submit_tag("Edit Input", id: "editButton", type: "button", style: "display: none;", class: "btn btn-primary") - = content_tag(:span, class: "recommenderSpinner") do - = image_tag("spinners/spinner_000000_16px.gif", style: "vertical-align: middle;") + %div.my-4 + = submit_tag(t('get_recommendations'), id: "recommenderButton", type: "button", class: "btn btn-primary") + = submit_tag("Edit Input", id: "editButton", type: "button", style: "display: none;", class: "btn btn-primary") + = content_tag(:span, class: "recommenderSpinner") do + = image_tag("spinners/spinner_000000_16px.gif", style: "vertical-align: middle;") -%div.row#recommenderErrorsDisplay.mb-4 - %div.col - %span.notTextError - Please paste a paragraph of text or some keywords to use in calculating ontology recommendations. - %br/ - %span.sumWeightsError - The sum of the weights must be greater than zero. - %br/ - %span.rangeWeightsError - All the weights must be greater or equal to zero. - %br/ - %span.invalidWeightsError - All the weights must be valid numeric values. - %br/ - %span.invalidMaxOntError - The maximum ontologies per set must be a valid integer value. - %br/ - %span.maxOntologiesError - The maximum ontologies per set must be a number between 2 and 4. - %br/ - %span.generalError - Problem getting recommendations, please try again. - %br/ - %span#noResults - No recommendations found. - %br/ - %span#noResultsSets - There are no ontology sets recommended for the input provided. Please try the "Ontologies" output. - %br/ - %span.inputSizeError - Please use less than 500 words. If you need to annotate larger pieces of text you can use the Recommender Web Service + %div.row#recommenderErrorsDisplay.mb-4 + %div.col + %span.notTextError + = t('recommender.ontology_recommendation_input') + %br/ + %span.sumWeightsError + = t('recommender.weight_sum_greater_than_zero') + %br/ + %span.rangeWeightsError + = t('recommender.weights_greater_than_zero') + %br/ + %span.invalidWeightsError + = t('recommender.valid_numeric_weights') + %br/ + %span.invalidMaxOntError + = t('recommender.valid_integer_max_ontologies_per_set') + %br/ + %span.maxOntologiesError + = t('recommender.valid_max_ontologies_per_set_range') + %br/ + %span.generalError + = t('recommender.recommendation_error') + %br/ + %span#noResults + = t('recommender.no_recommendations') + %br/ + %span#noResultsSets + = t('recommender.no_sets_recommended') + %br/ + %span.inputSizeError + = t('recommender.text_length_limit') -%div.row#resultsDisplay - %div.col - %h5#resultsHeader - %div#recommender-results.mb-5 + %div.row#resultsDisplay + %div.col + %h5#resultsHeader + %div#recommender-results.mb-5 + +:javascript + window.addEventListener("load", function() { + const value = document.getElementById("inputText").value + document.getElementById("inputText").click(); + document.getElementById("inputText").value = value + if(document.getElementById("inputText").value != ''){ + document.getElementById("recommenderButton").click() + window.scrollBy(0, 1080); + } + }); \ No newline at end of file diff --git a/app/views/schemes/_scheme.html.haml b/app/views/schemes/_scheme.html.haml index 2fcf310c9..0ab0ac23c 100644 --- a/app/views/schemes/_scheme.html.haml +++ b/app/views/schemes/_scheme.html.haml @@ -7,6 +7,7 @@ exclude_keys: []) do |c| - c.header(stripped: true) do |t| - t.add_row({th: 'ID'} , {td: scheme["@id"]}) - - t.add_row({th: 'Preferred Name'} , {td: get_scheme_label(scheme)}) + - t.add_row({th: 'Preferred Name'} , {td: display_in_multiple_languages(get_scheme_label(scheme))}) - t.add_row({th: 'Type'} , {td: scheme["@type"]}) + diff --git a/app/views/schemes/_tree_view.html.haml b/app/views/schemes/_tree_view.html.haml index 333f4223d..d707d5655 100644 --- a/app/views/schemes/_tree_view.html.haml +++ b/app/views/schemes/_tree_view.html.haml @@ -8,17 +8,13 @@ - if main_scheme_label.nil? = no_main_scheme_alert %div - %ul.simpleTree{data:{controller: 'simple-tree history', action: 'clicked->history#updateURL'}} + %ul.simpleTree{data:{controller: 'simple-tree', action: 'clicked->history#updateURL'}} %li.root %ul - if main_scheme_label.nil? = raw tree_link_to_schemes(schemes_labels ,main_scheme_label, selected_scheme_id) - else %li.open - %a{id: main_scheme_label["@id"], - href: scheme_path(main_scheme_label["@id"]), - data: { turbo: "true", 'turbo-frame': 'scheme', schemeid: main_scheme_label["@id"]}, - class: selected_scheme_id.eql?(main_scheme_label["@id"]) ? "active" : nil } - = get_scheme_label(main_scheme_label) + = raw link_to_scheme(main_scheme_label, selected_scheme_id) %ul = raw tree_link_to_schemes(schemes_labels ,main_scheme_label, selected_scheme_id) \ No newline at end of file diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index 5235f869e..91664b0a8 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -1,49 +1,53 @@ -- @title = "Search" +- @title = t("search.title") %div.container.mt-5 - %h1.display-4 - Class Search + %h1.display-4 + = t("search.class_search") = form_tag("/search", method: "post") do %div.form-group = text_field_tag("search_keywords", nil, class: "form-control", aria: {describedby: "classSearchHelpBlock"}) %small#classSearchHelpBlock.form-text.text-muted - = t(".search_keywords_placeholder") - = link_to("help", Rails.configuration.settings.links[:help_search], id: "search-help", - aria: {label: "View search documentation"}, class: "float-right") + = t("search.index.search_keywords_placeholder") + = link_to(t('help'), Rails.configuration.settings.links[:help_search], id: "search-help", + aria: {label: t('search.view_search_documentation')}, class: "float-right") %div.form-group - = link_to("Show advanced options", "javascript:void(0)", id: "advanced_options", data: {text_swap: "Hide advanced options"}, class: "form-text") - + = link_to(t('search.show_advanced_options'), "javascript:void(0)", id: "advanced_options", data: {text_swap: t('search.hide_advanced_options')}, class: "form-text") + -# Advanced search options - %div#search_options{style: "display: none;"} + %div#search_options %div.form-group.row - %div.col-sm-2 Include in search: + %div.col-sm-2.mb-4 Search language + %div.col-sm-10.mb-4 + %div.w-25 + = search_language_selector + %div.col-sm-2= t("search.include_in_search") + ":" %div.col-sm-10 %div.form-check = check_box(:search, :include_properties, class: "form-check-input") - = label(:search, :include_properties, "Property values", class: "form-check-label definition", title: t(".property_definition")) + = label(:search, :include_properties, t('search.property_values'), class: "form-check-label definition", title: t(".property_definition")) %div.form-check = check_box(:search, :include_obsolete, class: "form-check-input") - = label(:search, :include_obsolete, "Obsolete classes", class: "form-check-label definition", title: t(".obsolete_definition")) + = label(:search, :include_obsolete,t('search.obsolete_classes'), class: "form-check-label definition", title: t(".obsolete_definition")) %div.form-check = check_box(:search, :include_views, class: "form-check-input") - = label(:search, :include_views, "Ontology views", class: "form-check-label") - %div.form-group.row - %div.col-sm-2 Narrow search to: + = label(:search, :include_views, t('search.ontology_views'), class: "form-check-label") + + %div.col-sm-2= t("search.narrow_search_to") + ":" %div.col-sm-10 %div.form-check = check_box(:search, :exact_match, class: "form-check-input") - = label(:search, :exact_match, "Exact matches", class: "form-check-label") + = label(:search, :exact_match, t("exact_matches"), class: "form-check-label") %div.form-check = check_box(:search, :require_definition, class: "form-check-input") - = label(:search, :require_definition, "Classes with definitions", class: "form-check-label") + = label(:search, :require_definition, t("search.classes_with_definitions"), class: "form-check-label") %div.form-group - %h6{style: "font-size: 10pt !important"} Categories - = select(:search, :categories, options_for_select(categories_for_select), {}, style: "width: 432px", multiple: "true", data: {placeholder: t(".categories_placeholder")}) + %h6{style: "font-size: 10pt !important"}= t("search.categories") + = select(:search, :categories, options_for_select(categories_for_select), {}, style: "width: 432px", multiple: "true", data: {placeholder: t("search.index.categories_placeholder")}) %div.form-group.mb-5{style: "width:432px"} - = render :partial => "shared/ontology_picker", locals: {sel_text: "Ontologies"} + = render :partial => "shared/ontology_picker", locals: {sel_text: t("search.ontologies")} - = button_tag("Search", id: "search_button", class: "btn btn-primary") + = button_tag(t('search.title'), id: "search_button", class: "btn btn-primary") = content_tag(:span, id: "search_spinner") do %img{src: asset_path('spinners/spinner_000000_16px.gif'), style: "vertical-align: middle;"} @@ -58,10 +62,16 @@ :javascript // Hash of ontology id => name, acronym for lookup use via JS + jQuery(document).ready(function() { + jQuery(document).data().bp.ontologies = #{Hash[LinkedData::Client::Models::Ontology.all(include_views: true).map {|o| [o.id, {name: o.name, acronym: o.acronym}]}].to_json.html_safe} if (jQuery("#search_keywords").val() !== "") { performSearch(); } }); + + + + diff --git a/app/views/shared/_concept_picker.html.haml b/app/views/shared/_concept_picker.html.haml index d1b32729a..a3b61e816 100644 --- a/app/views/shared/_concept_picker.html.haml +++ b/app/views/shared/_concept_picker.html.haml @@ -1,5 +1,5 @@ = text_field_tag(name, concept_label, class: "search_autocomplete form-control", - placeholder: "Start typing to select a class", + placeholder: 'Start typing to select a class/concept', data: {controller: "form-auto-complete", "form-auto-complete-ontology_id-value": ontology_acronym , "form-auto-complete-target_property-value": 'name', diff --git a/app/views/shared/_ontology_picker.html.erb b/app/views/shared/_ontology_picker.html.erb index 9ddd31268..ea646e798 100644 --- a/app/views/shared/_ontology_picker.html.erb +++ b/app/views/shared/_ontology_picker.html.erb @@ -31,17 +31,21 @@ var hideChosenResults = function(){

    - <%=sel_text%> + <%=t('select_ontologies_list')%>

    - <%=select form_object, form_attribute, options_for_select(@onts_for_select, selected_ontologies), { }, :multiple => 'true', "data-placeholder".to_sym => "Start typing to select ontologies or leave blank to use all", :style => "width: 432px;" %> + <%=select form_object, form_attribute, options_for_select(@onts_for_select, selected_ontologies), { }, :multiple => 'true', "data-placeholder".to_sym => t("select_ontologies"), :style => "width: 432px;" %>
    <%=render_advanced_picker(custom_ontologies, selected_ontologies)%> diff --git a/app/views/shared/_ontology_picker_single.html.erb b/app/views/shared/_ontology_picker_single.html.erb index 53e2b4f9d..5bcba1323 100644 --- a/app/views/shared/_ontology_picker_single.html.erb +++ b/app/views/shared/_ontology_picker_single.html.erb @@ -25,6 +25,6 @@
    - <%= select object_name, field_name, @onts_for_select, { :include_blank => true, :selected => selected }, :id => picker_id, :class => "ontology_picker_single form-control", "data-placeholder".to_sym => placeholder, disabled: disabled %> + <%= select_input(label: placeholder, name: "#{object_name}[#{field_name}]", values: @onts_for_select, selected: selected) %>
    diff --git a/app/views/submissions/_form.html.haml b/app/views/submissions/_form.html.haml deleted file mode 100644 index c98657349..000000000 --- a/app/views/submissions/_form.html.haml +++ /dev/null @@ -1,62 +0,0 @@ -:javascript - $(document).ready(function() { - jQuery("#ontology_submission_form").validate(); - }); - - - function onMetadataChange(){ - let frame = document.getElementById('metadata_by_ontology') - let properties = document.getElementById('search_metadata') - let required = document.getElementById('filter-required-only') - let selectedProperties = "all" - if(properties){ - selectedProperties = Array.from(properties.selectedOptions).map(({ value }) => value).join(',') - } - frame.src = "?properties=" + selectedProperties + "&required=" + required.checked + "&show_sections=true" - } - -- unless @errors.nil? - %div.form-group.row - %div.col-sm-8.offset-sm-2.enable-lists{style: "color:red;"} - %strong Errors On Form - %ul - - for error in @errors - - if error.is_a? Array - %ul - - error[1].each do |key, message| - %li - = message - - else - %li - = error - -%div#editMetadataDiv - %div{:style => "width: 50%;margin: 3em auto;"} - %p{:style => "text-align: center;"} - To understand the ontologies metadata: - %a{:href => "https://github.com/agroportal/documentation/wiki/Ontology-metadata", :target => "_blank"} see the Wiki - %div{:style => "text-align: center;"} - %span.asterik * fields are required - %br - = extractable_metadatum_tooltip({ text: 'Metadata that can be extracted from the ontology' , content: "Extractable metadatum"}) - - %div.d-flex.align-items-center.justify-content-between{onchange: "onMetadataChange()"} - - unless @filters_disabled - %div.w-75.mt-3 - - if @submission.id - = render MetadataSelectorComponent.new(label: 'Filter properties to show', values: submission_editable_properties , selected: nil, inline: true) - %div - = render SwitchInputComponent.new(id:"filter-required-only", name: "required-only", label: "Required only", checked: @required_only) - - -= render partial: 'form_content', locals: {id: 'metadata_by_ontology', acronym: @ontology.acronym, submissionId: @submission.submissionId} - -%div.form-group.row - %div.col-12.text-center - %label.col-form-label.font-italic.asterik * Fields marked with an asterisk are required. - -%div.form-group.row - %div.col-6 - = submit_tag button_text, class: "btn btn-primary" - %div.col-6.text-right - = link_to "Cancel", :back, class: "btn btn-warning" diff --git a/app/views/submissions/_form_content.html.haml b/app/views/submissions/_form_content.html.haml index 85896b8dc..1df57ccf8 100644 --- a/app/views/submissions/_form_content.html.haml +++ b/app/views/submissions/_form_content.html.haml @@ -1,146 +1,12 @@ -= render TurboFrameComponent.new(id:id) do - = hidden_field object_name, :ontology, value: acronym - = hidden_field object_name, :id, value: submissionId - %div#general-card.mt-4 - = metadata_section('general', 'General', collapsed: false) do - = attribute_container('format', required: true) do - = turbo_frame_tag "#{object_name(acronym, submissionId)}Format_from_group_input" do - = render partial: 'submissions/submission_format_form' - = attribute_text_field_container('version') +- unless @errors.nil? + = 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('') + %hr#edit-ontology-actions-devider + .edit-ontology-actions + .save-button + = render Buttons::RegularButtonComponent.new(id:'save-button', value: "Save", variant: "primary", size: "slim", type: "submit") do |btn| + - btn.icon_left do + - inline_svg_tag "check.svg" - = attribute_form_group_container('status', required: true) do |c| - - selected_status = c.value ? c.value : "alpha" - - status_options = %w[alpha beta production retired] - = select c.name, c.method_name, status_options, {selected: selected_status, required: true}, class: "form-control" - - = attribute_form_group_container('location', required: true) do - = render partial: 'submissions/submission_location_form' - - %div#key-properties-card.mt-4 - = metadata_section('key-properties', 'Key properties', collapsed: false) do - = form_group_attribute("URI") - = form_group_attribute("deprecated") - = form_group_attribute("hasOntologySyntax") do - %p - Properties taken from - = link_to "W3C URIs for file format", "https://www.w3.org/ns/formats/", target: "_blank" - - = form_group_attribute("hasFormalityLevel") do - %p - Properties taken from - = link_to "DCMI KOS type vocabularies", "http://wiki.dublincore.org/index.php/NKOS_Vocabularies#KOS_Types_Vocabulary", target: "_blank" - - = form_group_attribute("isOfType") - = form_group_attribute("naturalLanguage") do - %p - Consider using a - = link_to "Lexvo URI", "http://www.lexvo.org", target: "_blank" - with ISO639-3 code - %br - e.g.: http://lexvo.org/id/iso639-3/eng - - %div#description-card.mt-4 - = metadata_section('description', 'Description', collapsed: !@required_only || !@submission.id.nil?) do - -# Description - = attribute_form_group_container('description', required: true) do |c| - = text_area c.name, c.method_name, rows: 5, value: @submission.description, required: true, class: "form-control" - - -# Home page - = form_group_attribute('homepage') do - Enter a URL for the main page of your ontology. - - -# Documentation page - = form_group_attribute('documentation') do - Enter a URL for a page that provides ontology documentation. - - -# Publications page - = form_group_attribute('publication') do - Enter a URL for a page that lists publications about your ontology. - - -# Used ontology engineering tool - = form_group_attribute("usedOntologyEngineeringTool") - - = metadata_section('more-description-details', 'More description details', parent_id: "description-card") do - - for attr_name in %w[abstract notes keywords alternative identifier] - = form_group_attribute(attr_name) - - - data = sections - - - data.each do |d| - = metadata_section(d[0], d[1], parent_id: "description-card") do - - for attr in @metadata.select { |m| m['display'] == d[2] } - = form_group_attribute(attr["attribute"]) - - %div#ontology-dates-card.mt-4 - = metadata_section('ontology-dates', 'Dates', collapsed: !@required_only || !@submission.id.nil?) do - = form_group_attribute("released", default: Date.today, required: true) - = form_group_attribute("modificationDate") - = metadata_section('more-dates', 'More dates', parent_id: "ontology-dates-card") do - - for attr in @metadata.select { |m| m['display'] == 'dates' } - = form_group_attribute(attr["attribute"]) - - %div#licenses-card.mt-4 - = metadata_section('licenses', 'Licenses') do - = form_group_attribute("hasLicense") do - %p - Consider using a - = link_to "URI to describe your License", "http://rdflicense.appspot.com", target: "_blank" - %p - Consider using - = link_to "INRIA licentia", "http://licentia.inria.fr/", target: "_blank" - to choose your license - - = metadata_section('more-licensing-info', 'More licensing information', parent_id: "licenses-card") do - - for attr in @metadata.select { |m| m['display'] == 'license' } - = form_group_attribute(attr["attribute"]) - - %div#community.mt-4 - = metadata_section('contacts-panel', 'Contacts', parent_id: "community", collapsed: !@required_only || !@submission.id.nil?) do - -# Contact(s) - = attribute_form_group_container('contact' ,required: true) do |c| - - @submission.contact = [] unless @submission.contact && @submission.contact.size > 0 - = render NestedFormInputsComponent.new do |c| - - c.template do - = render partial: "submissions/submission_contact_form", locals: {contact: nil, index: 'NEW_RECORD'} - - c.empty_state do - = hidden_field_tag object_name+"[contact][#{@submission.contact.size}][email]" - = hidden_field_tag object_name+"[contact][#{@submission.contact.size}][name]" - - @submission.contact.each_with_index do |contact, i| - - c.row do - = render partial: "submissions/submission_contact_form", locals: {contact: contact, index: i} - - = form_group_attribute("hasContributor") - = form_group_attribute("hasCreator") - - = metadata_section('more-community-info', 'More community information', parent_id: "community") do - - for attr in @metadata.select { |m| m['display'] == 'community' } - = form_group_attribute(attr["attribute"]) - - = metadata_section('more-people-info', 'More people information', parent_id: "community") do - - for attr in @metadata.select { |m| m['display'] == 'people' } - = form_group_attribute(attr["attribute"]) - - %div#ontology-relations-more.mt-4 - = metadata_section('ontology-relations', 'Ontology relations', parent_id: "ontology-relations-more") do - = form_group_attribute("useImports") - = form_group_attribute("hasPriorVersion") - = form_group_attribute("isAlignedTo") - = form_group_attribute("ontologyRelatedTo") - - = metadata_section('more-relations', 'More relations', parent_id: "ontology-relations-more") do - - for attr in @metadata - - if attr["display"].eql?("relations") - = form_group_attribute(attr["attribute"]) - - %div#ontology-content-metrics.mt-4 - = metadata_section('ontology-content', 'Ontology content', parent_id: "ontology-content-metrics") do - = form_group_attribute("preferredNamespacePrefix") - = form_group_attribute("preferredNamespaceUri") - - = metadata_section('more-informations', 'More content informations', parent_id: "ontology-content-metrics") do - - for attr in @metadata.select { |m| m['display'] == 'content' } - = form_group_attribute(attr["attribute"]) - - = metadata_section('more-metrics-informations', 'More metrics informations', parent_id: "ontology-content-metrics") do - - for attr in @metadata.select { |m| m['display'] == 'metrics' } - = form_group_attribute(attr["attribute"]) \ No newline at end of file diff --git a/app/views/submissions/_submission_contact_form.html.haml b/app/views/submissions/_submission_contact_form.html.haml deleted file mode 100644 index 590a9dd6c..000000000 --- a/app/views/submissions/_submission_contact_form.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%div.d-flex - %div.w-50.mx-1.mt-3 - = render FormGroupComponent.new(name: object_name, method: "contact[#{index.to_s.upcase}][name]", label: 'Name' ) do |c1| - = text_field c1.name, "contact[#{index.to_s.upcase}][name]", value: contact ? contact["name"] : '' , class: "form-control flex-grow-1 mx-2" - %div.w-50.mx-1.mt-3 - = render FormGroupComponent.new(name: object_name, method: "contact[#{index.to_s.upcase}][email]", label: 'Email' ) do |c2| - = text_field c2.name, "contact[#{index.to_s.upcase}][email]", value: contact ? contact["email"] : '', class: "form-control flex-grow-1 mx-2" diff --git a/app/views/submissions/_submission_format_form.html.haml b/app/views/submissions/_submission_format_form.html.haml deleted file mode 100644 index fc2a1ba51..000000000 --- a/app/views/submissions/_submission_format_form.html.haml +++ /dev/null @@ -1,70 +0,0 @@ -:javascript - - function ontologyFormatChange(event){ - let selected = event.target.selectedOptions.item(0) - let options = document.querySelectorAll('.format_options') - Array.from(options).forEach( x => $(x).hide()) - switch(selected.value){ - case 'OWL': - jQuery("#owl_options").show() - break - case 'SKOS': - jQuery("#skos_options").show() - break - } - } - -= attribute_form_group_container('hasOntologyLanguage', label: 'Format', required: true) do |c| - - ont_formats = %w[OBO OWL UMLS SKOS].sort - - selected = @submission.hasOntologyLanguage ? @submission.hasOntologyLanguage : "OWL" - = select c.name, c.method_name, options_for_select(ont_formats, selected), {required: true}, {class: "form-control", onchange: 'ontologyFormatChange(event)'} - - c.help do - %div#skos_options.format_options{style: "display:#{skos? ? 'block': 'none'}"} - SKOS vocabularies submitted to BioPortal must contain a minimum of one concept scheme and top concept assertion. - Please refer to the NCBO wiki for a more #{link_to 'detailed explanation', 'https://www.bioontology.org/wiki/index.php/SKOSSupport', target: "_blank" } - with examples. - -%div#owl_options.form-group.row.format_options - %div.col-sm-8.offset-sm-4 - %a#collapseOWLOptions{href: "#owl-options-properties", role: "button", data: {toggle: "collapse"}, aria: {expanded: "false", controls: "owl-options"}, - class: "btn btn-outline-secondary btn-sm"} Show advanced OWL options - - %div#owl-options-properties.collapse - %div.form-group.row - %div.col-sm-8.offset-sm-2 - %label.lead Customize default property settings - = attribute_text_field_container('prefLabelProperty', label: 'Preferred name', inline: false) do |c| - - c.help do - Enter a property ID, or leave blank to use the default setting: - = link_to 'http://www.w3.org/2004/02/skos/core#prefLabel', 'http://www.w3.org/2004/02/skos/core#prefLabel', target: "_blank" - - = attribute_text_field_container('synonymProperty', label: 'Synonym', inline: false) do |c| - - c.help do - Enter a property ID, or leave blank to use the default setting: - = link_to 'http://www.w3.org/2004/02/skos/core#altLabel', 'http://www.w3.org/2004/02/skos/core#altLabel', target: "_blank" - - - = attribute_text_field_container('definitionProperty', label: 'Definition', inline: false) do |c| - - c.help do - Enter a property ID, or leave blank to use the default setting: - = link_to 'http://www.w3.org/2004/02/skos/core#definition', 'http://www.w3.org/2004/02/skos/core#definition', target: "_blank" - - - = attribute_text_field_container('authorProperty', label: 'Author', inline: false) do |c| - - c.help do - Enter a property ID, or leave blank to use the default setting: - = link_to 'http://purl.org/dc/elements/1.1/creator', 'http://purl.org/dc/elements/1.1/creator', target: "_blank" - - - %div.form-group.row - %div.col-sm-8.offset-sm-2 - %label.lead Identify obsolete classes - = attribute_text_field_container('obsoleteProperty', label: 'Obsolete property', inline: false) do |c| - - c.help do - Optionally enter a property ID that indicates obsolete status for ontology classes - (the property value must be set to "true"). Note that by default, BioPortal checks for existence of the owl:deprecated property. - - = attribute_text_field_container('obsoleteParent', label: 'Obsolete branch root', inline: false) do |c| - - c.help do - Optionally enter a class ID for the root of an obsolete branch. All classes in the branch will be marked as - obsolete, with the exception of the root class. diff --git a/app/views/submissions/_submission_location_form.html.haml b/app/views/submissions/_submission_location_form.html.haml deleted file mode 100644 index 07faedf61..000000000 --- a/app/views/submissions/_submission_location_form.html.haml +++ /dev/null @@ -1,61 +0,0 @@ -:javascript - $(document).ready(function() { - - // Properly display the information for 'File Location' based on radio button - if (jQuery("#submission_isRemote_0").is(":checked")) { - location_toggle("upload"); - } - if (jQuery("#submission_isRemote_1").is(":checked")) { - location_toggle("remote"); - } - - // Select default 'File Location' radio button - if (!jQuery("#submission_isRemote_0").is(":checked") && !jQuery("#submission_isRemote_1").is(":checked") && !jQuery("#submission_isRemote_2").is(":checked")) { - jQuery("#submission_isRemote_0").attr("checked", "checked") - } - }); - - // Show/hide location inputs - function location_toggle(input_div) { - jQuery('.hidden_field').hide(); - jQuery('.hidden_field input').attr("disabled", true); - jQuery('#' + input_div + " input").removeAttr("disabled"); - jQuery('#' + input_div).show(); - } - - -- if !@masterFileOptions - %div.form-check - - checked = @ontology.summaryOnly - = radio_button(object_name, :isRemote, 3, :onclick=>"$('.hidden_field').hide();", checked: checked, aria: {describedBy: "metadataHelpBlock"}, class: "form-check-input") - %label.form-check-label{for: "submission_isRemote_3"} - Metadata only - %small.form-text.text-muted#metadataHelpBlock Allow users to view and search your ontology metadata, but not its classes and properties. - %div.form-check - - checked = !(@submission.pullLocation.nil? || @submission.pullLocation.empty?) - = radio_button(object_name, :isRemote, 1, :onclick=>"location_toggle('remote');", checked: checked, aria: {describedBy: "loadFromURLHelpBlock"}, class: "form-check-input") - %label.form-check-label{for: "submission_isRemote_1"} - Load from URL - %small.form-text.text-muted#loadFromURLHelpBlock New versions loaded on a nightly basis. - - display = (checked and "" or "display:none;") - %div.hidden_field#remote{style: display} - = text_field(object_name, :pullLocation, value: @submission.pullLocation, aria: {describedBy: "enterURLHelpBlock"}, class: "form-control") - %small.form-text.text-muted#enterURLHelpBlock Enter a URL, including the name of your ontology source file, e.g., http://www.example.com/my_ontology.owl. - %div.form-check - - checked = (!@ontology.summaryOnly) && @submission.pullLocation.nil? - = radio_button(object_name, :isRemote, 0, :onclick=>"location_toggle('upload');", checked: checked, class: "form-check-input") - %label.form-check-label{for: "submission_isRemote_0"} - Upload local file - - display = (checked and "" or "display:none;") - %div.hidden_field#upload{style: display} - = file_field(object_name, :filePath, class: "mt-2") -- else - -# TODO: Has this section of code actually been tested? - = radio_button object_name, :isRemote, 0, :onclick=>"location_toggle('upload');", checked: true - Upload Local File - %br/ - %span{:style => "font-size:11px;"} (choose a file on your local file system to upload) - - display = (checked and "" or "display:none;") - %div#upload{style: display} - = file_field object_name, :filePath, required: true - = select(object_name, "masterFileName", @masterFileOptions, { include_blank: "Select primary file from zip contents", required: true}, {style: "border-color: red;"}) diff --git a/app/views/submissions/_submissions.html.haml b/app/views/submissions/_submissions.html.haml index 6a666118f..3b3286a29 100644 --- a/app/views/submissions/_submissions.html.haml +++ b/app/views/submissions/_submissions.html.haml @@ -4,24 +4,29 @@ - more_colspan = 7 - more_colspan = 6 if @ont_restricted - %div.click_versions_collapse + %div.click_versions_collapse.p-1 = render_alerts_container(AdminController) - %table#ontology_versions.table.table-sm.table-striped - %thead - %tr - - if @ontology.admin?(session[:user]) - %th.align-middle ID - %th.align-middle Version - %th - = generate_attribute_text("released", "Released") - %th - = generate_attribute_text("modificationDate", "Modified") - %th - = generate_attribute_text("creationDate", "Uploaded") - - unless @ont_restricted - %th.align-middle Downloads - - if @ontology.admin?(session[:user]) - %th.align-middle Actions + = render TableComponent.new(id: 'ontology_versions', stripped: false, borderless: true) do |t| + - t.header do |header| + - if @ontology.admin?(session[:user]) + - header.th do + %div.align-middle + ID + - header.th do + %div.align-middle + Version + - header.th do + = attr_label("modificationDate", "Modified", show_tooltip: false) + - header.th do + = attr_label("creationDate", "Submitted", show_tooltip: false) + - unless @ont_restricted + - header.th do + %div.align-middle + Downloads + - if @ontology.admin?(session[:user]) + - header.th do + %div.align-middle + Actions - begin - submission_ready = @ontology.explore.latest_submission({:include_status => 'ready', display: 'submissionId'}) @@ -30,33 +35,40 @@ - submission_readyId = -1 - @submissions.each_with_index do |sub, index| - hidden_row_class = index >= 5 ? "hidden_ont hidden_select" : "" - %tr{class: "#{hidden_row_class}", id: "submission_#{sub.submissionId}"} + - t.row(id:"submission_#{sub.submissionId}" , class_css: hidden_row_class) do |r| + - if @ontology.admin?(session[:user]) - %td - = sub.submissionId - %td - = raw status_link(sub, sub.submissionId==submission_readyId) - %td - = xmldatetime_to_date(sub.released) unless sub.released.nil? - %td - = xmldatetime_to_date(sub.modificationDate) unless sub.modificationDate.nil? - %td - = xmldatetime_to_date(sub.creationDate) unless sub.creationDate.nil? + - r.td { raw sub.submissionId } + - r.td do + = render SubmissionStatusComponent.new(sub, sub.submissionId==submission_readyId) + - r.td { xmldatetime_to_date(sub.modificationDate) unless sub.modificationDate.nil? } + - r.td { xmldatetime_to_date(sub.creationDate) unless sub.creationDate.nil? } + - unless @ont_restricted - %td - = raw download_link(sub, @ontology) + - r.td do + %div.dropdown + %button.btn.btn-outline-primary.rounded-pill.dropdown-toggle{type:"button", 'data-toggle':"dropdown", 'aria-expanded': "false", style:'white-space: nowrap'} + Download + %span.sr-only Toggle Dropdown + .dropdown-menu + - links = download_link(sub,@ontology) + - links.each do |value| + - link,label = value.values + %a.dropdown-item{ href: link }= label + + -# = raw download_link(sub, @ontology) - if @ontology.admin?(session[:user]) - %td + - r.td do %div.d-flex - %a.btn.btn-sm.btn-link{:href => "/ontologies/#{@ontology.acronym}/submissions/#{sub.submissionId}/edit"} + %a.btn.btn-sm.btn-link{:href => "/ontologies/#{@ontology.acronym}/submissions/#{sub.submissionId}/edit", 'data-turbo-frame':"_top"} %span Edit - unless index.zero? - alert_text = "Are you sure you want to delete submission " + sub.submissionId.to_s + " for ontology " + @ontology.acronym + "?
    This action CAN NOT be undone!!!" = button_to "Delete", "/admin/ontologies/#{@ontology.acronym}/submissions/#{sub.submissionId}?turbo_stream=true", method: :delete, class:'btn btn-sm btn-link', form: {data: { turbo: true, turbo_confirm: alert_text, turbo_frame: '_top'}} - if @submissions.length > 5 - %tr - %td{colspan: more_colspan, class: "show_more_subs"} + - t.row(class_css: "show_more_subs") do |r| + - r.td(colspan: more_colspan) do %a#version_toggle{:href => ""} more... :javascript diff --git a/app/views/submissions/edit.html.haml b/app/views/submissions/edit.html.haml index 172a4f761..3bd739f9c 100644 --- a/app/views/submissions/edit.html.haml +++ b/app/views/submissions/edit.html.haml @@ -1,6 +1,58 @@ -%div.container.py-4.py-md-5 - %h3.text-center.mb-4 - Edit submission information - %small.text-muted for #{@submission.ontology.acronym} - = form_for :submission, url: ontology_submission_path(@ontology.acronym, @submission.submissionId), html: { id: "ontology_submission_form", method: :put, multipart: true } do |f| - = render partial: "form", locals: {f: f, button_text: "Save submission"} +- section = params[:section] || 'general' +.center + .edit-ontology-container + = turbo_frame_tag(params[:container_id]) do + = form_for :submission, url: ontology_submission_path(@ontology.acronym, params["id"]), html: { id: "ontology_submission_form", method: :put, multipart: true, 'data-turbo': true, 'data-turbo-frame': '_top', novalidate: 'true'} do + .edit-ontology-title + %div Edit ontology + %hr + .edit-ontology-sub-container + - if @selected_attributes.empty? + .edit-ontology-left-column{:role => "tablist",onchange:"onMetadataChange()"} + .edit-ontology-desc + = submission_metadata_selector + %div.nav.nav-pills.flex-column#categories-tabs + - @categories_order.each_with_index do |key, index| + %a.edit-ontology-tab-item.d-block{href: "##{key.parameterize}-tab", "data-toggle" => "pill", class: section.eql?(key.parameterize) ? 'active show' : ''} + = key.humanize + + #myTabContent.edit-ontology-right-column.w-100 + = render TurboFrameComponent.new(id:"metadata_by_ontology") do + = metadata_help_link + %div.tab-content + - if @selected_attributes.empty? + - @categories_order.each_with_index do |key, index| + - properties = @category_attributes[key] + .edit-ontology-tab.tab-pane.fade{id: key.parameterize+'-tab', class: section.eql?(key.parameterize) ? 'active show' : ''} + = render TurboFrameComponent.new(id: "ontology-content-#{index}", loading:"lazy", src: "edit_properties?properties=#{properties.join(',')}&container_id=ontology-content-#{index}") + - else + - link = ontology_submission_edit_properties_path(@ontology.acronym, params[:id], properties: @selected_attributes.join(','), container_id: 'ontology-content-0') + = render TurboFrameComponent.new(id: "ontology-content-0", loading:"lazy", src: link) + + %hr#edit-ontology-actions-devider + .edit-ontology-actions + - unless params[:container_id] + .cancel-button.mx-2{onClick: 'window.history.back();'} + = form_cancel_button + .save-button + = form_save_button + + + :javascript + function onMetadataChange(){ + document.querySelector('.edit-ontology-tab-item.d-block.active.show')?.classList.remove('active', 'show') + document.querySelector('.edit-ontology-tab.tab-pane.fade.active.show')?.classList.remove('active', 'show') + document.getElementById('categories-tabs')?.classList.add('disabled') + + let frame = document.getElementById('metadata_by_ontology') + let properties = document.getElementById('select_search_metadata') + + let selectedProperties = "all" + if(properties && properties.selectedOptions.length > 0){ + selectedProperties = Array.from(properties.selectedOptions).map(({ value }) => value).join(',') + frame.src = "./edit_properties?properties=" + selectedProperties + } else { + Turbo.visit(location.href) + } + + } diff --git a/app/views/submissions/new.html.haml b/app/views/submissions/new.html.haml deleted file mode 100644 index 21d83e669..000000000 --- a/app/views/submissions/new.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- @title = "Add new ontology submission" - -%div.container.py-4.py-md-5 - %h3.text-center.mb-4 - Add new submission - - if !(@submission.ontology.nil? || (@submission.ontology.is_a? String)) - %small.text-muted for #{@submission.ontology.acronym} - = form_for :submission, url: {action: "create"}, html: {id: "ontology_submission_form", multipart: true} do |f| - = render partial: "form", locals: {f: f, button_text: "Add submission"} diff --git a/app/views/users/_form.html.haml b/app/views/users/_form.html.haml index e70512e62..3dc7daa20 100644 --- a/app/views/users/_form.html.haml +++ b/app/views/users/_form.html.haml @@ -5,50 +5,53 @@ %img.lost-password-arrowback{:src => "#{asset_path("arrow-back.svg")}"} .register-title-container %h2.register-title - Create new account + = t('register.create_account') %hr#register-title-line/ %div .register-double-input .register-first-input %p.register-input-title - First name + = t('register.first_name') %font{:color => "red"} * - = text_field :user, :firstName, value: @user.firstname, class: "register-input-short" + = text_field :user, :firstName, value: @user.firstName, class: "register-input-short" %div %p.register-input-title - Last name + = t('register.last_name') %font{:color => "red"} * - = text_field :user, :lastName, value: @user.lastname, class: "register-input-short" + = text_field :user, :lastName, value: @user.lastName, class: "register-input-short" %p.register-input-title - Username + = t('register.username') %font{:color => "red"} * = text_field :user, :username, value: @user.username, class: "register-input-long" %p.register-input-title ORCID ID - %font.register-optional (optional) + %font.register-optional + = t('register.optional') %img.register-input-icon{:src => "#{asset_path("orcid.svg")}"}/ = text_field :user, :orcidId, value: @user.orcidId, class: "register-input-long register-input-with-icon" %p.register-input-title Github ID - %font.register-optional (optional) + %font.register-optional + = t('register.optional') %img.register-input-icon{:src => "#{asset_path("github-icon.svg")}"}/ = text_field :user, :githubId, value: @user.githubId, class: "register-input-long register-input-with-icon" %p.register-input-title - Email + = t('register.email') %font{:color => "red"} * = text_field :user, :email, value: @user.email, class: "register-input-long" %p.register-input-title - Password + = t('register.password') %font{:color => "red"} * = password_field :user, :password, class: "register-input-long" %p.register-input-title - Confirm password + = t('register.confirm_password') %font{:color => "red"} * = password_field :user, :password_confirmation, class: "register-input-long" - if using_captcha? = recaptcha_tags .d-flex %input#user_register_mail_list{:checked => "checked", :name => "user[register_mail_list]", :type => "checkbox", :value => "1"}/ - %p#register-check-text Register for the AgroPortal's mailing list + %p#register-check-text + = t('register.mailing_list') .register-button-container - = render Buttons::PrimaryButtonComponent.new(type: "submit", value: "Register") + = render Buttons::RegularButtonComponent.new(id: 'register-button', value: "Register", type:'submit') diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index da8327ff0..b4307dc1a 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -125,7 +125,7 @@ %font{:color => "red"} * = password_field :user, :password_confirmation, class: "register-input-long" %br - %input.register-button{:type => "submit", :value => "Save", :name => "Update"}/ + = render Buttons::RegularButtonComponent.new(id: 'update-button', value: "Save", type:'submit') - unless params[:password].eql?("true") .change-password %a{:href => edit_user_path(@user.username, password: true)} diff --git a/app/views/users/new.html.haml b/app/views/users/new.html.haml index 95b41374e..e6d6bc8bc 100644 --- a/app/views/users/new.html.haml +++ b/app/views/users/new.html.haml @@ -1,9 +1,9 @@ -- @title = "Register" +- @title = t('register.title') = form_for(:user, :url => users_path) do |f| - unless @errors.nil? .enable-lists{:style => "color: red; padding: 1em;"} - Errors creating your account: + = t('register.account_errors') %ul - for error in @errors - %li= error + %li= error.is_a?(Array) ? error.last : error = render :partial => 'form', :locals => {:f => f} diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index bf3657498..f47328998 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -6,7 +6,7 @@ .account-page-first-row .account-page-card .account-page-personal-informations-title-bar - %h4.account-page-card-title Personal information + %h4.account-page-card-title Personal Information %a.account-page-rounded-button{:href => edit_user_path(url_encode(@user.username))} = render partial: "shared/svgs/edit_icon" .account-page-info-column @@ -18,7 +18,7 @@ %p.info = @user.lastName .account-page-info-column - %p.title Email address: + %p.title Email: %p.info = @user.email .account-page-info-column @@ -30,14 +30,14 @@ %p.info =@user.orcidId .account-page-info-column - %p.title Github ID: + %p.title GitHub ID: %p.info =@user.githubId .account-page-card .account-page-card-container .account-page-card-sub-container - %h4.account-page-card-title Mailing list subscription - %p.account-page-card-desc AgroPortal announcements email list + %h4.account-page-card-title Mailing List Subscription + %p.account-page-card-desc="Register to the #{portal_name} announcements mailing list." - subscribed = false - if subscribed %a.account-page-subscribe-button{:href =>"mailto:#{$ANNOUNCE_SERVICE_HOST}?Subject=unsubscribe%20#{$ANNOUNCE_LIST}"} @@ -49,30 +49,31 @@ .account-page-card-container .account-page-card-sub-container %h4.account-page-card-title API Key - %p.account-page-card-desc Your API Key can be used to access the NCBO API (REST) Services + %p.account-page-card-desc + = "Your API Key can be used to access #{portal_name} Web services" %p.apikey = session[:user].apikey %a{:href => "javascript:navigator.clipboard.writeText('"+session[:user].apikey+"');"} = render partial: "shared/svgs/copy_icon" .account-page-api-documentation-link - %a{href: "https://data.bioportal.lirmm.fr/documentation", target: "_blank"} + %a{href: "#{$REST_URL}/documentation", target: "_blank"} API documentation = render partial: "shared/svgs/external_link_icon" .account-page-card %h4.account-page-card-title - Custom Ontology Set + Custom Semantic Resource Set #custom_ontologies.enable-lists - if at_slice? %p{style: "padding-left: 7px; font-size: 10pt; margin: -3px 0 7px;"} Please %a{href: "#{$UI_URL}/account"} visit the main site - to modify your Custom Ontology Set. + to modify your Custom Semantic Resource Set. - else %p.account-page-card-desc - Customize your AgroPortal display: Pick the ontologies that you want to see on AgroPortal will hide all other ontologies. + Customize your #{portal_name} display: Pick the semantic resources that you want to see on #{portal_name} will hide all other semantic resources. %p %span{style: "font-weight: normal; font-size: 9pt; padding-left: 7px;"} - %a#edit_custom_ontologies{href: "javascript:void(0);"} select ontologies + %a#edit_custom_ontologies{href: "javascript:void(0);"} Select semantic resources - if @user_ontologies && !@user_ontologies.empty? %ul - @user_ontologies.each do |ont| @@ -80,18 +81,20 @@ %li #{ont.name} (#{ont.acronym}) - else - %p{style: "padding-left: 7px;"} You haven't picked any ontologies yet - %p.account-page-card-desc Note: This feature works only when you are logged in. + %p{style: "padding-left: 7px;"} You haven't picked any semantic resources yet + %p.account-page-card-desc Note: this feature works only when you are logged in. #custom_ontologies_picker{style: "left: -9999px; position: absolute;"} = form_tag custom_ontologies_path(url_encode(@user.username)) do - selected = @user.customOntology.map {|o| LinkedData::Client::Models::Ontology.get(o).acronym} - - locals = { custom_ontologies: @all_ontologies, selected_ontologies: selected, sel_text: "Select Custom Ontologies" } + - locals = { custom_ontologies: @all_ontologies, selected_ontologies: selected, sel_text: "Select Custom Semantic Resources" } = render partial: "shared/ontology_picker", locals: locals - = submit_tag "Save Custom Ontologies", class: "link_button" + = submit_tag "Save Custom Semantic Resources", class: "link_button" .account-page-second-row - - unless @user.subscription.nil? || @user.subscription.empty? - .account-page-card - %h4.account-page-card-title Subscriptions + .account-page-card + %h4.account-page-card-title Subscriptions + - if @user.subscription.nil? || @user.subscription.empty? + Not subscribed to any semantic resource + - else - @user.subscription.each do |subscription| - ont_id = subscription[:ontology] - ont = (!subscription[:ontology].nil? ? subscription[:ontology].split('/').last: nil) # ensure we get the acronym @@ -104,29 +107,33 @@ - else = type = subscribe_button(ont_id) - - no_ontologies = true - - unless @admin_ontologies.nil? || @admin_ontologies.empty? - - no_ontologies = false - .account-page-card - %h4.account-page-card-title Submitted ontologies - .account-page-small-cards-container + .account-page-card + %h4.account-page-card-title Submitted Semantic Resources + .account-page-small-cards-container + - if @admin_ontologies.nil? || @admin_ontologies.empty? + .account-page-no-ontology.w-100 + %img{:src => "#{asset_path("empty-box.svg")}"}/ + %p You didn't upload any semantic resource yet + %a.account-page-upload-ontology-button{href: "/ontologies/new"} Upload semantic resource + - else - @admin_ontologies.each do |ont| .account-page-submitted-ontology{data: {controller: 'tooltip'}, title: ont.name} %a{href: "/ontologies/#{ont.acronym}"}= ont.acronym - - unless @user_projects.nil? || @user_projects.empty? - - no_ontologies = false - .account-page-card - %h4.account-page-card-title Projects Created - .account-page-small-cards-container + - unless ont.views.nil? || ont.views.empty? + - ont.views.each do |view| + .account-page-submitted-ontology{data: {controller: 'tooltip'}, title: ont.name} + %a{href: "/ontologies/#{view.match(/\/([^\/]+)$/)[1]}"}= view.match(/\/([^\/]+)$/)[1] + + .account-page-card + %h4.account-page-card-title Projects Created + .account-page-small-cards-container + - if @user_projects.nil? || @user_projects.empty? + No project created + - else - @user_projects.each do |project| .account-page-submitted-ontology %a{href: "/projects/#{project.acronym}"}= project.name - - if no_ontologies - .account-page-card - .account-page-no-ontology - %img{:src => "#{asset_path("empty-box.svg")}"}/ - %p You didn't upload any ontology yet - %a.account-page-upload-ontology-button{href: "/ontologies/new"} Upload ontology + :javascript jQuery(document).ready(function(){ jQuery("#edit_custom_ontologies").click(editCustomOntologies); diff --git a/bin/dev b/bin/dev index a1104a50b..a4e05fa14 100755 --- a/bin/dev +++ b/bin/dev @@ -1,9 +1,11 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh -if ! command -v foreman &> /dev/null -then +if ! gem list foreman -i --silent; then echo "Installing foreman..." gem install foreman fi -foreman start -f Procfile.dev "$@" +# Default to port 3000 if not specified +export PORT="${PORT:-3000}" + +exec foreman start -f Procfile.dev "$@" diff --git a/bin/ontoportal b/bin/ontoportal new file mode 100755 index 000000000..792974b88 --- /dev/null +++ b/bin/ontoportal @@ -0,0 +1,189 @@ +#!/usr/bin/env bash + +# Function to display script usage information +show_help() { + echo "Usage: $0 {dev|test|run|help} [--reset-cache] [--api-url API_URL] [--api-key API_KEY]" + echo " dev : Start the Ontoportal Web UI development server." + echo " Example: $0 dev --api-url http://localhost:9393 --api-key my_api_key" + echo " Use --reset-cache to remove volumes: $0 dev --reset-cache" + echo " test : Run tests. Specify either a test file:line_number or empty for 'all'." + echo " Example: $0 test test/integration/login_flows_test.rb:22 " + echo " run : Run a command in the Ontoportal Web UI Docker container." + echo " help : Show this help message." + echo + echo "Description:" + echo " This script provides convenient commands for managing an Ontoportal Web UI" + echo " application using Docker Compose. It includes options for starting the development server," + echo " running tests, and executing commands within the Ontoportal Web UI Docker container." + echo + echo "Goals:" + echo " - Simplify common tasks related to Ontoportal Web UI development using Docker." + echo " - Provide a consistent and easy-to-use interface for common actions." +} +# Function to update or create the .env file with API_URL and API_KEY +update_env_file() { + local api_url="$1" + local api_key="$2" + + # Update the .env file with the provided values + file_content=$(<.env) + + # Make changes to the variable + while IFS= read -r line; do + if [[ "$line" == "API_URL="* ]]; then + echo "API_URL=$api_url" + elif [[ "$line" == "API_KEY="* ]]; then + echo "API_KEY=$api_key" + else + echo "$line" + fi + done <<< "$file_content" > .env +} + +# Function to create configuration files if they don't exist +create_config_files() { + if [ ! -f ".env" ]; then + echo "Creating .env file from env.sample" + cp .env.sample .env + fi + + if [ ! -f "config/bioportal_config_development.rb" ]; then + echo "Creating config/bioportal_config_development.rb file from config/bioportal_config_env.rb.sample" + cp config/bioportal_config_env.rb.sample config/bioportal_config_development.rb + fi + + if [ ! -f "config/database.yml" ]; then + echo "Creating config/database.yml file from config/database.yml.sample" + cp config/database.yml.sample config/database.yml + fi +} + +# Function to handle the "dev" option +dev() { + echo "Starting Ontoportal Web UI development server..." + + + local reset_cache=false + local api_url="" + local api_key="" + + # Check for command line arguments + while [[ "$#" -gt 0 ]]; do + case $1 in + --reset-cache) + reset_cache=true + shift + ;; + --api-url) + api_url="$2" + shift 2 + ;; + --api-key) + api_key="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + show_help + exit 1 + ;; + esac + done + + + + # Check if arguments are provided + if [ -n "$api_url" ] && [ -n "$api_key" ]; then + # If arguments are provided, update the .env file + update_env_file "$api_url" "$api_key" + else + # If no arguments, fetch values from the .env file + source .env + api_url="$API_URL" + api_key="$API_KEY" + fi + + if [ -z "$api_url" ] || [ -z "$api_key" ]; then + echo "Error: Missing required arguments. Please provide both --api-url and --api-key or update them in your .env" + exit 1 + fi + + # Check if --reset-cache is present and execute docker compose down --volumes + if [ "$reset_cache" = true ]; then + echo "Resetting cache. Running: docker compose down --volumes" + docker compose down --volumes + fi + + echo "Run: bundle exec rails s -b 0.0.0.0 -p 3000" + docker compose run --rm -it --service-ports rails bash -c "(bundle check || bundle install) && bin/rails db:prepare && bundle exec rails s -b 0.0.0.0 -p 3000" +} + +# Function to handle the "test" option +test() { + + + local api_url="" + local api_key="" + local test_options="" + + # Check for command line arguments + while [ "$#" -gt 0 ]; do + case "$1" in + --api-url) + shift + api_url="$1" + ;; + *) + + if [ -z "$test_options" ]; then + test_options="$1" + else + + test_options="$test_options $1" + fi + ;; + esac + shift + done + + if [ -z "$api_url" ]; then + api_url=http://localhost:9393 + echo "Running API..." + bin/run_api + fi + + echo "Running tests..." + echo "Run: API_URL=$api_url bundle exec rails test -v $test_options" + + docker compose run --rm -it test bash -c "(bundle check || bundle install) && RAILS_ENV=test bin/rails db:prepare && API_URL=$api_url bundle exec rails test -v $test_options" + + # echo "Stopping API..." + # bin/stop_api +} + +# Function to handle the "run" option +run() { + echo "Run: $*" + docker compose run --rm -it rails bash -c "$*" +} + +create_config_files +# Main script logic +case "$1" in + "run") + run "${@:2}" + ;; + "dev") + dev "${@:2}" + ;; + "test") + test "${@:2}" + ;; + "help") + show_help + ;; + *) + show_help + exit 1 + ;; +esac diff --git a/bin/run_api b/bin/run_api new file mode 100755 index 000000000..c44c794ba --- /dev/null +++ b/bin/run_api @@ -0,0 +1,69 @@ +#!/bin/bash +display_help() { + echo "Usage: $0 [options]" + echo "Options:" + echo " -f Remove tmp/ontoportal_docker" + echo " -k API_KEY API key" + echo " -r REPOSITORY Image repository" + echo " -t TAG Image tag" + echo " -s ONTOLOGY Starter ontology" + echo " -u URL Remote API URL" + echo " -h Display this help message" + exit 0 +} + + +env_file_path="$(realpath "$(dirname "$0")")/../.env" + +# Read and parse the .env file +if [ -f "$env_file_path" ]; then + while IFS='=' read -r key value; do + [[ "$key" =~ ^\s*# ]] && continue # Skip commented lines + [[ "$value" ]] || continue # Skip lines without values + export "$key"="$value" + done < "$env_file_path" +fi + + +while getopts ":k:r:t:s:u:fh" opt; do + case $opt in + k) api_key="$OPTARG" ;; + r) image_repository="$OPTARG" ;; + t) image_tag="$OPTARG" ;; + s) starter_ontology="$OPTARG" ;; + u) remote_api_url="$OPTARG" ;; + f) rm -fr tmp/ontoportal_docker ;; + h) display_help ;; + \?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;; + :) echo "Option -$OPTARG requires an argument." >&2; exit 1 ;; + esac +done + + + +if [ ! -d "tmp/ontoportal_docker" ]; then + git clone --depth=1 https://github.com/syphax-bouazzouni/ontoportal_docker.git tmp/ontoportal_docker +fi + +# If an option is not provided, check and use the corresponding value from the environment variables +api_key="${api_key:-$OP_API_KEY}" +image_repository="${image_repository:-$API_IMAGE_REPOSITORY}" +image_tag="${image_tag:-$API_IMAGE_TAG}" +starter_ontology="${starter_ontology:-$STARTER_ONTOLOGY}" +remote_api_url="${remote_api_url:-$OP_API_URL}" + +if curl -sSf http://localhost:9393 > /dev/null 2>&1; then + echo "API is already running in http://localhost:9393" + exit 0 +fi + +# Check if the Docker Compose service exists +docker compose ls -a --filter "name=ontoportal_docker" | grep -q "ontoportal_docker" && no_provision=true || no_provision=false +( + cd tmp/ontoportal_docker || exit 1 + command="./run -k $api_key -r $image_repository -t $image_tag -s $starter_ontology -u $remote_api_url" + [ "$no_provision" = true ] && command="$command --no-provision" + echo "Run: $command" + eval "$command" +) + diff --git a/bin/stop_api b/bin/stop_api new file mode 100755 index 000000000..31308b172 --- /dev/null +++ b/bin/stop_api @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# Check if the directory exists +if [ -d "tmp/ontoportal_docker" ]; then + # Check if the container is running + if docker ps --filter "name=ontoportal_docker-api" --format "{{.Names}}" | grep -q "ontoportal_docker-api"; then + container_names=$(docker ps --filter "name=ontoportal_docker-api" --format "{{.Names}}") + docker stop "$container_names" + fi + + cd "tmp/ontoportal_docker" && docker compose down --volumes +fi + diff --git a/config/application.rb b/config/application.rb index ea51961b9..1213596fa 100644 --- a/config/application.rb +++ b/config/application.rb @@ -11,6 +11,11 @@ class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.0 + # permitted locales available for the application + config.i18n.available_locales = [:en, :fr, :it, :de] + config.i18n.default_locale = :en + + # Configuration for the application, engines, and railties goes here. # # These settings can be overridden in specific environments using the files diff --git a/config/bioportal_config_env.rb.sample b/config/bioportal_config_env.rb.sample index 4c83979d0..4cc57816b 100644 --- a/config/bioportal_config_env.rb.sample +++ b/config/bioportal_config_env.rb.sample @@ -1,138 +1,110 @@ -# coding: utf-8 - -#local IP address lookup. This doesn't make connection to external hosts -require 'socket' -def local_ip - orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily - - UDPSocket.open do |s| - s.connect '8.8.8.8', 1 #google - s.addr.last - end -ensure - Socket.do_not_reverse_lookup = orig -end - -$LOCAL_IP = local_ip - # Organization info -$ORG = "NCBO" -$ORG_URL = "http://www.bioontology.org" - +$ORG = ENV['ORG'] +$ORG_URL = ENV['ORG_URL'] # Site name (required) -$SITE = "BioPortal" -$SITE_URL = "localhost" - +$SITE = ENV['SITE'] # Full string for site, EX: "NCBO BioPortal", do not modify -$ORG_SITE = ($ORG.nil? || $ORG.empty?) ? $SITE : "#{$ORG} #{$SITE}" - +$ORG_SITE = $ORG.nil? || $ORG.empty? ? $SITE : "#{$ORG} #{$SITE}" # The URL for the BioPortal Rails UI (this application) -$UI_URL = "http://localhost:3000" - - +$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" - +$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 = "" - +$API_KEY = ENV['API_KEY'] # BioPortal API service address -$REST_URL = "http://example.org:8080/" - +$REST_URL = ENV['API_URL'] # Annotator REST service address -#$ANNOTATOR_URL = "http://services.stageportal.lirmm.fr/annotator" -$ANNOTATOR_URL = "#{$REST_URL}/annotator" - +# $ANNOTATOR_URL = "http://services.stageportal.lirmm.fr/annotator" +$ANNOTATOR_URL = $PROXY_URL = ENV['ANNOTATOR_URL'] +# NCBO annotator URL and apikey +$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" - +$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 = {} +# OAuth2 authentication +$OMNIAUTH_PROVIDERS = { + github: { + client_id: 'CLIENT_ID', + client_secret: 'CLIENT_SECRET', + icon: 'github.svg', + enable: true + }, + google: { + strategy: :google_oauth2, + client_id: 'CLIENT_ID', + client_secret: 'CLIENT_SECRET', + icon: 'google.svg', + enable: true + }, + orcid: { + client_id: 'CLIENT_SECRET', + client_secret: 'CLIENT_SECRET', + icon: 'orcid.svg', + enable: false + }, + keycloak: { + strategy: :keycloak_openid, + client_id: 'YOUR_KEYCLOAK_CLIENT_ID', + client_secret: 'YOUR_KEYCLOAK_CLIENT_SECRET', + client_options: { site: 'KEYCLOAK_SITE', realm: 'KEYCLOAK_REALM' }, + name: 'keycloak', + icon: 'keycloak.svg', + 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 = ["MDRFRE", "WHO-ARTFRE", "MSHFRE", "CIM-10"] - +$RESTRICTED_DOWNLOADS = [] # Ontolobridge endpoint url -$ONTOLOBRIDGE_BASE_URL = "https://ontolobridge.ccs.miami.edu/api-test/requests" - +$ONTOLOBRIDGE_BASE_URL = 'https://ontolobridge.ccs.miami.edu/api-test/requests' # Ontolobridge authentication token -$ONTOLOBRIDGE_AUTHENTICATION_TOKEN = "Token Uq2pae73ktMtmgjUgtnhEOuHxr9sZeuK" - +$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" - -# Max number of children to return when rendering a tree view -$MAX_CHILDREN = 2500 - -# Max number of children that it's possible to display (more than this is either too slow or not helpful to users) -$MAX_POSSIBLE_DISPLAY = 10000 - +$LEGACY_REST_URL = 'http://example.org:8080/bioportal' # Release version text (appears in footer of all pages, except 404 and 500 errors) -$RELEASE_VERSION = "4.24 (February 2016)" - +$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 = "" - -# A user id for user 'anonymous' for use when a user is required for an action on the REST service but you don't want to require a user to login -$ANONYMOUS_USER = 0 - -# Redis server to use for NCBO::Resolver -$REDIS_HOST = "" -$REDIS_PORT = 6379 - -# Cube metrics reporting -$ENABLE_CUBE = false -$CUBE_HOST = "localhost" -$CUBE_PORT = 1180 - +$ANALYTICS_ID = ENV['ANALYTICS_ID'] # Enable client request caching -$CLIENT_REQUEST_CACHING = false - -# If you don't use Airbrake you can have exceptions emailed to the $ERROR_EMAIL address by setting this to 'true' -$EMAIL_EXCEPTIONS = false - +$CLIENT_REQUEST_CACHING = true # Email settings ActionMailer::Base.smtp_settings = { - :address => "", # smtp server address, ex: smtp.example.org - :port => 25, # smtp server port - :domain => "", # fqdn of rails server, ex: rails.example.org + address: '', # smtp server address, ex: smtp.example.org + port: 25, # smtp server port + domain: '' # fqdn of rails server, ex: rails.example.org } - # Announcements mailman mailing list REQUEST address, EX: list-request@lists.example.org # NOTE: You must use the REQUEST address for the mailing list. ONLY WORKS WITH MAILMAN LISTS. -$ANNOUNCE_LIST = "sifrportal-users@lirmm.fr" - +$ANNOUNCE_LIST = ENV['SUPPORT_EMAIL'] # Email addresses used for sending notifications (errors, feedback, support) -$SUPPORT_EMAIL = "sifrportal-support@lirmm.fr" - +$SUPPORT_EMAIL = ENV['SUPPORT_EMAIL'] # Email used to send notifications -$NOTIFICATION_EMAIL = "notifications@bioportal.lirmm.fr" - +$NOTIFICATION_EMAIL = ENV['SUPPORT_EMAIL'] # 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 @@ -144,33 +116,12 @@ $NOTIFICATION_EMAIL = "notifications@bioportal.lirmm.fr" # # 3. Set the USE_RECAPTCHA option to 'true' ENV['USE_RECAPTCHA'] = 'false' - # Custom BioPortal logging require 'log' -$REMOTE_LOGGING = false - -## -# Flex Options -## - -# URL to pull Flex apps from -$FLEX_URL = "/flex" - -# Flex App API keys -$ANNOTATOR_FLEX_APIKEY = "" -$SEARCH_FLEX_APIKEY = "" -$RECOMMENDER_FLEX_APIKEY = "" -$FLEXOVIZ_APIKEY = "" - # URL where BioMixer GWT app is located -#$BIOMIXER_URL = "http://bioportal-integration.bio-mixer.appspot.com" -$BIOMIXER_URL = "http://#{$SITE_URL}/BioMixer" - -# Array with all the additional ontology metadata to display in the ontology details -$ADDITIONAL_ONTOLOGY_METADATA = ["naturalLanguage", "hasLicense", "notes", "modificationDate", "URI", "hasContributor", "hasCreator", "endorsedBy", "hasDomain", - "keyClasses", "keywords", "isOfType", "knowUsage", "designedForOntologyTask", "hasFormalityLevel", "usedImports", "usedKnowledgeRepresentationParadigm", - "usedOntologyEngineeringMethodology", "usedOntologyEngineeringTool"] - +# $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 @@ -179,43 +130,93 @@ $ADDITIONAL_ONTOLOGY_METADATA = ["naturalLanguage", "hasLicense", "notes", "modi # $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 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 = { } - -# Used in browse for facetted search to avoid having to call the API (for performance) -$FORMALITY_LEVELS = { - "http://w3id.org/nkos/nkostype#classification_schema" => "Classification scheme", - "http://w3id.org/nkos/nkostype#dictionary" => "Dictionary", - "http://w3id.org/nkos/nkostype#gazetteer" => "Gazetteer", - "http://w3id.org/nkos/nkostype#glossary" => "Glossary", - "http://w3id.org/nkos/nkostype#list" => "List", - "http://w3id.org/nkos/nkostype#name_authority_list" => "Name authority list", - "http://w3id.org/nkos/nkostype#ontology" => "Ontology", - "http://w3id.org/nkos/nkostype#semantic_network" => "Semantic network", - "http://w3id.org/nkos/nkostype#subject_heading_scheme" => "Subject heading scheme", - "http://w3id.org/nkos/nkostype#synonym_ring" => "Synonym ring", - "http://w3id.org/nkos/nkostype#taxonomy" => "Taxonomy", - "http://w3id.org/nkos/nkostype#terminology" => "Terminology", - "http://w3id.org/nkos/nkostype#thesaurus" => "Thesaurus" -} -$IS_OF_TYPE = { - "http://omv.ontoware.org/2005/05/ontology#ApplicationOntology" => "Application Ontology", - "http://omv.ontoware.org/2005/05/ontology#CoreOntology" => "Core Ontology", - "http://omv.ontoware.org/2005/05/ontology#DomainOntology" => "Domain Ontology", - "http://omv.ontoware.org/2005/05/ontology#TaskOntology" => "Task Ontology", - "http://omv.ontoware.org/2005/05/ontology#UpperLevelOntology" => "Upper Level Ontology", - "http://omv.ontoware.org/2005/05/ontology#Vocabulary" => "Vocabulary" +$SITE_NOTICE = {} +$HOME_PAGE_LOGOS = { + supported_by: [ + { + img_src: 'logos/supports/numev.png', + url: 'http://www.lirmm.fr/numev', + target: '_blank' + }, + { + img_src: 'logos/supports/anr.png', + url: 'https://anr.fr/en', + target: '_blank' + }, + { + img_src: 'logos/supports/eu.png', + url: 'https://commission.europa.eu/research-and-innovation_en', + target: '_blank' + } + ], + with_the_collaboration_of: [ + { + img_src: 'logos/collaboration/d2kab.png', + url: 'http://d2kab.mystrikingly.com', + target: '_blank' + }, + { + img_src: 'logos/collaboration/lirmm.png', + url: 'http://www.lirmm.fr', + target: '_blank' + }, + { + img_src: 'logos/collaboration/inrae.png', + url: 'https://www.inrae.fr/enm', + target: '_blank' + }, + { + img_src: 'logos/collaboration/stanford.png', + url: 'https://www.stanford.edu', + target: '_blank' + } + ] } -$NATURAL_LANGUAGES = { - "http://lexvo.org/id/iso639-3/eng" => "English", - "http://lexvo.org/id/iso639-3/fra" => "French", - "http://lexvo.org/id/iso639-3/spa" => "Spanish", - "http://lexvo.org/id/iso639-3/por" => "Portuguese", - "http://lexvo.org/id/iso639-3/ita" => "Italian", - "http://lexvo.org/id/iso639-3/deu" => "German" + +$FOOTER_LINKS = { + social: [ + { logo: 'social/people.svg', link: 'https://github.com/orgs/agroportal/people' }, + { logo: 'social/github.svg', link: 'https://github.com/agroportal' }, + { logo: 'social/twitter.svg', link: 'https://twitter.com/lagroportal' } + ], + sections: { + products: { + ontoportal: 'https://ontoportal.org/', + release_notes: 'https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/release-notes-btKjZk5tU2', + api: 'https://data.agroportal.lirmm.fr/', + sparql: 'https://sparql.agroportal.lirmm.fr/test/' + }, + support: { + contact_us: 'https://agroportal.lirmm.fr/feedback', + wiki: 'https://www.bioontology.org/wiki/', + documentation: 'https://ontoportal.github.io/documentation/' + }, + agreements: { + terms: '', + privacy_policy: '', + cite_us: '', + acknowledgments: '' + }, + about: { + about_us: 'https://github.com/agroportal/project-management', + projects: 'https://d2kab.mystrikingly.com/', + team: 'https://github.com/orgs/agroportal/people' + } + } } + +$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) + if File.exist?("config/bioportal_config_development_#{$HOSTNAME}") + require_relative "bioportal_config_development_#{$HOSTNAME}" + end +end diff --git a/config/bioportal_config_test.rb b/config/bioportal_config_test.rb new file mode 100644 index 000000000..81aad6510 --- /dev/null +++ b/config/bioportal_config_test.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +$SITE = 'Testportal' +$HOSTNAME = 'testportal' +$UI_HOSTNAME = 'localhost' +$UI_URL = "http://#{$UI_HOSTNAME}:3000" + +$API_KEY = ENV['API_KEY'] +$REST_URL = ENV['API_URL'] +$BIOMIXER_URL = ENV['BIOMIXER_URL'] +$ANNOTATOR_URL = $PROXY_URL = ENV['ANNOTATOR_URL'] +$FAIRNESS_URL = ENV['FAIRNESS_URL'] + +# config/initializers/omniauth_providers.rb +$OMNIAUTH_PROVIDERS = { + github: { + client_id: 'CLIENT_ID', + client_secret: 'CLIENT_SECRET', + icon: 'github.svg', + enable: true + }, + google: { + strategy: :google_oauth2, + client_id: 'CLIENT_ID', + client_secret: 'CLIENT_SECRET', + icon: 'google.svg', + enable: true + }, + orcid: { + client_id: 'CLIENT_SECRET', + client_secret: 'CLIENT_SECRET', + icon: 'orcid.svg', + enable: false + }, + keycloak: { + strategy: :keycloak_openid, + client_id: 'YOUR_KEYCLOAK_CLIENT_ID', + client_secret: 'YOUR_KEYCLOAK_CLIENT_SECRET', + client_options: { site: 'KEYCLOAK_SITE', realm: 'KEYCLOAK_REALM' }, + name: 'keycloak', + icon: 'keycloak.svg', + enable: false + } +}.freeze + +$INTERPORTAL_HASH = {} + +# If your BioPortal installation includes Fairness score set this to true +$FAIRNESS_DISABLED = false + +# Pairing a name with an array of ontology virtual ids will allow you to filter ontologies based on a subdomain. +# 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 +$ONTOLOGY_SLICES = {} + +# Cube metrics reporting +$ENABLE_CUBE = false + +$NOT_DOWNLOADABLE = {} +# Enable client request caching +$CLIENT_REQUEST_CACHING = true + +# If you don't use Airbrake you can have exceptions emailed to the $ERROR_EMAIL address by setting this to 'true' +$EMAIL_EXCEPTIONS = false + +# Announcements mailman mailing list REQUEST address, EX: list-request@lists.example.org +# NOTE: You must use the REQUEST address for the mailing list. ONLY WORKS WITH MAILMAN LISTS. +$ANNOUNCE_LIST ||= 'appliance-users-request@localhost' + +# Email addresses used for sending notifications (errors, feedback, support) +$SUPPORT_EMAIL ||= 'support@localhost' +$ADMIN_EMAIL ||= 'admin@localhost' +$ERROR_EMAIL ||= 'errors@localhost' + +# Custom BioPortal logging +require 'log' +$REMOTE_LOGGING = false + +## +# Custom Ontology Details +# Custom details can be added on a per ontology basis using a key/value pair as columns of the details table +# +# Example: +# $ADDITIONAL_ONTOLOGY_DETAILS = { 1000 => { "Additional Detail" => "Text to be shown in the right-hand column." } } +## +$ADDITIONAL_ONTOLOGY_DETAILS = {} + +# Site notice appears on all pages and remains closed indefinitely. Stored below as a hash with a unique key and a +# EX: $SITE_NOTICE = { :unique_key => 'Put your message here (can include html if you use +$SITE_NOTICE = {} +################################ +## AUTO-GENERATED DO NOT MODIFY +################################# + +# Full string for site, EX: "NCBO BioPortal" +$ORG_SITE = $ORG.nil? || $ORG.empty? ? $SITE : "#{$ORG} #{$SITE}" + +$HOME_PAGE_LOGOS = { + supported_by: [ + { + img_src: 'logos/supports/numev.png', + url: 'http://www.lirmm.fr/numev', + target: '_blank' + }, + { + img_src: 'logos/supports/anr.png', + url: 'https://anr.fr/en', + target: '_blank' + }, + { + img_src: 'logos/supports/eu.png', + url: 'https://commission.europa.eu/research-and-innovation_en', + target: '_blank' + } + ], + with_the_collaboration_of: [ + { + img_src: 'logos/collaboration/d2kab.png', + url: 'http://d2kab.mystrikingly.com', + target: '_blank' + }, + { + img_src: 'logos/collaboration/lirmm.png', + url: 'http://www.lirmm.fr', + target: '_blank' + }, + { + img_src: 'logos/collaboration/inrae.png', + url: 'https://www.inrae.fr/enm', + target: '_blank' + }, + { + img_src: 'logos/collaboration/stanford.png', + url: 'https://www.stanford.edu', + target: '_blank' + } + ] +} + +$FOOTER_LINKS = { + social: [ + { logo: 'social/people.svg', link: 'https://github.com/orgs/agroportal/people' }, + { logo: 'social/github.svg', link: 'https://github.com/agroportal' }, + { logo: 'social/twitter.svg', link: 'https://twitter.com/lagroportal' } + ], + sections: { + products: { + ontoportal: 'https://ontoportal.org/', + release_notes: 'https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/release-notes-btKjZk5tU2', + api: 'https://data.agroportal.lirmm.fr/', + sparql: 'https://sparql.agroportal.lirmm.fr/test/' + }, + support: { + contact_us: 'https://agroportal.lirmm.fr/feedback', + wiki: 'https://www.bioontology.org/wiki/', + documentation: 'https://ontoportal.github.io/documentation/' + }, + agreements: { + terms: '', + privacy_policy: '', + cite_us: '', + acknowledgments: '' + }, + about: { + about_us: 'https://github.com/agroportal/project-management', + projects: 'https://d2kab.mystrikingly.com/', + team: 'https://github.com/orgs/agroportal/people' + } + } +} + +$UI_THEME = :stageportal +if File.exist?('config/bioportal_config_development_testportal.lirmm.fr.rb') + require_relative 'bioportal_config_development_testportal.lirmm.fr' # local credentials +end diff --git a/config/boot.rb b/config/boot.rb index 282011619..6a51f27d2 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,3 +1,4 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) require "bundler/setup" # Set up gems listed in the Gemfile. +require 'bootsnap/setup' \ No newline at end of file diff --git a/config/database.yml.sample b/config/database.yml.sample index c92d72df2..6edf6ed31 100644 --- a/config/database.yml.sample +++ b/config/database.yml.sample @@ -12,10 +12,9 @@ default: &default adapter: mysql2 encoding: utf8mb4 - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root - password: bp_user - host: db + password: root + host: <%= ENV["DB_HOST"] %> development: <<: *default diff --git a/config/environments/development.rb b/config/environments/development.rb index b595b87cd..7e0593176 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -7,7 +7,16 @@ # it changes. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false + config.action_mailer.delivery_method = :letter_opener_web + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_deliveries = true + config.action_mailer.default_url_options = { host: '0.0.0:3000' } # Adjust the host/port as needed + + config.action_mailer_letter_opener_location = Rails.root.join('tmp', 'my_mails') + + # Do not eager load code on boot. config.eager_load = false @@ -16,7 +25,9 @@ # Enable server timing config.server_timing = true - + + # Allow all hosts in development + config.hosts = nil # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. if Rails.root.join("tmp/caching-dev.txt").exist? diff --git a/config/environments/production.rb b/config/environments/production.rb index 3eea9fbc8..3eb3961f0 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -92,8 +92,10 @@ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') # Include the BioPortal-specific configuration options - require Rails.root.join('config', "bioportal_config_#{Rails.env}.rb") - + if File.exist?(Rails.root.join('config', "bioportal_config_#{Rails.env}.rb")) + require Rails.root.join('config', "bioportal_config_#{Rails.env}.rb") + 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 } diff --git a/config/environments/test.rb b/config/environments/test.rb index 4448d0e8f..d35b8d0f7 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -14,18 +16,18 @@ # Eager loading loads your whole application. When running a single test locally, # this probably isn't necessary. It's a good idea to do in a continuous integration # system, or in some way before deploying your code. - config.eager_load = ENV["CI"].present? + config.eager_load = ENV['CI'].present? # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - "Cache-Control" => "public, max-age=#{1.hour.to_i}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false - config.cache_store = :null_store + config.cache_store = ActiveSupport::Cache::MemCacheStore.new('localhost:11211', namespace: 'BioPortal') # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false diff --git a/config/initializers/graphql_client.rb b/config/initializers/graphql_client.rb index 69a909bb9..57760a168 100644 --- a/config/initializers/graphql_client.rb +++ b/config/initializers/graphql_client.rb @@ -6,7 +6,7 @@ module GitHub HTTPAdapter = GraphQL::Client::HTTP.new('https://api.github.com/graphql') do def headers(_context) - { 'Authorization': "Bearer #{Rails.application.credentials[:kgcl][:github_access_token]}" } + { 'Authorization': "Bearer #{Rails.application.credentials.dig(:kgcl, :github_access_token)}" } end end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 000000000..c7c4027c3 --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,5 @@ +Rails.application.config.middleware.use OmniAuth::Builder do + Array($OMNIAUTH_PROVIDERS).each do |provider, config| + provider config[:strategy] || provider, config[:client_id], config[:client_secret], client_options: {}.merge(config[:client_options].to_h) + end +end \ No newline at end of file diff --git a/config/locales/de.yml b/config/locales/de.yml new file mode 100644 index 000000000..4524bf312 --- /dev/null +++ b/config/locales/de.yml @@ -0,0 +1,409 @@ +--- +de: + activaterecord: + errors: + models: + license: + attributes: + encrypted_key: + invalid_license_key: is an invalid license key + no_appliance_id_for_comparison: Could not be validated. Unable to retrieve virtual appliance ID. + appliance_id_mismatch: is an appliance id mismatch + + date: + 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" # March 1, 2017 + + additional_parameters: Zusätzliche Parameter werden erklärt unter + admin: + licenses: + create: + success: Lizenz erfolgreich verlängert! + all: Alle + annotator: + annotate_text_prompt: Zu beschriftenden Text eingeben oder einfügen + annotations_result: Anmerkungen + fast_context: FastContext + filters: + confidence_threshold: Filterkonfidenzschwellenwert + confidence_threshold_help: Geben Sie die Mindestposition in der Punkteverteilung + an (zwischen 1 und 100) + exclude_numbers: Nummern ausschließen + exclude_synonyms: Synonyme ausschließen + include_mappings: Mappings einbeziehen + match_longest_only: Nur das längste Spiel + match_partial_words: Erkennen von Wortteilen + max_hierarchy_level: Vorfahren bis zur Ebene einbeziehen + score: Punktzahl einbeziehen + score_help: Bewertung von Anmerkungen nach der früheren NCBO 2009-Maßnahme (alt) + oder Bewertung von Anmerkungen nach der C-Value-Maßnahme (cvalue) oder Bewertung + von Anmerkungen nach der C-Value-Maßnahme mit Hierarchieerweiterung (cvalueh) + score_threshold: Filter nach Schwellenwert + score_threshold_help: Mindestpunktzahl für Anmerkungen festlegen + get_annotator: Anmerkungen erhalten + index: + fast_context: + tooltip: Aktivieren Sie FastContext, um zu erkennen, ob ein Konzept verneint + wurde (bejaht, verneint), wer das gefundene Konzept erlebt hat (Patient, + andere), wann das kommentierte Konzept aufgetreten ist (kürzlich, historisch, + hypothetisch) und/oder ob das kommentierte Konzept unsicher ist (sicher, + unsicher). + intro: Annotationen für biomedizinische Texte mit Ontologieklassen erhalten + lemmatize: + tooltip: Aktivieren Sie Lemmatize, um eingereichten Text zu lemmatisieren + und ein lemmatisiertes Wörterbuch für Anmerkungen zu verwenden + sample_text: Das Melanom ist ein bösartiger Tumor der Melanozyten, der hauptsächlich + in der Haut, aber auch im Darm und im Auge vorkommt. + lemmatize: Lemmatisieren + results_filtered_by: Die Ergebnisse werden gefiltert nach + select: Wählen Sie %{name} + start_typing_to_select: Beginnen Sie mit der Eingabe, um %{type} auszuwählen oder + lassen Sie es leer, um alle zu verwenden + title: Kommentator + umls: + semantic_groups: UMLS Semantische Gruppen + semantic_types: UMLS-Semantische Typen + certainty: Gewissheit + class: Klasse + clear_selection: Auswahl löschen + concepts: + request_term: + new_term_instructions: | +

    Diese Ontologie ist in OntoloBridge integriert und ermöglicht es den Nutzern der Gemeinschaft, Ergänzungen zur öffentlichen Ontologie vorzuschlagen. Füllen Sie die nachstehende Vorlage aus, um eine Begriffsanfrage direkt an den Ontologiemanager zu senden.

    +
    +

    Begriffsbezeichnung (erforderlich)
    Vorgeschlagener Begriffsname. Wenn ein Begriff mit mehr als einem Synonym beschrieben werden kann, geben Sie hier nur den bevorzugten Namen ein.

    +
    +
    +

    Begriffsbeschreibung (erforderlich)
    Eine kurze Definition, Beschreibung oder Verwendung des vorgeschlagenen Begriffs. Synonyme von weiteren Begriffen können in diesem Abschnitt aufgeführt werden.

    +
    +
    +

    Superclass (erforderlich)
    Der übergeordnete Begriff des vorgeschlagenen Begriffs. Der übergeordnete Begriff muss ein bestehender Eintrag in der aktuellen Ontologie sein. Die Oberklasse kann direkt aus dem Bioportal-Klassenbaum ausgewählt werden.

    +
    +
    +

    Referenzen (optional)
    Bieten Sie Belege für die Existenz des gesuchten Begriffs, wie Pubmed IDs von Artikeln oder Links zu anderen Ressourcen, die den Begriff beschreiben.

    +
    +
    +

    Begründung (optional)
    Geben Sie hier zusätzliche Informationen über den gesuchten Begriff an.

    +
    + context: Kontext + coverage: Erfassungsbereich + filter: Filter + format_results: Ergebnisse formatieren als + get_json_version: Abrufen der json-Version + get_recommendations: Empfehlungen erhalten + help: Hilfe + home: + agroportal_figures: "%{site} in Zahlen:" + benefit1: Entdecken Sie neue Erkenntnisse und Zusammenhänge, indem Sie andere + Ontologien im Repository erforschen. + benefit2: Tragen Sie zum Wachstum und zur Entwicklung Ihres Bereichs bei, indem + Sie neue Konzepte und Kategorien hinzufügen. + benefit3: Verwenden Sie die Versionskontrolle, um die Änderungen an Ihrer Ontologie + im Laufe der Zeit zu verwalten und mit anderen Benutzern zusammenzuarbeiten. + benefit4: Holen Sie sich Feedback und Vorschläge von anderen Benutzern, die Ihre + Ontologie überprüfen und kommentieren können. + benefit5: Holen Sie sich den FAIR Score und die Metriken für Ihre Ontologie. + fair_details: Siehe Details + fairness: FAIR-Punkte + get_annotations: Anmerkungen erhalten + get_recommendations: Empfehlungen erhalten + index: + tagline: Die Heimat von Vokabularen und Ontologien in der Agrarwissenschaft + und verwandten Bereichen. + title: Willkommen bei der %{site} + welcome: Willkommen auf der %{site}, + ontology_upload: Möchten Sie eine Ontologie hochladen? + ontology_upload_benefits: 'Indem Sie Ihre Ontologie auf %{site} hochladen, können + Sie:' + ontology_upload_button: Ontologie hochladen + ontology_upload_desc: Das Hochladen einer Ontologie ist eine Möglichkeit, Ihr + Fachwissen mit anderen zu teilen. + paste_text_prompt: Einfügen eines Textabsatzes oder einiger Schlüsselwörter ... + recommender_annotator: Empfehlungsgeber und Kommentator + supported_by: Unterstützt von + twitter_news: Nachrichten + with_collaboration: In Zusammenarbeit mit der + input: Eingabe + insert_sample_text: Beispieltext einfügen + keywords: Schlüsselwörter + keywords_separated_by_commas: Schlüsselwörter durch Kommas getrennt + knowledge_detail: Wissen Detail + landscape: + average_metrics: Durchschnittliche Metriken + category: Kategorie + filter_network: Netzwerk filtern + funding_endorsing_organizations: Organisationen, die die meisten Ontologien finanzieren + und befürworten + group: Gruppe + groups_and_categories: Gruppen und Kategorien + intro: Visualisierung von Daten, die aus im Portal gespeicherten Ontologien abgerufen + werden + more_properties_charts: Weitere Eigenschaftsdiagramme + most_active_ontologies: Die aktivsten Ontologien + most_active_organizations: Die aktivsten Organisationen + most_active_people: Die aktivsten Menschen + most_active_people_as_reviewer: Die aktivsten Personen als Gutachter + most_mentioned_people: Meistgenannte Personen als Kontaktperson, Ersteller, Mitwirkender + oder Kurator + most_mentioned_people_as_reviewer: Personen, die Notizen, Rezensionen und Projekte + veröffentlicht haben + ontologies_activity_on: Ontologie-Aktivität auf %{site} + ontologies_by: Ontologien nach %{type} + ontologies_contributors: Beiträge zur Ontologieentwicklung + ontologies_count_by_catalog: Anzahl der Ontologien in jedem Datenkatalog + ontologies_formats: Verwendetes Format + ontologies_languages: Natürliche Sprachen der Ontologien + ontologies_licenses: Von Ontologien verwendete Lizenzen + ontologies_with_notes_reviews_projects: Ontologien mit Notizen, Bewertungen und + Projekten + ontology_fairness_evaluator: Ontologie FAIRness Evaluator (O'FAIRe) + ontology_formality_levels: Formalitätsebenen von Ontologien + ontology_properties_pie_charts: Kreisdiagramme für die in Ontologien verwendeten + Eigenschaften + ontology_relations_network: Ontologie-Beziehungsnetz + ontology_tools: Meistgenutzte Tools zur Erstellung von Ontologien + owl_ontology_author_uris: URIs für Autoreneigenschaften, die für OWL-Ontologien + verwendet werden + owl_ontology_definition_uris: URIs für Definitionsmerkmale, die für OWL-Ontologien + verwendet werden + owl_ontology_preflabel_uris: URIs für prefLabel-Eigenschaften, die für OWL-Ontologien + verwendet werden + owl_ontology_synonym_uris: URIs für synonyme Eigenschaften, die für OWL-Ontologien + verwendet werden + properties_usage_proportion: Der Anteil der verwendeten Eigenschaften in den gespeicherten + Ontologien + properties_use: Nutzung der Immobilie + relations_between_stored_ontologies: Beziehungen zwischen gespeicherten Ontologien + im Portal + size: Größe + title: "%{site} Landschaft" + layout: + header: + account_setting: Kontoeinstellungen + annotator: Kommentator + browse: Durchsuchen + documentation: Dokumentation + help: Hilfe + landscape: Landschaft + login: Anmeldung + logout: abmeldung + mappings: Mappings + publications: Veröffentlichungen + release_notes: Anmerkungen zur Veröffentlichung + search_prompt: Suche in %{portal_name} ... + submit_feedback: Feedback senden + support: unterstützung + login: + enter_email: Ihre E-Mail eingeben + enter_password: Geben Sie Ihr Passwort ein + forgot_password: Passwort vergessen? + invalid_login: Fehler im Formular + no_account: Sie haben noch kein Konto? + password: Passwort + register: Register + title: Anmeldung + username_or_email: Benutzername oder E-Mail + mappings: + find_mappings: Zuordnungen einer Klasse/eines Konzepts finden + intro: Mappings zwischen Klassen verschiedener Ontologien durchsuchen + loading_mappings: Mappings laden... + mappings_bulk_load: Mapping Bulk Load + no_mappings_available: Keine Mappings verfügbar + title: Korrespondenzen + upload_mappings: Mappings hochladen + matched_class: Abgestimmte Klasse + matched_ontology: abgestimmte Ontologie + max_ontologies_per_set: Maximale Anzahl von Ontologien pro Satz + nbco_annotatosplus: + annotations: Anmerkungen + enter_paste_text_to_annotate: 'Geben Sie den zu beschriftenden Text ein oder fügen + Sie ihn ein:' + exclude_numbers: Nummern ausschließen + exclude_synonyms: Synonyme ausschließen + fast_context: + help: 'Aktivieren Sie FastContext, um festzustellen: ob ein Konzept verneint + wurde (bejaht, verneint), wer das gefundene Konzept erlebt hat (Patient, andere), + wann das kommentierte Konzept aufgetreten ist (kürzlich, historisch, hypothetisch) + und/oder ob das kommentierte Konzept unsicher ist (sicher, unsicher).' + title: FastContext + filters: + additional_parameters_explained_at: 'Weitere Parameter werden auf der Seite + erläutert:' + by: + certainty: Gewissheit + class: Klasse + experiencer: Erlebnisträger + filter: Filter + match_context: Kontext + match_type: Typ + matched_class: Assoziierte Klasse + matched_ontology: Assoziierte Ontologie + negation: Negation + ontology: Ontologie + score: Ergebnis + temporality: Zeitlichkeit + title: Die Ergebnisse werden gefiltert nach + umls_sem_type: UMLS-Semantischer Typ + confidence_threshold: Vertrauensschwelle filtern + confidence_threshold_help: Geben Sie die Mindestposition in der Punkteverteilung + an (zwischen 1 und 100) + format_results_as: 'Ergebnisse formatieren als:' + reproduce_results_using: Reproduzieren Sie diese Ergebnisse mit der + score_threshold: Filter nach Schwellenwert + score_threshold_help: Geben Sie den Mindestwert für die Bewertung von Anmerkungen + an + include_ancestors_up_to_level: Vorfahren bis zur Ebene einbeziehen + include_mappings: Mappings einbeziehen + include_score: Punktzahl einbeziehen + index: + intro: | + Der NCBO Annotator+ ist ein Proxy, der den NCBO Annotator Web Service auf dem NCBO BioPortal aufruft. +

    + Tchechmedjiev, A., Abdaoui, A., Emonet, V., Melzi, S., Jonnagaddala, J., & Jonquet, C. (2018). Enhanced features for annotating and indexing clinical text with NCBO Annotator+. Bioinformatics, 34(11), 1962-1965. +

    + Wenn Sie die API verwenden, geben Sie bitte einen gültigen NCBO BioPortal API-Schlüssel an und greifen Sie auf den Dienst unter http://services.bioportal.lirmm. de/ncbo_annotatorplus
    zu + Der an NCBO Annotator+ übermittelte Text muss in englischer Sprache verfasst sein. + sample_text: Das Melanom ist ein bösartiger Tumor der Melanozyten, der hauptsächlich + in der Haut, aber auch im Darm und im Auge vorkommt. + title: NCBO Annotator + + insert_sample_text: Beispieltext einfügen + match_longest_only: Nur das längste Spiel + match_partial_words: Teilwörter zuordnen + recognizer: Erkennung von Entitäten + score_help: Bewertung von Anmerkungen nach der früheren NCBO 2009-Maßnahme (alt) + oder Bewertung von Anmerkungen nach der C-Value-Maßnahme (cvalue) oder Bewertung + von Anmerkungen nach der C-Value-Maßnahme mit Hierarchieerweiterung (cvalueh) + select: Wählen Sie %{name} + select_ontologies: Beginnen Sie mit der Eingabe, um Ontologien auszuwählen, oder + lassen Sie das Feld leer, um alle Ontologien zu verwenden + select_ontologies_list: Ontologien auswählen + show_advanced_options: Erweiterte Optionen anzeigen + start_typing_to_select: Beginnen Sie mit der Eingabe, um %{type} auszuwählen oder + lassen Sie es leer, um alle zu verwenden + umls: + semantic_groups: UMLS Semantische Gruppen + semantic_types: UMLS-Semantische Typen + negation: verneinung + none: keine + ontologies: + ontology_search_prompt: 'Suche nach einer Ontologie oder einem Konzept (Beispiel: + Agrovoc ...)' + self: Ontologien + ontology: Ontologie + ontology_details: + concept: + definitions: Definitionen + id: ID + in_schemes: In Schemata + member_of: Mitglied von + no_preferred_name_for_selected_language: Kein bevorzugter Name für die gewählte + Sprache. + obsolete: Veraltet + preferred_name: Bevorzugter Name + synonyms: Synonyme + type: Typ + metadata: + additional_metadata: Zusätzliche Metadaten + header: + last_submission_date: Datum laatste indiening + + ontology_sets: Ontologie-Sets + output: Ausgabe + projects: + contacts: Kontakte + create_new_project: Neues Projekt erstellen + created: Erstellt + creator: Benutzer + delete_admin_only: Löschen (nur für Administratoren) + delete_confirm: Sind Sie sicher? + description: Beschreibung + description_text: Beschreibung Text + edit: Bearbeiten + home_page: Hauptseite + index: + intro: Durchsuchen Sie eine Auswahl von Projekten, die %{site} Ressourcen verwenden + institutions: Einrichtungen + ontologies: Ontologien + project_description: Beschreibung des Projekts + self: Projekte + title: Liste der Projekte + view_projects_help: Projekte anzeigen Hilfe + recommender: + intro: Empfehlungen für die relevantesten Ontologien anhand eines Auszugs aus + einem biomedizinischen Text oder einer Liste von Schlüsselwörtern erhalten + no_recommendations: Keine Empfehlungen gefunden + no_sets_recommended: Es gibt keine empfohlenen Ontologiesätze für die angegebene + Eingabe. Bitte versuchen Sie die Ausgabe 'Ontologien'. + ontology_recommendation_input: Bitte fügen Sie einen Textabschnitt oder einige + Schlüsselwörter ein, um Ontologieempfehlungen zu berechnen. + ontology_recommender: Ontologie-Empfehlungsprogramm + paste_text_recommendations: Fügen Sie einen Textabschnitt oder einige Schlüsselwörter + ein, die bei der Berechnung der Ontologieempfehlungen verwendet werden sollen + recommendation_error: Problem beim Abrufen von Empfehlungen, bitte versuchen Sie + es erneut + text_length_limit: Bitte verwenden Sie weniger als 500 Wörter. Wenn Sie längere + Texte kommentieren müssen, können Sie den Webdienst für Empfehlungen nutzen. + title: empfehlungsgeber + valid_integer_max_ontologies_per_set: Die maximale Anzahl von Ontologien pro Satz + muss ein gültiger ganzzahliger Wert sein + valid_max_ontologies_per_set_range: Die maximale Anzahl von Ontologien pro Satz + muss zwischen 2 und 4 liegen + valid_numeric_weights: Alle Gewichte müssen gültige numerische Werte sein + weight_sum_greater_than_zero: Die Summe der Gewichte muss größer als Null sein + weights_greater_than_zero: Alle Gewichte müssen größer als oder gleich Null sein + register: + account_errors: 'Fehler bei der Erstellung Ihres Kontos:' + confirm_password: Bestätigen Sie das Passwort + create_account: Neues Konto erstellen + email: E-Mail + first_name: Vornamen + last_name: Nachname + mailing_list: Registrieren Sie sich für die Mailingliste des AgroPortal + optional: "(fakultativ)" + password: Passwort + title: Register + username: Benutzername + reproduce_results: Reproduzieren Sie diese Ergebnisse mit der + score: Ergebnis + search: + categories: Kategorien + class_search: Klasse suchen + classes_with_definitions: Klassen mit Definitionen + hide_advanced_options: Erweiterte Optionen ausblenden + include_in_search: In die Suche einbeziehen + index: + categories_placeholder: Beginnen Sie mit der Eingabe, um Kategorien auszuwählen, + oder lassen Sie das Feld leer, um alle zu verwenden + obsolete_definition: 'Eine Klasse, die von den Autoren der Ontologie als veraltet + gekennzeichnet wurde und deren Verwendung nicht empfohlen wird. Diese Klassen + werden oft in Ontologien belassen (anstatt sie ganz zu löschen), damit bestehende + Systeme, die von ihnen abhängen, weiterhin funktionieren + + ' + property_definition: Eine benannte Assoziation zwischen zwei Entitäten. Beispiele + sind "Definition" (eine Beziehung zwischen einer Klasse und Text) und "Teil + von" (eine Beziehung zwischen zwei Klassen). + search_keywords_placeholder: Geben Sie eine Klasse ein, z. B. Melanom + narrow_search_to: Suche eingrenzen auf + obsolete_classes: Überholte Klassen + ontologies: Ontologien + ontology_views: Ontologie-Ansichten + property_values: Immobilienwerte + show_advanced_options: Erweiterte Optionen anzeigen + title: Suche + view_search_documentation: Dokumentation zur Suche anzeigen + select_from_list: Aus Liste auswählen + select_ontologies: Beginnen Sie mit der Eingabe, um Ontologien auszuwählen, oder + lassen Sie sie leer, um alle zu verwenden + select_ontologies_list: Ontologien auswählen + show_advanced_options: Erweiterte Optionen anzeigen + specialization: Spezialisierung + temporality: Zeitlichkeit + text: Text + type: Typ + umls_sem_type: UMLS Sem Typ + view_fair_scores_definitions: Definitionen für faire Noten anzeigen + visits: Besuche + weights_configuration: Gewichte Konfiguration diff --git a/config/locales/en.rb.sample b/config/locales/en.rb.sample deleted file mode 100644 index 56e0ccd50..000000000 --- a/config/locales/en.rb.sample +++ /dev/null @@ -1,112 +0,0 @@ -{ - :en => { - :home => { - :intro => 'Use ' + $SITE + ' to access and share ontologies. You can create ontology-based - annotations for your own text , link your own project that uses ontologies to the - description of those ontologies , find and create relations between terms in - different ontologies, review and comment on ontologies and their components as you - browse them. Sign in to ' + $SITE + ' - to submit a new ontology or ontology-based project, provide comments on ontologies or add ontology mappings.', - - :facebook_button => '', - - :twitter_button => '', - - :annotate => { - :intro => 'The ' + $ORG_SITE + ' Annotator processes text submitted by users, recognizes relevant ontology terms in the text and returns - the annotations to the user. Use the interface below to submit sample text to get ontology-based annotations. Hover the mouse pointer on any - button to see what it does. Click on the (?) to see a detailed help panel. -

    - Subscribe to the NCBO Annotator Users Google group to learn more about - who and how the Annotator is being used in different projects.' - }, - - :resources => { - :intro => $ORG + ' is building a system for automated ontology-based annotation and indexing of data. We process the textual metadata of diverse elements of resources - to annotate and index them with terms from appropriate ontologies. Use the interface below to search the resulting index of annotations and to identify - data resources annotated with particular ontology terms.' - }, - - :footer => 'Powered by NCBO BioPortal   - Release Notes' - }, - - :projects => { - :intro => 'Browse the ontology-based projects in the community: Each project description is linked to ' + $ORG_SITE + ' ontologies that the project uses. - Use the ‘Add Project’ link to add your ontology-based project to this list and to link it to ' + $ORG_SITE + ' ontologies. - Your project will then appear on the pages that list the details for the ontologies that you selected. We also invite you to review ontologies that you used in your project.' - }, - - :ontologies => { - :intro => 'Access all ontologies that are available in ' + $ORG_SITE + ': - You can filter this list by category to display ontologies relevant for a certain domain. - You can also filter ontologies that belong to a certain group. Subscribe to the ' + $ORG_SITE + ' RSS feed - to receive alerts for submissions of new ontologies, new versions of ontologies, new notes, and new projects. You can subscribe to feeds for a specific ontology at - the individual ontology page. Add a new ontology to ' + $ORG_SITE + ' using the Submit New Ontology link (you need to sign in - to see this link).', - - :metrics => { - :intro => '' + $SITE + ' calculates the metrics on the salient properties of the ontology, including statistics and quality-control - and quality-assurance metrics. Each ontology may have all, some, or no values filled in for its metrics and only metrics - for the most recent version are reflected. The metrics currently do not distinguish between the terms defined directly in - this ontology and imported terms (for OWL) or referenced terms (for OBO). - See metrics descriptions.' - } - }, - - :mappings => { - :intro => 'Use this page to explore mappings between ontologies that you are interested in. You will also see the mappings when you browse individual ontologies.' - }, - - :about => { - :welcome => 'Welcome to the National Center for Biomedical Ontology’s BioPortal. BioPortal is a Web-based application for accessing and sharing biomedical ontologies.', - :getting_started => $SITE + ' allows users to browse, upload, download, search, comment on, and create mappings for ontologies.', - :browse => ' -

    - Users can browse and explore individual ontologies by navigating either a tree structure or an animated graphical view. Users can also view mappings and ontology metadata, and download ontologies. -

    -

    - Additionally, users who are signed in may also submit a new ontology to the library. All submissions to the library are reviewed. -

    ', - :announce_list => 'To receive notices of new ' + $SITE + ' releases or site outages, please email ' + $SUPPORT_EMAIL, - :release_notes => '' - }, - - :most_viewed_date => 'March, 2012', - - :most_viewed => - ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    OntologyViews
    ', - - :stats => - '' - } -} diff --git a/config/locales/en.yml b/config/locales/en.yml index 29050980a..3daa18ad8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,449 +1,78 @@ +--- en: - nbco_annotatosplus: - score_help: "Score annotations following previous NCBO 2009 measure (old) or Score annotations following C-Value measure (cvalue) or Score annotations following C-Value measure with hierarchy expansion (cvalueh)" - start_typing_to_select: "Start typing to select %{type} or leave blank to use all" - include_ancestors_up_to_level: "Include ancestors up to level" - include_score: "Include score" - - date: - 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' # March 1, 2017 - - layouts: - footer: - copyright_html: Copyright © 2005‑2022, The Board of Trustees of Leland Stanford Junior University. All rights reserved. - grant_html: > - %{site} is currently being developed within French ANR D2KAB project (ANR-18-CE23-0017). It also receives or received support from ANR SIFR project (ANR-12-JS02-0010), European Union H2020-MSCA SIFRm project (No 701771), the NUMEV Labex (ANR-10-LABX-20), the IBC of Montpellier project (ANR-11-BINF0002), the Agro Labex (ANR-10-LABX-0001) as well as from University of Montpellier and the CNRS. - notices: - license_contact: > - For more information, email support@ontoportal.org or - visit https://ontoportal.org/licensing. - license_obtain: > - If you are the owner of this OntoPortal installation, you can visit - https://license.ontoportal.org to obtain a license. - license_expired: > - We're sorry, but the license for this OntoPortal installation has expired. If you are the owner of this OntoPortal installation, - please visit https://license.ontoportal.org to renew your license. - license_trial: - one: - This installation of the OntoPortal Appliance is a trial license, which will expire in 1 day. - other: - This installation of the OntoPortal Appliance is a trial license, which will expire in %{count} days. - - home: - index: - find_ontology_placeholder: Start typing ontology name, then choose from list - query_placeholder: Enter a class, e.g. Melanoma - tagline: the world's most comprehensive repository of biomedical ontologies - title: Welcome to the %{organization} - welcome: Welcome to %{site}, - help: - welcome: Welcome to the National Center for Biomedical Ontology’s %{site}. %{site} is a web-based application for accessing and sharing biomedical ontologies. - getting_started: > - %{site} allows users to browse, upload, download, search, comment on, and create mappings for ontologies. - browse: > - Users can browse and explore individual ontologies by navigating either a tree structure or an animated graphical view. Users can also view mappings and - ontology metadata, and download ontologies. Additionally, users who are signed in may submit a new ontology to the library. - rest_examples_html: View documentation and examples of the %{site} REST API. - announce_list_html: > - To receive notices of new releases or site outages, please subscribe to the - bioontology-support list. - - annotator: - index: - intro: Get annotations for biomedical text with classes from the ontologies - annotatorplus_html: Check out AnnotatorPlus beta; a new version of the Annotator with added support for negation, and more! - fast_context: - tooltip: "Enable FastContext to detect : if a concept has been negated (affirmed, negated), who experienced the found concept (patient, other), when the annotated concept occurred (recent, historical, hypothetical), and/or if the annotated concept is uncertain (certain, uncertain)." - sample_text: Melanoma is a malignant tumor of melanocytes which are found predominantly in skin but also in the bowel and the eye. - - select: "Select %{name}" - - fast_context: - title: "FastContext" - help: "Activate FastContext to detect: if a concept has been negated (affirmed, negated), who experienced the concept found (patient, other), when the annotated concept occurred (recent, historical, hypothetical), and/or if the annotated concept is uncertain (certain, uncertain)." - - annotations: "Annotations" - - filters: - by: - filter: "Filter" - title: "Results are filtered by" - class: "Class" - ontology: "Ontology" - match_type: "Type" - match_context: "Context" - matched_class: "Associated Class" - matched_ontology: "Associated Ontology" - umls_sem_type: "UMLS Semantic Type" - score: "Score" - negation: "Negation" - experiencer: "Experiencer" - temporality: "Temporality" - certainty: "Certainty" - - additional_parameters_explained_at: "Additional parameters are explained on the page:" - format_results_as: "Format results as:" - reproduce_results_using: "Reproduce these results using the" - match_longest_only: "Match longest only" - match_partial_words: "Recognize partial words" - include_mappings: "Include mappings" - exclude_numbers: "Exclude numbers" - exclude_synonyms: "Exclude synonyms" - max_hierarchy_level: "Include ancestors up to level" - score: "Include score" - score_help: "Score annotations according to the previous NCBO 2009 measure (old) or score annotations using the C-Value measure (cvalue) or score annotations using the C-Value measure with hierarchy expansion (cvalueh)" - score_threshold: "Filter by score threshold" - score_threshold_help: "Specify the minimum score value for annotations" - confidence_threshold: "Filter confidence threshold" - confidence_threshold_help: "Specify the minimum position in the scores distribution (between 1 and 100)" - entity_recognizer: "entity recognition tool" - - index: - intro: > - The NCBO Annotator+ is a proxy calling the NCBO Annotator Web service on the NCBO BioPortal. -

    - Tchechmedjiev, A., Abdaoui, A., Emonet, V., Melzi, S., Jonnagaddala, J., & Jonquet, C. (2018). Enhanced functionalities for annotating and indexing clinical text with the NCBO Annotator+. Bioinformatics, 34(11), 1962-1965. -

    - If using the API, please provide a valid NCBO BioPortal apikey and hit the service at http://services.bioportal.lirmm.fr/ncbo_annotatorplus
    - Text submitted to the NCBO Annotator+ must be in English. - sample_text: Melanoma is a malignant tumor of melanocytes which are found predominantly in skin but also in the bowel and the eye. - - recommender: - intro: Get recommendations for the most relevant ontologies based on an excerpt from a biomedical text or a list of keywords - - search: - index: - intro: Search for a class in multiple ontologies - search_keywords_placeholder: Enter a class, e.g. Melanoma - categories_placeholder: Start typing to select categories or leave blank to use all - property_definition: Named association between two entities. Examples are "definition" (a relation between a class and some text) and "part-of" (a relation between two classes). - obsolete_definition: > - A class that the authors of the ontology have flagged as being obsolete and which they recommend that people not use. These classes - are often left in ontologies (rather than removing them entirely) so that existing systems that depend on them will continue to function. - - projects: - index: - intro: Browse a selection of projects that use %{site} resources - - landscape: - title: "%{site} Landscape" - intro: Visualize data retrieved from ontologies stored in the portal - groups_and_categories: Groups and Categories - ontologies_by: "Ontologies by %{type}" - ontologies_count_by_catalog: Number of ontologies in each data catalog - properties_use: Property Usage - properties_usage_proportion: The proportion of properties usage among stored ontologies - ontologies_languages: Natural languages of ontologies - ontologies_licenses: Licenses used by ontologies - ontology_tools: Most used tools to build ontologies - more_properties_charts: More properties charts - ontology_properties_pie_charts: Pie charts for the properties used in ontologies - owl_ontology_preflabel_uris: URIs for prefLabel properties used for OWL ontologies - owl_ontology_synonym_uris: URIs for synonym properties used for OWL ontologies - owl_ontology_definition_uris: URIs for definition properties used for OWL ontologies - owl_ontology_author_uris: URIs for author properties used for OWL ontologies - ontology_types: Ontology types - ontology_formality_levels: Formality levels of ontologies - ontologies_formats: Format used - ontologies_contributors: Contributors to ontology development - most_active_people: Most active people - most_mentioned_people: Most mentioned people as contact, creator, contributor or curator - most_active_organizations: Most active organizations - funding_endorsing_organizations: Organizations funding and endorsing the most ontologies - ontologies_activity_on: "Ontology Activity on %{site}" - most_active_people_as_reviewer: "Most active people as reviewers" - most_mentioned_people_as_reviewer: People who published notes, reviews, and projects - most_active_ontologies: "Most active ontologies" - ontologies_with_notes_reviews_projects: "Ontologies with notes, reviews, and projects" - ontology_relations_network: "Ontology Relations Network" - relations_between_stored_ontologies: "Relations between stored ontologies in the portal" - filter_network: "Filter network" - ontology_fairness_evaluator: "Ontology FAIRness Evaluator (O’FAIRe)" - average_metrics: "Average metrics" - group: Group - category: Category - size: Size - - home: - find_ontology: "Find an ontology" - search_class: "Search Class" - browse_by_group: "Browse by Group" - browse_ontologies: "Browse ontologies" - comprehensive_repository: "the most comprehensive repository of biomedical ontologies in the world" - advanced_search: "Advanced Search" - ontology_visits: "Ontology visits" - fair_scores: "FAIR Scores" - clear_selection: "Clear Selection" - latest_notes: "Latest Notes" - ontologies: "Ontologies" - classes: "Classes" - individuals: "People" - projects: "Projects" - users: "Users" - no_recent_notes: "No recent notes were submitted" - index: - find_ontology_placeholder: Start typing the ontology name, then choose from - query_placeholder: Enter a class, eg Melanoma - tagline: The home of vocabularies and ontologies in agronomy and related fields. - title: Welcome to the %{organization} - welcome: Welcome to %{site}, - help: - welcome: Welcome to the %{site} of the National Center for Biomedical Ontology. %{site} is a web application for accessing and sharing biomedical ontologies. - getting_started: > - %{site} allows users to browse, upload, download, search, comment, and create mappings for ontologies. - browse: > - Users can browse and explore individual ontologies by navigating either in a tree structure or in an animated graphical view. Users can also view mappings and - ontology metadata and ontology download. Additionally, logged in users can submit a new ontology to the library. - rest_examples_html: View documentation and examples for the %{site} REST API. - announce_list_html: > - To receive notices of new releases or site outages, please subscribe to - bioontology support list. - - recommend: - intro: Get recommendations for the most relevant ontologies from an excerpt of a biomedical text or a list of keywords - title: "recommender" - ontology_recommender: "Ontology Recommender" - ontology_recommendation_input: "Please paste a paragraph of text or some keywords to compute ontology recommendations." - weight_sum_greater_than_zero: "The sum of weights must be greater than zero" - weights_greater_than_zero: "All weights must be greater than or equal to zero" - valid_numeric_weights: "All weights must be valid numeric values" - valid_integer_max_ontologies_per_set: "Max ontologies per set must be a valid integer value" - valid_max_ontologies_per_set_range: "Max ontologies per set must be a number between 2 and 4" - recommendation_error: "Problem retrieving recommendations, please try again" - no_recommendations: "No recommendations found" - no_sets_recommended: "There are no recommended ontology sets for the provided input. Please try the 'Ontologies' output." - text_length_limit: "Please use less than 500 words. If you need to annotate longer pieces of text, you can use the recommendation web service." - - mappings: - title: "Correspondences" - upload_mappings: "Upload mappings" - mappings_bulk_load: "Mapping Bulk Load" - intro: "Browse mappings between classes of different ontologies" - no_mappings_available: "No mappings available" - loading_mappings: "Loading mappings..." - find_mappings: "Find mappings of a class/concept" - view_mappings_help: "View mappings help" - select_class: "Start typing to select a class" - select_ontologies: "Start typing to select ontologies or leave blank to use all ontologies" - select_semantic_types: "Select UMLS semantic types" - select_semantic_types_help: "Start typing to select UMLS semantic types or leave blank to use all types" - select_semantic_groups: "Select UMLS semantic groups" - select_semantic_groups_help: "Start typing to select UMLS semantic groups or leave blank to use all groups" - include_ancestors_up_to_level: "Include ancestors up to level" - include_score: "Include score" - - annotator: - title: "Annotator" - get_annotator: "Get annotations" - filters: - match_longest_only: "Match longest only" - match_partial_words: "Recognize partial words" - include_mappings: "Include Mappings" - exclude_numbers: "Exclude Numbers" - exclude_synonyms: "Exclude synonyms" - max_hierarchy_level: "Include ancestors up to level" - score: "Include score" - score_help: "Score annotations following previous NCBO 2009 measure (old) or Score annotations following C-Value measure (cvalue) or Score annotations following C-Value measure with hierarchy expansion (cvalueh)" - score_threshold: "Filter by score threshold" - score_threshold_help: "Specify minimum score value for annotations" - confidence_threshold: "Filter Confidence Threshold" - confidence_threshold_help: "Specify the minimum position in the score distribution (between 1 and 100)" - recognizer recognizer: "entity recognition tool" - start_typing_to_select: "Start typing to select %{type} or leave blank to use all" - select: "Select %{name}" - enter_or_paste_text: "Enter or paste text to annotate" - fast_context: "FastContext" - lemmatize: "Lemmatize" - annotations_result: "Annotations" - results_filtered_by: "Results are filtered by" - - umls: - semantic_types: "UMLS Semantic Types" - semantic_groups: "UMLS Semantic Groups" - index: - intro: Get annotations for biomedical text with ontology classes - annotatorplus_html: Check out the beta version of AnnotatorPlus; a new version of the Annotator with added support for negation, and more! - fast_context: - tooltip: "Enable FastContext to detect: if a concept was denied (affirmed, denied), who experienced the found concept (patient, other), when the annotated concept occurred (recent, historical, hypothetical), and/ or if the annotated concept is uncertain (certain, uncertain)." - lemmatize: - tooltip: "Enable Lemmatize to lemmatize submitted text and use a lemmatized dictionary for annotations" - sample_text: Melanoma is a malignant tumor of melanocytes found mainly in the skin but also in the intestine and the eye. - - ontology_details: - metadata: - details: "Details" - acronym: "Acronym" - visibility: "Visibility" - viewing_restriction: "Viewing Restriction" - view_of_ontology: "View of ontology" - description: "Description" - status: "Status" - format: "Format" - contact: "Contact" - categories: "Categories" - groups: "Groups" - pull_url: "Pull URL" - submissions: "Submissions" - links: "Links" - add_submission: "Add Submission" - views_of: "Views of" - create_new_view: "Create a new view" - no_views_of: "No views of %{name} available" - go_to_rest_api_json_entry: "Go to REST API JSON Entry" - get_my_metadata_back: "Get my metadata back" - n_triple: "N-Triple" - json_ld: "JSON-LD" - rdf_xml: "RDF/XML" - view_individual_metrics_definitions: "View individual metrics definitions" - metrics: "Metrics" - metrics_not_calculated_yet: "We have not yet calculated metrics for" - classes: "Classes" - individuals: "Individuals" - properties: "Properties" - max_depth: "Maximum depth" - max_children: "Maximum number of children" - avg_children: "Average number of children" - single_child_classes: "Classes with a single child" - many_children_classes: "Classes with more than 25 children" - no_definition_classes: "Classes without definition" - visits: "Visits" - download_as_csv: "Download as CSV" - projects_using: "Projects Using" - no_projects_using: "No projects are using" - create_new_project: "Create a new project" - additional_metadata: "Additional Metadata" - - layout: - header: - browse: "Browse" - search: "Search" - mappings: "Mappings" - recommend: "Recommend" - annotator: "Annotator" - ncbo_annotator_plus: "NCBO Annotator+" - projects: "Projects" - landscape: "Landscape" - login: "Login" - account_setting: "Account Settings" - submit_feedback: "Send Feedback" - help: "Help" - release_notes: "Release Notes" - publications: "Publications" - footer: - products: Products - ontoportal: OntoPortal - release_notes: Release Notes - api: API - sparql: SPARQL - support: Support - contact_us: Contact Us - wiki: Wiki - documentation: Documentation - agreements: Agreements - terms: Terms - privacy_policy: Privacy Policy - cite_us: Cite Us - acknowledgments: Acknowledgments - about: About - about_us: About Us - projects: Projects - team: Team - copyright_html: Copyright © 2005-2022, Leland Stanford Junior University Board of Trustees. All rights reserved. - grant_html: > - %{site} is currently being developed as part of the ANR D2KAB project (ANR-18-CE23-0017). It receives or has also received support from the ANR SIFR project (ANR-12-JS02-0010), European Union Project H2020-MSCA SIFRm (N° 701771), the Labex NUMEV (ANR-10-LABX-20), the Montpellier IBC project (ANR-11 -BINF0002) , the Agro Labex (ANR-10-LABX-0001) as well than the University of Montpellier and the CNRS. - notes: - license_contact: > - For more information, email support@ontoportal.org or - visit https://ontoportal.org/licensing. - license_obtain: > - If you are the owner of this OntoPortal installation, you can visit - https://license.ontoportal.org to obtain a license. - license_expired: > - We're sorry, but the license for this OntoPortal installation has expired. If you are the owner of this OntoPortal installation, - please visit https://license.ontoportal.org to renew your license. - license_trial: - one: This OntoPortal appliance installation is a trial license, which will expire in 1 day. - other: This OntoPortal appliance installation is a trial license, which will expire in %{count} days. - # Other - - about: > -
    -

    Summary

    -

    - Many vocabularies and ontologies are produced to represent and annotate agronomic data. It is therefore necessary to have a common platform to identify them, host them and use them in agro-informatics applications. The AgroPortal project aims to provide a repository of reference ontologies for agronomy, by reusing the NCBO BioPortal technology. The scientific results and the experience of the biomedical field are thus exploited and transposed in the field of agronomy, including plants, food, the environment and possibly animal sciences. We propose an ontology portal that offers ontology hosting, search, versioning, visualization, commenting, recommendation, allows semantic annotation, as well as storing and exploiting alignments of ontologies. All this in an infrastructure fully compliant with the Semantic Web. The AgroPortal project pays particular attention to meeting the requirements of the agronomic community in terms of ontology formats (e.g. SKOS, trait dictionaries) or supported functionalities. The AgroPortal project is based on five agronomic use cases that participate in the design and orientation of the platform. AgroPortal already offers a robust and stable reference repository, of great value for the field of agronomy. -

    -

    Use cases

    - -
    -

    New Features

    -

    - See the release-notes -

    -
    -

    Partners

    -

    The National Center for Biomedical Ontology (NCBO), the Research Institute for Development (IRD), Research Data Alliance (RDA), - Bioversity International, Food & Agriculture Organization (FAO), Global Open Data for Agriculture & Nutrition (Godan Action), National Institute for Agronomic Research (INRA)

    -

    thanks

    -

    The AgroPortail is partly produced as part of the Semantic Indexing of French Biomedical Resources project (SIFR) - who have received funding from the EU H2020 research and innovation program under Marie Sklodowska-Curie (grant 701771) - and the National Research Agency (ANR-12-JS02-01001 grant), Labex NUMEV (ANR-10-LABX-20 grant), - the Institute of Computational Biology of Montpellier (grant ANR-11-BINF-0002) as well as by the University of Montpellier and the CNRS. - We also thank the National Center of Biomedical Ontologies for their help and the time spent with us in the deployment of the AgroPortail.

    -

    Team

    - To contact us: firstname.lastname@lirmm.fr -
      -
    • Clément Jonquet, researcher at LIRMM (Univ. of Montpellier, France), principal investigator of the AgroPortal project
    • -
    • Anne Toulet, researcher at LIRMM (Univ. of Montpellier, France)
    • -
    • Vincent Emonet, engineer at LIRMM (Univ. of Montpellier, France)
    • -
    -
    - activaterecord: errors: models: license: attributes: encrypted_key: - invalid_license_key: is an invalid license key + invalid_license_key: is an invalid license key. no_appliance_id_for_comparison: Could not be validated. Unable to retrieve virtual appliance ID. - appliance_id_mismatch: is an appliance id mismatch - - admin: - licenses: - create: - success: License renewed successfully! + appliance_id_mismatch: is an appliance id mismatch. date: 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" # March 1, 2017 - - ontologies: - self: "Ontologies" - loading: Loading ontologies - intro: Browse the Ontology Library - please_wait: Please wait.. - browse: Explore - welcome_admin: Welcome admin - admin_help: This color indicates features reserved for administrators - debug_info: Debug Info - submit_new_ontology: Submit a new ontology - entry_type: Entry Type - uploaded_in_the_last: Uploaded in the last - + + acceptance: Acceptance + additional_parameters: 'Additional parameters explained at ' + admin: + licenses: + create: + success: License renewed successfully! + + all: All + + annotator: + annotate_text_prompt: Enter or paste text to be annotated + annotations_result: Annotations + fast_context: FastContext + filters: + confidence_threshold: Filter confidence threshold + confidence_threshold_help: Specify the minimum position in the score distribution + (between 1 and 100). + exclude_numbers: Exclude numbers + exclude_synonyms: Exclude synonyms + include_mappings: Include mappings + match_longest_only: Match longest only + match_partial_words: Recognize partial words + max_hierarchy_level: Include ancestors up to level + score: Include score + score_help: Score annotations following previous NCBO 2009 measure (old) or + Score annotations following C-Value measure (cvalue) or Score annotations + following C-Value measure with hierarchy expansion (cvalueh). + score_threshold: Filter by score threshold + score_threshold_help: Specify minimum score value for annotations. + get_annotator: Get annotations + index: + fast_context: + tooltip: 'Enable FastContext to detect: if a concept is denied (affirmed, + denied), who experienced the found concept (patient, other), when the annotated + concept occurred (recent, historical, hypothetical), and/ or if the annotated + concept is uncertain (certain, uncertain).' + intro: Get annotations for text with ontology classes + lemmatize: + tooltip: Enable Lemmatize to lemmatize submitted text and use a lemmatized + dictionary for annotations + sample_text: Melanoma is a malignant tumor of melanocytes found mainly in the + skin but also in the intestine and the eye. + lemmatize: Lemmatize + results_filtered_by: Results are filtered by + select: Select %{name} + start_typing_to_select: Start typing to select %{type} or leave blank to use all + title: Annotator + umls: + semantic_groups: UMLS Semantic Groups + semantic_types: UMLS Semantic Types + + certainty: Certainty + class: Class + clear_selection: Clear selection concepts: request_term: - new_term_instructions: > + new_term_instructions: |

    This ontology integrates with OntoloBridge, allowing community users to suggest additions to the public ontology. Complete the template below to submit a term request directly to the Ontology Manager.

    Term label (required)
    Suggested term name. If a term can be described with more than one synonym, enter only the preferred name here.

    @@ -452,101 +81,334 @@ en:

    Term Description (required)
    A brief definition, description, or usage of the suggested term. Synonyms of additional terms may be listed in this section.

    -

    Superclass (required)
    The parent term of the suggested term. The parent term must be an existing entry in the current ontology. The superclass can be selected directly from the Bioportal class tree.

    +

    Superclass (required)
    The parent term of the suggested term. The parent term must be an existing entry in the current ontology. The superclass can be selected directly from the class tree.

    -

    References (optional)
    Provide evidence that the requested term exists, such as Pubmed IDs of articles or links to other resources describing the term.

    +

    References (optional)
    Provide evidence that the requested term exists, such as IDs of articles or links to other resources describing the term.

    Justification (optional)
    Provide here any additional information about the requested term.

    + context: Context + coverage: Coverage + filter: Filter + format_results: 'Format results as ' + get_json_version: Get the json version + get_recommendations: Get recommendations + help: Help + + home: + agroportal_figures: "%{site} in figures" + benefit1: Discover new insights and knowledge by exploring other ontologies or smeantic resources in the repository. + benefit2: Contribute to knowledge sharing and semantic interoperability in your domain. + benefit3: Map your ontology to other relevant ones in the domain and collaborate with other users. + benefit4: Get feedback and suggestions from other users who can use and comment on your ontology. + benefit5: Precisely describe your ontology with relevant metadata and get a FAIR score for your ontology. + fair_details: See details + fairness: FAIR Score + get_annotations: Get annotations + get_recommendations: Get recommendations + index: + tagline: The home of ontologies and semantic artefacts in agri-food and related domains. + title: Welcome to %{site} + welcome: Welcome to %{site}, + ontology_upload: Do you want to share an ontology? + ontology_upload_benefits: 'By uploading and sharing your ontology to %{site}, you can:' + ontology_upload_button: Submit ontology + ontology_upload_desc: Uploading an ontology or another type of semantic artefact (vocabulary, terminology, thesaurus, ...) is a way of sharing your knowledge with others. + paste_text_prompt: Enter a paragraph of text or some keywords... + recommender_annotator: Recommender and Annotator + supported_by: Supported by + twitter_news: News + with_collaboration: With the collaboration of + + input: Input + insert_sample_text: Insert sample text + keywords: Keywords + keywords_separated_by_commas: Keywords separated by commas + knowledge_detail: Knowledge detail + + landscape: + average_metrics: Average metrics + category: category + filter_network: Filter network + funding_endorsing_organizations: Organizations funding and endorsing the most + ontologies + group: group + groups_and_categories: Groups and Categories + intro: Visualize the panorama of ontologies and other semantic resources of %{site} via metadata aggregation. + more_properties_charts: More properties charts + most_active_ontologies: Most active ontologies + most_active_organizations: Most active organizations + most_active_people: Most active people + most_active_people_as_reviewer: Most active user account + most_mentioned_people: Most mentioned people as contact, creator, contributor or curator + most_mentioned_people_as_reviewer: User account who published notes, reviews, and projects + ontologies_activity_on: Ontology activity + ontologies_by: Ontologies by %{type} + ontologies_contributors: Contributors to ontology development + ontologies_count_by_catalog: Number of ontologies in each semantic artefact catalog + ontologies_formats: Representation language + ontologies_languages: Natural languages + ontologies_licenses: Licenses + ontologies_with_notes_reviews_projects: Ontologies with notes, reviews, and projects + ontology_fairness_evaluator: Ontology FAIRness Evaluator (O’FAIRe) + ontology_formality_levels: Formality levels + ontology_properties_pie_charts: Pie charts retated to object description properties (i.e., meatdata properties used to describe ontology objects). + ontology_relations_network: Ontology relations network + ontology_tools: Most used tools to build ontologies + owl_ontology_author_uris: Properties used to specify object author + owl_ontology_definition_uris: Properties used to specify objects definition + owl_ontology_preflabel_uris: Properties used to specify objects preferred label + owl_ontology_synonym_uris: Properties used to specify object synonyms + properties_usage_proportion: Pie charts related to the most frequent values for certain metadata properties + properties_use: Property usage + relations_between_stored_ontologies: Set of relationships between %{site} ontologies as captured by metadata. Green ontologies are stored in the repository while those in blue are external resources. + size: size + title: "Landscape" + + layout: + header: + account_setting: Account settings + annotator: Annotator + browse: Browse + documentation: Documentation + help: Help + landscape: Landscape + login: Login + logout: Logout + mappings: Mappings + publications: Publications + release_notes: Release notes + search_prompt: Search in %{portal_name} ... + submit_feedback: Send feedback + support: Support + cite_us: Cite Us + footer: + products: Products + ontoportal: OntoPortal + release_notes: Release Notes + api: API + sparql: SPARQL + support: support + contact_us: Contact Us + wiki: Wiki + documentation: Documentation + agreements: Legal + terms: Terms and Conditions + privacy_policy: Privacy Policy + cite_us: Cite Us + acknowledgments: Acknowledgments + about: About + about_us: About Us + projects: D2KAB + team: Team - # General - - showing: Display - of: of - sort: Sort - popular: Popular - name: Name - classes_count: Number of classes - instances_concepts_count: Number of instances/concepts - Notes: Notes - upload_date: Upload date - release_date: Release date - fair_score: FAIR score - search_rank: Search Rank - no_matches: No matches! - uploaded: Uploaded - view_of: View of - view: View - summary_only: Summary only - groups: Groups - categories: Categories - admins: Administrators - status: Status - no_submissions_available: No submissions available - classes: classes + login: + enter_email: Enter your username + enter_password: Enter your password + forgot_password: Forgot password? + invalid_login: Errors on the form + no_account: Do not have an account? + password: Password + register: Register + title: Login + username_or_email: Username + + mappings: + 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 + + matched_class: Matched class + matched_ontology: matched ontology + max_ontologies_per_set: Maximum number of ontologies per set + + nbco_annotatosplus: + annotations: Annotations + enter_paste_text_to_annotate: 'Enter or paste the text to annotate' + exclude_numbers: Exclude numbers + exclude_synonyms: Exclude synonyms + fast_context: + help: 'Activate FastContext to detect: if a concept has been negated (affirmed, + negated), who experienced the concept found (patient, other), when the annotated + concept occurred (recent, historical, hypothetical), and/or if the annotated + concept is uncertain (certain, uncertain).' + title: FastContext + filters: + additional_parameters_explained_at: 'Additional parameters are explained on + the page:' + by: + certainty: Certainty + class: Class + experiencer: Experiencer + filter: Filter + match_context: Context + match_type: Type + matched_class: Associated Class + matched_ontology: Associated Ontology + negation: Negation + ontology: Ontology + score: Score + temporality: Temporality + title: Results are filtered by + umls_sem_type: UMLS Semantic Type + confidence_threshold: Filter confidence threshold + confidence_threshold_help: Specify the minimum position in the scores distribution (between 1 and 100) + format_results_as: 'Format results as:' + reproduce_results_using: Reproduce these results using the + score_threshold: Filter by score threshold + score_threshold_help: Specify the minimum score value for annotations + include_ancestors_up_to_level: Include ancestors up to level + include_mappings: Include mappings + include_score: Include score + index: + intro: | + The NCBO Annotator+ is a proxy calling the NCBO Annotator web service on the NCBO BioPortal. +

    + Tchechmedjiev, A., Abdaoui, A., Emonet, V., Melzi, S., Jonnagaddala, J., & Jonquet, C. (2018). Enhanced features for annotating and indexing clinical text with NCBO Annotator+. Bioinformatics, 34(11), 1962-1965. +

    + If you are using the API, please provide a valid NCBO BioPortal API key and access the service at http://services.bioportal.lirmm. en/ncbo_annotatorplus
    + Text submitted to NCBO Annotator+ must be in English. + sample_text: Melanoma is a malignant tumor of melanocytes found mainly in the skin but also in the intestine and the eye. + title: NCBO Annotator + + insert_sample_text: Insert sample text + match_longest_only: Match longest only + match_partial_words: Match partial words + recognizer: Entity recognition + score_help: Score annotations following previous NCBO 2009 measure (old) or Score + annotations following C-Value measure (cvalue) or Score annotations following + C-Value measure with hierarchy expansion (cvalueh) + select: Select %{name} + select_ontologies: Start typing to select ontologies or leave blank to use all + ontologies + select_ontologies_list: Select ontologies + show_advanced_options: Show advanced options + start_typing_to_select: Start typing to select %{type} or leave blank to use all + umls: + semantic_groups: UMLS Semantic Groups + semantic_types: UMLS Semantic Types + negation: negation + none: none + + ontologies: + ontology_search_prompt: 'Search an ontology or a term (e.g., plant height)' + self: Ontologies + + ontology: Ontology + ontology_details: + concept: + definitions: Definitions + id: ID + in_schemes: In schemes + member_of: Member of + no_preferred_name_for_selected_language: No preferred name for selected language. + obsolete: Obsolete + preferred_name: Preferred name + synonyms: Synonyms + type: Type + metadata: + additional_metadata: Additional Metadata + header: + last_submission_date: Last submission date + ontology_sets: Ontology sets + output: Output + + projects: + contacts: Contacts + create_new_project: Create new project + created: Created + creator: User + delete_admin_only: Delete (admin only) + delete_confirm: Are you sure? + description: Description + description_text: Description text + edit: Edit + home_page: Home page + index: + intro: Browse a selection of projects that use %{site} ontologies + institutions: Institutions + ontologies: Ontologies + project_description: Project Description + self: Projects + title: Projects List + view_projects_help: View projects help + + recommender: + intro: Get recommendations for the most relevant ontologies from an excerpt of text or a list of keywords + no_recommendations: No recommendations found + no_sets_recommended: There are no recommended ontology sets for the provided input. Please try the 'Ontologies' output. + ontology_recommendation_input: Please paste a paragraph of text or some keywords to compute ontology recommendations. + ontology_recommender: Recommender + paste_text_recommendations: Paste a paragraph of text or some keywords separated by commas to compute ontology recommendations + recommendation_error: Problem computing recommendations, please try again. + text_length_limit: Please use less than 500 words. If you need to annotate longer pieces of text, you can use the recommendation wWb service. + title: Recommender + valid_integer_max_ontologies_per_set: Max ontologies per set must be a valid integer + value + valid_max_ontologies_per_set_range: Max ontologies per set must be a number between + 2 and 4 + valid_numeric_weights: All weights must be valid numeric values + weight_sum_greater_than_zero: The sum of weights must be greater than zero + weights_greater_than_zero: All weights must be greater than or equal to zero + + register: + account_errors: 'Errors creating your account:' + confirm_password: Confirm password + create_account: Create new account + email: Email + first_name: First name + last_name: Last name + mailing_list: Register to the %{site}'s mailing list + optional: "(Optional)" + password: Password + title: Register + username: Username + + reproduce_results: 'Reproduce these results using the ' + score: Score + + search: + categories: Categories + class_search: Class Search + classes_with_definitions: Classes with definitions + hide_advanced_options: Hide advanced pptions + include_in_search: Include in search + index: + categories_placeholder: Start typing to select categories or leave blank to + use all + obsolete_definition: 'A class that the ontology authors have flagged as obsolete + and recommend not to use. These classes are often left in ontologies (rather + than being deleted entirely) so that existing systems that depend on them + continue to function." - category: Category - group: Group - size: size - ontology_content: Ontology content - natural_language: Natural language - formality_levels: Formality levels - is_of_type: Is of type - missing_status: Missing Status - types: Types - artifacts: Artifacts - formats: formats - selected_ontologies: Selected ontologies - all: "All" - none: "none" - keywords: "Keywords" - keywords_separated_by_commas: "Keywords separated by commas" - see_details: "See details" - view_fair_scores_definitions: "View fair scores definitions" - get_json_version: "Get the json version" - select_ontologies: "Start typing to select ontologies or leave blank to use them all" - clear_selection: "Clear Selection" - select_from_list: "Select from list" - more: "More" - statistics: "Statistics" - average: "Average" - min: "Min" - max: "Max" - median: "Median" - slices: "Slices" - help: "Help" - or: "Or" - show_advanced_options: "Show advanced options" - insert_sample_text: "Insert sample text" - class: "Class" - filter: "Filter" - ontology: "Ontology" - type: "Type" - context: "Context" - umls_sem_type: "UMLS Sem Type" - matched_ontology: "matched ontology" - matched_class: "Matched class" - score: "Score" - negation: "negation" - experience: "Experience" - temporality: "Temporality" - certainty: "Certainty" - format_results: "Format results as " - reproduce_results: "Reproduce these results using the " - additional_parameters: "Additional Parameters Explained at " - input: "Input" - text: "Text" - output: "Output" - ontology_sets: "Ontology sets" - insert_sample_input: "Insert sample input" - weights_configuration: "Weights Configuration" - coverage: "Coverage" - accept: "Accept" - knowledge_detail: "Knowledge Detail" - specialization: "Specialization" - max_ontologies_per_set: "Maximum number of ontologies per set" - paste_input_text: "Paste a paragraph of text or keywords to use in calculating ontology recommendations" - get_recommendations: "Get recommendations" - select_ontologies_list: "Select ontologies" + ' + property_definition: A named association between two entities. Examples are + 'definition' (a relationship between a class and text) and 'part of' (a relationship + between two classes). + search_keywords_placeholder: Enter a term, e.g. Melanoma + narrow_search_to: Narrow search to + obsolete_classes: Obsolete classes + ontologies: Ontologies + ontology_views: Ontology views + property_values: Property values + show_advanced_options: Show advanced options + title: Search + view_search_documentation: View search documentation + + select_from_list: Select from list + select_ontologies: Start typing to select ontologies or leave blank to use them all + select_ontologies_list: Select ontologies + show_advanced_options: Show advanced options + specialization: Specialization + temporality: Temporality + text: Text + type: Type + umls_sem_type: UMLS Semantic Type + view_fair_scores_definitions: View FAIR scores definitions + visits: Visits + weights_configuration: Weights configuration diff --git a/config/locales/fr.yml b/config/locales/fr.yml new file mode 100644 index 000000000..78d888ff9 --- /dev/null +++ b/config/locales/fr.yml @@ -0,0 +1,413 @@ +--- +fr: + activerecord: + errors: + models: + license: + attributes: + encrypted_key: + invalid_license_key: est une clé de licence invalide. + no_appliance_id_for_comparison: n'a pas pu être validé. Impossible de récupérer l'ID de l'appliance virtuelle. + appliance_id_mismatch: ne correspond pas à l'ID de l'appliance virtuelle. + + date: + formats: + year_month_day_concise: "%d-%m-%Y" # 01-03-2017 + month_day_year: "%-d %b %Y" # 1 Mar 2017 + monthfull_day_year: "%-d %B %Y" # 1 March 2017 + + acceptance: Acceptation + additional_parameters: 'Paramètres supplémentaires expliqués à ' + admin: + licenses: + create: + success: Licence renouvelée avec succès ! + + all: Tout + + annotator: + annotate_text_prompt: Saisissez ou collez le texte à annoter + annotations_result: Annotations + fast_context: FastContext + filters: + confidence_threshold: Seuil de confiance + confidence_threshold_help: Spécifiez la position minimale dans la distribution + des scores (entre 1 et 100). + exclude_numbers: Exclure les nombres + exclude_synonyms: Exclure les synonymes + include_mappings: Inclure les alignements + match_longest_only: Reconnaître les mots les plus long uniquement + match_partial_words: Reconnaître les mots partiels + max_hierarchy_level: Inclure les ancêtres jusqu'au niveau + score: Inclure le score + score_help: Score des annoations suivant la mesure originale NCBO 2009 (ancienne) + ou Score des annoations suivant la mesure C-Value (cvalue) ou Score des annoations + suivant la mesure C-Value avec expansion de la hiérarchie (cvalueh). + score_threshold: Seuil de score + score_threshold_help: Spécifiez la valeur de score minimum pour les annotations. + get_annotator: Obtenir les annotations + index: + fast_context: + tooltip: 'Activez FastContext pour détecter : si un concept est négatif (affirmé, + nié), qui a vécu le concept trouvé (patient, autre), quand le concept annoté + s''est-il produit (récent, historique, hypothétique), et/ou si le concept annoté + est incertain (certain, incertain).' + intro: Obtenez des annotations avec des classes des ontologies + lemmatize: + tooltip: Activez Lemmatize pour lemmatiser le texte soumis et utiliser un + dictionnaire lemmatisé pour les annotations + sample_text: Le mélanome est une tumeur maligne des mélanocytes qui se trouvent + principalement dans la peau mais aussi dans l'intestin et l'œil. + lemmatize: Lemmatiser + results_filtered_by: Les résultats sont filtrés par + select: Sélectionnez %{name} + start_typing_to_select: Commencez à taper pour sélectionner %{type} ou laissez vide pour tout utiliser + title: Annotateur + umls: + semantic_groups: Groupes sémantiques UMLS + semantic_types: Types sémantiques UMLS + + certainty: Certitude + class: Classe + clear_selection: Effacer la sélection + concepts: + request_term: + new_term_instructions: | +

    Cette ontologie s'intègre à OntoloBridge, permettant aux utilisateurs de la communauté de suggérer des ajouts à l'ontologie publique. Remplissez le modèle ci-dessous pour soumettre une demande de terme directement au responsable de l'ontologie.

    +
    +

    Libellé du terme (obligatoire)
    Nom du terme suggéré. Si un terme peut être décrit avec plusieurs synonymes, n'indiquez ici que le nom préféré.

    +
    +
    +

    Description du terme (obligatoire)
    Une brève définition, description ou utilisation du terme suggéré. Des synonymes de termes supplémentaires peuvent être répertoriés dans cette section.

    +
    +
    +

    Superclasse (obligatoire)
    Le terme parent du terme suggéré. Le terme parent doit être une entrée existante de l'ontologie actuelle. La superclasse peut être sélectionnée directement à partir de l'arborescence des classes.

    +
    +
    +

    Références (facultatif)
    Fournissez des preuves de l'existence du terme demandé, telles que les identifiants d'articles ou des liens vers d'autres ressources décrivant le terme.

    +
    +
    +

    Justification (facultatif)
    Fournissez ici toute information supplémentaire sur le terme demandé.

    +
    + context: Contexte + coverage: Couverture + experiencer: Experiencer + filter: Filtre + format_results: 'Formater les résultats en ' + get_json_version: Obtenir la version json + get_recommendations: Obtenir des recommandations + help: Aide + + home: + agroportail_figures: "%{site} en chiffres" + benefit1: Découvrez de nouvelles perspectives et connaissances en explorant d'autres ontologies ou resources sémantiques dans le portail. + benefit2: Contribuez au partage des connaissances et à l’interopérabilité sémantique dans votre domaine. + benefit3: Alignez votre ontologie avec d'autres ontologies du domaine et collaborez avec d'autres utilisateurs. + benefit4: Obtenez des commentaires et des suggestions d'autres utilisateurs qui peuvent utiliser et commenter votre ontologie. + benefit5: Décrivez précisément votre ontologie avec des métadonnées pertinentes et obtenez un score de "FAIRness" pour votre ontologie. + fair_details: Voir détails + fairness: Score FAIR + get_annotations: Obtenir des annotations + get_recommendations: Obtenir des recommandations + index: + tagline: L'hôte des ontologies et des artefacts sémantiques dans l’agroalimentaire et les domaines connexes. + title: Bienvenue sur %{site} + welcome: Bienvenue sur %{site}, + ontology_upload: Voulez-vous charger une ontologie ? + ontology_upload_benefits: 'En chargeant et partageant votre ontologie sur %{site}, vous pouvez :' + ontology_upload_button: Charger une ontologie + ontology_upload_desc: Charger une ontologie ou un autre type d'artefact sémantique (vocabulaire, terminologie, thésaurus, ...) est un moyen de partager vos connaissances avec d'autres. + paste_text_prompt: Collez un paragraphe de texte ou quelques mots-clés... + recommender_annotator: Recommandeur et Annotateur + supported_by: Avec le soutien de + twitter_news: Actualités + with_collaboration: Une collaboration de + + input: Entrée + insert_sample_text: Insérez un exemple de texte + keywords: Mots-clés + keywords_separated_by_commas: Mots-clés séparés par des virgules + knowledge_detail: Détail de la connaissance + + landscape: + average_metrics: Métriques moyennes + category: catégorie + filter_network: Filtrer le réseau + funding_endorsing_organizations: Organisations qui financent et soutiennent + le plus grand nombre d'ontologies + group: groupe + groups_and_categories: Groupes et catégories + intro: Visualiser le panorama des ontologies et autres resources sémantiques de %{site} via l'aggrégation des métadonnées. + more_properties_charts: Plus de graphiques de propriétés + most_active_ontologies: Ontologies les plus actives + most_active_organizations: Organisations les plus actives + most_active_people: Personnes les plus actives + most_active_people_as_reviewer: Comptes utilisateurs les plus actifs + most_mentioned_people: Personnes les plus mentionnées en tant que contact, créateur, contributeur ou curateur + most_mentioned_people_as_reviewer: Comptes utilisateurs ayant publié des notes, des avis et des projets + ontologies_activity_on: Activité autour des ontologies + ontologies_by: Ontologies par %{type} + ontologies_contributors: Contributeurs au développement des ontologies + ontologies_count_by_catalog: Nombre d'ontologies dans chaque catalogue d'artéfacts sémantiques + ontologies_formats: Language de representation + ontologies_languages: Langues + ontologies_licenses: Licences + ontologies_with_notes_reviews_projects: Ontologies avec des notes, des évaluations et des projets + ontology_fairness_evaluator: Évaluateur du niveau de FAIRness des ontologies (O’FAIRe) + ontology_formality_levels: Niveaux de formalité + ontology_properties_pie_charts: Diagrammes liés aux propriétés de description d'objet (i.e., les propriétés de métadonnées utilisées pour décrire les objets des ontologies). + ontology_relations_network: Réseau des relations entre ontologies + ontology_tools: Outils les plus utilisés pour construire des ontologies + owl_ontology_author_uris: Propriétés utilisées pour spécifier l'auteur de l'objet + owl_ontology_definition_uris: Propriétés utilisées pour spécifier la définition de l'objet + owl_ontology_preflabel_uris: Propriétés utilisées pour spécifier le nom préféré de l'objet + owl_ontology_synonym_uris: Propriétés utilisées pour spécifier les synonymes de l'objet + properties_usage_proportion: Diagrammes liés aux valeurs les plus fréquentes pour certaines propriétés de métadonnées + properties_use: Utilisation des propriétés + relations_between_stored_ontologies: Ensemble des relations entre les ontologies stockées de %{site} tel que capturées par les métadonnées. Les ontologies en vertes sont stockées dans le portail tandis que celles en bleus sont des resources extérieures. + size: taille + title: "Panorama" + + layout: + header: + account_setting: Paramètres du compte + annotator: Annotateur + browse: Parcourir + documentation: Documentation + help: Aide + landscape: Panorama + login: Connexion + logout: Décconnexion + mappings: Alignements + publications: Publications + recommender: Recommendeur + release_notes: Notes de version + search_prompt: Rechercher dans %{portal_name} ... + submit_feedback: Envoyer un commentaire + support: Assistance + cite_us: Citez-nous + footer: + products: Produits + ontoportal: OntoPortal + release_notes: Notes de version + api: API + sparql: SPARQL + support: Assistance + contact_us: Contactez nous + wiki: Wiki + documentation: Documentation + agreements: Juridique + terms: Termes et Conditions + privacy_policy: Politique de confidentialité + cite_us: Citez-nous + acknowledgments: Remerciements + about: À propos + about_us: À propos de nous + projects: D2KAB + team: Équipe + + login: + enter_email: Saisissez votre nom d'utilisateur + enter_password: Saisissez votre mot de passe + forgot_password: Mot de passe oublié ? + invalid_login: Erreurs sur le formulaire + no_account: Vous n'avez pas de compte ? + password: Mot de passe + register: S'inscrire + title: Connexion + username_or_email: Nom d'utilisateur + + mappings: + find_mappings: Trouver les alignements d'une classe ou d'un concept + intro: Trouver tous les alignements d'une ontologie + loading_mappings: Chargement des alignements... + mappings_bulk_load: Charger des alignements via un fichier source + no_mappings_available: Aucune correspondance disponible + title: Alignements + upload_mappings: Charger des alignements + + matched_class: Classe correspondante + matched_ontology: ontologie correspondante + max_ontologies_per_set: Nombre maximum d'ontologies par ensemble + + nbco_annotatosplus: + annotations: Annotations + enter_paste_text_to_annotate: 'Saisissez ou collez le texte à annoter' + exclude_numbers: Exclure les nombres + exclude_synonyms: Exclure les synonymes + fast_context: + help: 'Activez FastContext pour détecter : si un concept est négatif (affirmé, + nié), qui a vécu le concept trouvé (patient, autre), quand le concept annoté + s''est-il produit (récent, historique, hypothétique), et/ou si le concept annoté + est incertain (certain, incertain).' + title: FastContext + filters: + additional_parameters_explained_at: 'Des paramètres supplémentaires sont expliqués + à la page :' + by: + certainty: Certitude + class: Classe + experiencer: Personne concerné + filter: Filtrer + match_context: Contexte + match_type: Type + matched_class: Classe associée + matched_ontology: Ontologie associée + negation: Négation + ontology: Ontologie + score: Score + temporality: Temporalité + title: Les résultats sont filtrés par + umls_sem_type: Type sémantique UMLS + confidence_threshold: Seuil de confiance + confidence_threshold_help: Spécifiez la position minimale dans la distribution des scores (entre 1 et 100) + format_results_as: 'Formater les résultats en tant que :' + reproduce_results_using: Reproduire ces résultats en utilisant le + score_threshold: Seuil de score + score_threshold_help: Spécifiez la valeur de score minimum pour les annotations. + include_ancestors_up_to_level: Inclure les ancêtres jusqu'au niveau + include_mappings: Inclure les alignements + include_score: Inclure le score + index: + intro: | + Le NCBO Annotator+ est un proxy appelant le service Web NCBO Annotator du NCBO BioPortal. +

    + Tchechmedjiev, A., Abdaoui, A., Emonet, V., Melzi, S., Jonnagaddala, J., & Jonquet, C. (2018). Fonctionnalités améliorées pour annoter et indexer le texte clinique avec NCBO Annotator+. Bioinformatique, 34(11), 1962-1965. +

    + Si vous utilisez l'API, veuillez fournir une clé d'API pour le NCBO BioPortal valide et accéder au service via http://services.bioportail.lirmm.fr/ncbo_annotatorplus
    + Le texte soumis au NCBO Annotator+ doit être en anglais. + sample_text: Le mélanome est une tumeur maligne des mélanocytes qui se trouvent principalement dans la peau mais aussi dans l'intestin et l'œil. + title: NCBO Annotator + + insert_sample_text: Insérer un exemple de texte + match_longest_only: Reconnaître les mots les plus long uniquement + match_partial_words: Reconnaître les mots partiels + recognizer: Reconnaissance d'entité + score_help: Score des annoations suivant la mesure originale NCBO 2009 (ancienne) + ou Score des annoations suivant la mesure C-Value (cvalue) ou Score des annoations + suivant la mesure C-Value avec expansion de la hiérarchie (cvalueh). + select: Sélectionnez %{name} + select_ontologies: Commencez à taper pour sélectionner des ontologies ou laissez + vide pour utiliser toutes les ontologies + select_ontologies_list: Sélectionnez les ontologies + show_advanced_options: Affichez les options avancées + start_typing_to_select: Commencez à taper pour sélectionner %{type} ou laissez vide pour tout utiliser + umls: + semantic_groups: Groupes sémantiques UMLS + semantic_types: Types sémantiques UMLS + negation: négation + none: aucune + + ontologies: + ontology_search_prompt: 'Recherchez une ontologie ou un terme (e.g., hauteur de la plante)' + self: Ontologies + + ontology: Ontologie + ontology_details: + concept: + definitions: Définitions + id: Identifiant + in_schemes: Dans les schémas + member_of: Membre de + no_preferred_name_for_selected_language: Pas de nom préféré pour la langue sélectionnée. + obsolete: Obsolète + preferred_name: Nom préféré + synonyms: Synonymes + type: Type + metadata: + additional_metadata: Métadonnées supplémentaires + header: + last_submission_date: Date de la dernière soumission + ontology_sets: Ensembles d'ontologies + output: Sortie + + projects: + contacts: Contacts + create_new_project: Créer un nouveau projet + created: Créé + creator: Utilisateur + delete_admin_only: Supprimer (administrateur seulement) + delete_confirm: Êtes-vous sûr(e) ? + description: Description + description_text: Texte de description + edit: Modifier + home_page: Page d'accueil + index: + intro: Parcourez une sélection de projets qui utilisent les ontologies de %{site} + institutions: Institutions + ontologies: Ontologies + project_description: Description du projet + self: Projets + title: Liste de projets + view_projects_help: Aide à la visualisation des projets + + recommender: + intro: Obtenez des recommandations d'ontologies les plus pertinentes à partir d'un extrait d'un texte ou d'une liste de mots-clés + no_recommendations: Aucune recommandation trouvée + no_sets_recommended: Il n'y a pas d'ensembles d'ontologies recommandés pour l'entrée fournie. Veuillez essayer la sortie 'Ontologies'. + ontology_recommendation_input: Saisissez un paragraphe de texte ou quelques mots-clés pour calculer les recommandations d'ontologie. + ontology_recommender: Recommandeur + paste_text_recommendations: Collez un paragraphe de texte ou quelques mots-clés séparés par des virgules à utiliser pour calculer les recommandations d'ontologie + recommendation_error: Problème lors du calcul des recommandations, veuillez réessayer. + text_length_limit: Veuillez utiliser moins de 500 mots. Si vous devez annoter de plus grands morceaux de texte, vous pouvez utiliser le service Web de recommandation. + title: Recommandeur + valid_integer_max_ontologies_per_set: Le nombre maximum d'ontologies par ensemble doit être une valeur entière valide + valid_max_ontologies_per_set_range: Le nombre maximum d'ontologies par ensemble doit être un nombre entre 2 et 4 + valid_numeric_weights: Tous les poids doivent être des valeurs numériques valides + weight_sum_greater_than_zero: La somme des poids doit être supérieure à zéro + weights_greater_than_zero: Tous les poids doivent être supérieurs ou égaux à zéro + + register: + account_errors: 'Erreurs lors de la création de votre compte :' + confirm_password: Confirmer le mot de passe + create_account: Créer un nouveau compte + email: Email + first_name: Prénom + last_name: Nom + mailing_list: S'inscrire à la liste de diffusion de %{site} + optional: "(Facultatif)" + password: Mot de passe + title: S'inscrire + username: Nom d'utilisateur + + reproduce_results: 'Reproduisez ces résultats en utilisant le ' + score: Score + + search: + categories: Catégories + class_search: Recherche de classe + classes_with_definitions: Classes avec définitions + hide_advanced_options: Masquer les options avancées + include_in_search: Inclure dans la recherche + index: + categories_placeholder: Commencez à taper pour sélectionner des catégories ou + laissez vide pour tout utiliser + obsolete_definition: 'Une classe que les auteurs de l''ontologie ont signalée + comme étant obsolète et qu''ils recommandent de ne pas utiliser. Ces classes + sont souvent laissées dans des ontologies (plutôt que de les supprimer entièrement) + afin que les systèmes existants qui en dépendent continuent à fonctionner. + + ' + property_definition: Association nommée entre deux entités. Les exemples sont + "définition" (une relation entre une classe et du texte) et "partie de" (une + relation entre deux classes). + search_keywords_placeholder: Entrez un terme, e.g., leaf area + narrow_search_to: Réduire la recherche à + obsolete_classes: Classes obsolètes + ontologies: Ontologies + ontology_views: Vues d'ontologie + property_values: Valeurs de propriété + show_advanced_options: Afficher les options avancées + title: Rechercher + view_search_documentation: Voir la documentation de recherche + select_from_list: Sélectionner dans la liste + select_ontologies: Commencez à taper pour sélectionner des ontologies ou laissez vide pour toutes les utiliser + select_ontologies_list: Sélectionnez les ontologies + show_advanced_options: Afficher les options avancées + specialization: Spécialisation + temporality: Temporalité + text: Texte + type: Type + umls_sem_type: Type Semantique UMLS + view_fair_scores_definitions: Voir les définition des scores de FAIRness + visits: Visites + weights_configuration: Configuration des poids diff --git a/config/locales/it.yml b/config/locales/it.yml new file mode 100644 index 000000000..107542219 --- /dev/null +++ b/config/locales/it.yml @@ -0,0 +1,402 @@ +--- +it: + activaterecord: + errors: + models: + license: + attributes: + encrypted_key: + invalid_license_key: is an invalid license key + no_appliance_id_for_comparison: Could not be validated. Unable to retrieve virtual appliance ID. + appliance_id_mismatch: is an appliance id mismatch + + date: + 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" # March 1, 2017 + + additional_parameters: Parametri aggiuntivi spiegati in + admin: + licenses: + create: + success: Licenza rinnovata con successo! + all: Tutti + annotator: + annotate_text_prompt: Inserire o incollare il testo da annotare + annotations_result: Annotazioni + fast_context: Contesto veloce + filters: + confidence_threshold: Soglia di confidenza del filtro + confidence_threshold_help: Specificare la posizione minima nella distribuzione + dei punteggi (tra 1 e 100) + exclude_numbers: Escludere i numeri + exclude_synonyms: Escludere sinonimi + include_mappings: Includere le mappature + match_longest_only: Solo la partita più lunga + match_partial_words: Riconoscere parole parziali + max_hierarchy_level: Includere gli antenati fino al livello + score: Includere il punteggio + score_help: Punteggio delle annotazioni secondo la precedente misura NCBO 2009 + (old) o Punteggio delle annotazioni secondo la misura C-Value (cvalue) o Punteggio + delle annotazioni secondo la misura C-Value con espansione gerarchica (cvalueh) + score_threshold: Filtrare per soglia di punteggio + score_threshold_help: Specificare il valore minimo del punteggio per le annotazioni + get_annotator: Ottenere le annotazioni + index: + fast_context: + tooltip: 'Abilita FastContext a rilevare: se un concetto è stato negato (affermato, + negato), chi ha sperimentato il concetto trovato (paziente, altro), quando + si è verificato il concetto annotato (recente, storico, ipotetico), e/o + se il concetto annotato è incerto (certo, incerto).' + intro: Ottenere annotazioni per un testo biomedico con classi ontologiche + lemmatize: + tooltip: Abilita Lemmatize per lemmatizzare il testo inviato e utilizzare + un dizionario lemmatizzato per le annotazioni + sample_text: Il melanoma è un tumore maligno dei melanociti che si trova principalmente + nella pelle, ma anche nell'intestino e nell'occhio. + lemmatize: Lemmatizzare + results_filtered_by: I risultati sono filtrati da + select: Selezionare %{name} + start_typing_to_select: Iniziare a digitare per selezionare %{type} o lasciare + in bianco per utilizzare tutti i dati + title: Annotatore + umls: + semantic_groups: Gruppi semantici UMLS + semantic_types: Tipi semantici UMLS + certainty: Certezza + class: Classe + clear_selection: Azzeramento della selezione + concepts: + request_term: + new_term_instructions: | +

    Questa ontologia si integra con OntoloBridge, permettendo agli utenti della comunità di suggerire aggiunte all'ontologia pubblica. Compilare il modello sottostante per inviare una richiesta di termine direttamente al gestore dell'ontologia.

    +
    +

    Etichetta del termine (obbligatorio)
    Nome del termine suggerito. Se un termine può essere descritto con più di un sinonimo, inserire qui solo il nome preferito.

    +
    +
    +

    Descrizione del termine (obbligatorio)
    Breve definizione, descrizione o uso del termine suggerito. In questa sezione si possono elencare i sinonimi di altri termini.

    +
    +
    +

    Superclasse (obbligatorio)
    Il termine padre del termine suggerito. Il termine genitore deve essere una voce esistente nell'ontologia corrente. La superclasse può essere selezionata direttamente dall'albero delle classi del Bioportale.

    +
    +
    +

    Riferimenti (facoltativo)
    Fornire prove dell'esistenza del termine richiesto, come ID Pubmed di articoli o link ad altre risorse che descrivono il termine.

    +
    +
    +

    Giustificazione (opzionale)
    Fornisci qui qualsiasi informazione aggiuntiva sul termine richiesto.

    +
    + context: Contesto + coverage: Copertura + filter: Filtro + format_results: Formatta i risultati come + get_json_version: Ottenere la versione json + get_recommendations: Raccomandazioni + help: Aiuto + home: + agroportal_figures: "%{site} in cifre:" + benefit1: Scoprite nuove intuizioni e connessioni esplorando le altre ontologie + presenti nel repository. + benefit2: Contribuite alla crescita e allo sviluppo del vostro dominio aggiungendo + nuovi concetti e categorie. + benefit3: Utilizzate il controllo di versione per gestire le modifiche alla vostra + ontologia nel tempo e collaborare con altri utenti. + benefit4: Ottenere feedback e suggerimenti da altri utenti che possono rivedere + e commentare la vostra ontologia. + benefit5: Ottenere il punteggio e le metriche FAIR per l'ontologia. + fair_details: Vedi dettagli + fairness: Punteggi FAIR + get_annotations: Ottenere le annotazioni + get_recommendations: Raccomandazioni + index: + tagline: La casa dei vocabolari e delle ontologie in agronomia e nei campi correlati. + title: Benvenuti alla %{site} + welcome: Benvenuti al %{site}, + ontology_upload: Volete caricare un'ontologia? + ontology_upload_benefits: 'Caricando la propria ontologia su %{site}, è possibile:' + ontology_upload_button: Caricare l'ontologia + ontology_upload_desc: Caricare un'ontologia è un modo per condividere la propria + conoscenza del dominio con altri. + paste_text_prompt: Incollare un paragrafo di testo o alcune parole chiave ... + recommender_annotator: Raccomandatore e annotatore + supported_by: Supportato da + twitter_news: Notizie + with_collaboration: Con la collaborazione di + input: Ingresso + insert_sample_text: Inserire un testo di esempio + keywords: Parole chiave + keywords_separated_by_commas: Parole chiave separate da virgole + knowledge_detail: Dettaglio della conoscenza + landscape: + average_metrics: Metriche medie + category: Categoria + filter_network: Rete di filtraggio + funding_endorsing_organizations: Organizzazioni che finanziano e sostengono il + maggior numero di ontologie + group: Gruppo + groups_and_categories: Gruppi e categorie + intro: Visualizzare i dati recuperati dalle ontologie memorizzate nel portale + more_properties_charts: Altri grafici delle proprietà + most_active_ontologies: Le ontologie più attive + most_active_organizations: Le organizzazioni più attive + most_active_people: Le persone più attive + most_active_people_as_reviewer: Le persone più attive come recensori + most_mentioned_people: Le persone più citate come contatto, creatore, collaboratore + o curatore + most_mentioned_people_as_reviewer: Persone che hanno pubblicato note, recensioni + e progetti + ontologies_activity_on: Attività ontologica su %{site} + ontologies_by: Ontologie per %{type} + ontologies_contributors: Contribuenti allo sviluppo dell'ontologia + ontologies_count_by_catalog: Numero di ontologie in ogni catalogo di dati + ontologies_formats: Formato utilizzato + ontologies_languages: Linguaggi naturali delle ontologie + ontologies_licenses: Licenze utilizzate dalle ontologie + ontologies_with_notes_reviews_projects: Ontologie con note, recensioni e progetti + ontology_fairness_evaluator: Valutatore di ontologia FAIR (O'FAIRe) + ontology_formality_levels: Livelli di formalità delle ontologie + ontology_properties_pie_charts: Grafici a torta per le proprietà utilizzate nelle + ontologie + ontology_relations_network: Rete di relazioni ontologiche + ontology_tools: Strumenti più utilizzati per costruire ontologie + owl_ontology_author_uris: URI per le proprietà dell'autore utilizzate per le ontologie + OWL + owl_ontology_definition_uris: URI per le proprietà di definizione utilizzate per + le ontologie OWL + owl_ontology_preflabel_uris: URI per le proprietà prefLabel utilizzate per le + ontologie OWL + owl_ontology_synonym_uris: URI per le proprietà dei sinonimi utilizzati per le + ontologie OWL + properties_usage_proportion: La percentuale di utilizzo delle proprietà tra le + ontologie memorizzate + properties_use: Uso della proprietà + relations_between_stored_ontologies: Relazioni tra le ontologie memorizzate nel + portale + size: Dimensione + title: "%{site} Paesaggio" + layout: + header: + account_setting: Impostazioni dell'account + annotator: Annotatore + browse: Sfogliare + documentation: Documentazione + help: Aiuto + landscape: Paesaggio + login: Accesso + logout: logout + mappings: Mappature + publications: Pubblicazioni + release_notes: Note di rilascio + search_prompt: Cerca in %{portal_name} ... + submit_feedback: Invia un feedback + support: supporto + login: + enter_email: Inserisci il tuo indirizzo e-mail + enter_password: Inserire la password + forgot_password: Hai dimenticato la password? + invalid_login: Errori nel modulo + no_account: Non avete un account? + password: Password + register: Registro + title: Accesso + username_or_email: Nome utente o e-mail + mappings: + find_mappings: Trovare le mappature di una classe/concetto + intro: Sfogliare le mappature tra classi di diverse ontologie + loading_mappings: Caricamento delle mappature... + mappings_bulk_load: Mappatura del carico massivo + no_mappings_available: Nessuna mappatura disponibile + title: Corrispondenze + upload_mappings: Caricare le mappature + matched_class: Classe abbinata + matched_ontology: ontologia abbinata + max_ontologies_per_set: Numero massimo di ontologie per set + nbco_annotatosplus: + annotations: Annotazioni + enter_paste_text_to_annotate: 'Inserire o incollare il testo da annotare:' + exclude_numbers: Escludere i numeri + exclude_synonyms: Escludere sinonimi + fast_context: + help: 'Attivare FastContext per rilevare: se un concetto è stato negato (affermato, + negato), chi ha sperimentato il concetto trovato (paziente, altro), quando + si è verificato il concetto annotato (recente, storico, ipotetico), e/o se + il concetto annotato è incerto (certo, incerto).' + title: Contesto veloce + filters: + additional_parameters_explained_at: 'Ulteriori parametri sono spiegati nella + pagina:' + by: + certainty: Certezza + class: Classe + experiencer: Esperto + filter: Filtro + match_context: Contesto + match_type: Tipo + matched_class: Classe associata + matched_ontology: Ontologia associata + negation: Negazione + ontology: Ontologia + score: Punteggio + temporality: Temporalità + title: I risultati sono filtrati da + umls_sem_type: Tipo semantico UMLS + confidence_threshold: Soglia di fiducia del filtro + confidence_threshold_help: Specificare la posizione minima nella distribuzione + dei punteggi (tra 1 e 100) + format_results_as: 'Formattare i risultati come:' + reproduce_results_using: Riprodurre questi risultati utilizzando il metodo + score_threshold: Filtrare per soglia di punteggio + score_threshold_help: Specificare il valore minimo del punteggio per le annotazioni + include_ancestors_up_to_level: Includere gli antenati fino al livello + include_mappings: Includere le mappature + include_score: Includere il punteggio + index: + intro: | + NCBO Annotator+ è un proxy che chiama il servizio web NCBO Annotator sul BioPortale NCBO. +

    + Tchechmedjiev, A., Abdaoui, A., Emonet, V., Melzi, S., Jonnagaddala, J., & Jonquet, C. (2018). Funzioni migliorate per l'annotazione e l'indicizzazione di testi clinici con NCBO Annotator+. Bioinformatica, 34(11), 1962-1965. +

    + Se si utilizza l'API, fornire una chiave API NCBO BioPortal valida e accedere al servizio all'indirizzo http://services.bioportal.lirmm. en/ncbo_annotatorplus
    + Il testo inviato a NCBO Annotator+ deve essere in inglese. + sample_text: Il melanoma è un tumore maligno dei melanociti che si trova principalmente + nella pelle, ma anche nell'intestino e nell'occhio. + title: Annotatore NCBO + + insert_sample_text: Inserire un testo di esempio + match_longest_only: Solo la partita più lunga + match_partial_words: Abbinare parole parziali + recognizer: Riconoscimento dell'entità + score_help: Punteggio delle annotazioni secondo la precedente misura NCBO 2009 + (old) o Punteggio delle annotazioni secondo la misura C-Value (cvalue) o Punteggio + delle annotazioni secondo la misura C-Value con espansione gerarchica (cvalueh) + select: Selezionare %{name} + select_ontologies: Iniziare a digitare per selezionare le ontologie o lasciare + vuoto per utilizzare tutte le ontologie + select_ontologies_list: Selezionare le ontologie + show_advanced_options: Mostra le opzioni avanzate + start_typing_to_select: Iniziare a digitare per selezionare %{type} o lasciare + vuoto per usare tutti + umls: + semantic_groups: Gruppi semantici UMLS + semantic_types: Tipi semantici UMLS + negation: negazione + none: nessuno + ontologies: + ontology_search_prompt: 'Ricerca di un''ontologia o di un concetto (es.: Agrovoc + ...)' + self: Ontologie + ontology: Ontologia + ontology_details: + concept: + definitions: Definizioni + id: ID + in_schemes: In Schemi + member_of: Membro di + no_preferred_name_for_selected_language: Nessun nome preferito per la lingua + selezionata. + obsolete: Obsoleto + preferred_name: Nome preferito + synonyms: Sinonimi + type: Tipo + metadata: + additional_metadata: Metadati aggiuntivi + header: + last_submission_date: Data dell'ultimo invio + + ontology_sets: Insiemi di ontologie + output: Uscita + projects: + contacts: Contatti + create_new_project: Creare un nuovo progetto + created: Creato + creator: Utente + delete_admin_only: Elimina (solo per l'amministratore) + delete_confirm: Sei sicuro? + description: Descrizione + description_text: Descrizione Testo + edit: Modifica + home_page: Pagina iniziale + index: + intro: Sfoglia una selezione di progetti che utilizzano risorse %{site} + institutions: Istituzioni + ontologies: Ontologie + project_description: Descrizione del progetto + self: Progetti + title: Elenco dei progetti + view_projects_help: Visualizza i progetti Aiuto + recommender: + intro: Ottenere raccomandazioni per le ontologie più rilevanti da un estratto + di un testo biomedico o da un elenco di parole chiave + no_recommendations: Nessuna raccomandazione trovata + no_sets_recommended: Non ci sono set di ontologie consigliati per l'input fornito. + Provare con l'output "Ontologie". + ontology_recommendation_input: Incollare un paragrafo di testo o alcune parole + chiave per calcolare le raccomandazioni dell'ontologia. + ontology_recommender: Raccomandatore di ontologia + paste_text_recommendations: Incollare un paragrafo di testo o alcune parole chiave + da utilizzare per calcolare le raccomandazioni dell'ontologia + recommendation_error: Problema nel recupero delle raccomandazioni, provare di + nuovo + text_length_limit: Si prega di utilizzare meno di 500 parole. Se avete bisogno + di annotare testi più lunghi, potete utilizzare il servizio web di raccomandazione. + title: raccomandatore + valid_integer_max_ontologies_per_set: Il numero massimo di ontologie per set deve + essere un valore intero valido + valid_max_ontologies_per_set_range: Il numero massimo di ontologie per set deve + essere un numero compreso tra 2 e 4 + valid_numeric_weights: Tutti i pesi devono essere valori numerici validi + weight_sum_greater_than_zero: La somma dei pesi deve essere maggiore di zero + weights_greater_than_zero: Tutti i pesi devono essere maggiori o uguali a zero + register: + account_errors: 'Errori nella creazione dell''account:' + confirm_password: Confermare la password + create_account: Creare un nuovo account + email: Email + first_name: Nome + last_name: Cognome + mailing_list: Registrarsi alla mailing list di AgroPortal + optional: "(Opzionale)" + password: Password + title: Registro + username: Nome utente + reproduce_results: Riprodurre questi risultati utilizzando il metodo + score: Punteggio + search: + categories: Categorie + class_search: Ricerca di classe + classes_with_definitions: Classi con definizioni + hide_advanced_options: Nascondere le opzioni avanzate + include_in_search: Includere nella ricerca + index: + categories_placeholder: Iniziare a digitare per selezionare le categorie o lasciare + vuoto per utilizzare tutte le categorie + obsolete_definition: 'Una classe che gli autori dell''ontologia hanno segnalato + come obsoleta e che raccomandano di non usare. Spesso queste classi vengono + lasciate nelle ontologie (invece di essere eliminate del tutto) in modo che + i sistemi esistenti che dipendono da esse continuino a funzionare" + + ' + property_definition: Un'associazione nominativa tra due entità. Esempi sono + "definizione" (una relazione tra una classe e un testo) e "parte di" (una + relazione tra due classi). + search_keywords_placeholder: Inserire una classe, ad es. Melanoma + narrow_search_to: Restringere la ricerca a + obsolete_classes: Classi obsolete + ontologies: Ontologie + ontology_views: Viste ontologiche + property_values: Valori della proprietà + show_advanced_options: Mostra opzioni avanzate + title: Ricerca + view_search_documentation: Visualizza la documentazione della ricerca + select_from_list: Selezionare dall'elenco + select_ontologies: Iniziare a digitare per selezionare le ontologie o lasciare vuoto + per utilizzarle tutte + select_ontologies_list: Selezionare le ontologie + show_advanced_options: Mostra le opzioni avanzate + specialization: Specializzazione + temporality: Temporalità + text: Testo + type: Tipo + umls_sem_type: Tipo UMLS Sem + view_fair_scores_definitions: Visualizza le definizioni dei punteggi equi + visits: Visite + weights_configuration: Configurazione dei pesi diff --git a/config/routes.rb b/config/routes.rb index 56d1d4f52..959f25f40 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,13 +1,21 @@ Rails.application.routes.draw do root to: 'home#index' + mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? + + get 'auth/:provider/callback', to: 'login#create_omniauth' + get 'locale/:language', to: 'language#set_locale_language' get '/notes/new_comment', to: 'notes#new_comment' get '/notes/new_proposal', to: 'notes#new_proposal' get '/notes/new_reply', to: 'notes#new_reply' delete '/notes', to: 'notes#destroy' resources :notes, constraints: { id: /.+/ } - + get 'agents/show_search', to: 'agents#show_search' + get 'agents/:id/usages', to: 'agents#agent_usages', constraints: { id: /.+/ } + 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 @@ -38,10 +46,15 @@ get 'ontologies/:ontology_id/concepts', to: 'concepts#show_concept' resources :ontologies do - resources :submissions + 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' end resources :login @@ -83,10 +96,9 @@ # Top-level pages match '/feedback', to: 'home#feedback', via: [:get, :post] get '/account' => 'home#account' - get '/help' => 'home#help' - get '/about' => 'home#about' get '/site_config' => 'home#site_config' get '/validate_ontology_file' => 'home#validate_ontology_file_show' + post '/annotator_recommender_form' => 'home#annotator_recommender_form' match '/validate_ontology_file' => 'home#validate_ontology_file', via: [:get, :post] get '/layout_partial/:partial' => 'home#render_layout_partial' match '/visits', to: 'visits#index', via: :get @@ -117,6 +129,8 @@ 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' + # Analytics get '/analytics/:action' => 'analytics#(?-mix:search_result_clicked|user_intention_surveys)' @@ -147,11 +161,14 @@ get 'ajax/label_xl', to: "label_xl#show" get '/ajax/biomixer' => 'concepts#biomixer' get '/ajax/fair_score/html' => 'fair_score#details_html' + get '/ajax/submission/show_additional_metadata/:id' => 'ontologies#show_additional_metadata' + 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' # User get '/logout' => 'login#destroy', :as => :logout get '/lost_pass' => 'login#lost_password' diff --git a/config/settings.yml b/config/settings.yml index 0a8d2dbbd..7b5a9422a 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -21,6 +21,7 @@ shared: widgets: 'http://www.bioontology.org/wiki/NCBO_Widgets' wiki: 'https://www.bioontology.org/wiki/' mappings: 'https://www.bioontology.org/wiki/BioPortal_Help#Mappings_Tab' + metadata_help: 'https://doc.jonquetlab.lirmm.fr/share/32c082ff-97f5-45f4-bafe-05f41c9a5ce4' development: links: diff --git a/db/schema.rb b/db/schema.rb index 51716810e..22dc2b318 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema[7.0].define(version: 2020_09_21_120918) do - create_table "analytics", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "analytics", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "segment" t.string "action" t.string "bp_slice" @@ -22,13 +22,13 @@ t.datetime "updated_at", precision: nil end - create_table "licenses", charset: "utf8mb3", force: :cascade do |t| + create_table "licenses", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.text "encrypted_key" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "ontologies", charset: "utf8mb3", force: :cascade do |t| + create_table "ontologies", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "acronym", null: false t.text "new_term_instructions" t.text "custom_message" @@ -37,7 +37,7 @@ t.index ["acronym"], name: "index_ontologies_on_acronym", unique: true end - create_table "timeouts", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "timeouts", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "path" t.integer "ontology_id" t.text "concept_id" @@ -45,7 +45,7 @@ t.timestamp "created" end - create_table "virtual_appliance_users", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "virtual_appliance_users", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "user_id" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil diff --git a/docker-compose.yml b/docker-compose.yml index 38be0278f..1eb928b19 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,52 +1,86 @@ x-app: &default-app - build: - context: "." - target: "app" - args: - - "UID=${UID:-1000}" - - "GID=${GID:-1000}" - - "RAILS_ENV=${RAILS_ENV:-production}" - - "NODE_ENV=${NODE_ENV:-production}" - depends_on: - - "db" - - "cache" - links: - - "db" - - "cache" + image: agroportal/ontoportal_web_ui:development env_file: - ".env" tty: true volumes: - - "${DOCKER_WEB_VOLUME}" - - bundle:/usr/local/bundle + - .:/app + - bundle:/usr/local/bundle - node:/node_modules + - rails_cache:/app/tmp/cache + - assets:/app/public/assets + - /var/run/docker.sock:/var/run/docker.sock + tmpfs: + - /tmp + - /app/tmp/pids services: db: - environment: - MYSQL_ROOT_PASSWORD: 'bp_user' - image: "mysql:latest" - profiles: ["db"] + image: "mysql:8.0" + networks: + - default + volumes: + - mysql-data:/var/lib/mysql ports: - "3306:3306" - volumes: - - db-v:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: root + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + timeout: 5s + retries: 3 + cache: - deploy: - resources: - limits: - cpus: "${DOCKER_REDIS_CPUS:-0}" - memory: "${DOCKER_REDIS_MEMORY:-0}" - image: "memcached:1.6.17-bullseye" - profiles: ["cache"] + image: memcached:latest + restart: unless-stopped + command: [ "-m", "1024" ] + networks: + - default + ports: + - "11211:11211" + node: + <<: *default-app + command: "yarn build --watch" - web: + rails: <<: *default-app + 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: - - "${DOCKER_WEB_PORT_FORWARD:-127.0.0.1:3000}:${PORT:-3000}" - profiles: ["web"] - command: "yarn build --watch" + - "3000:3000" + test: + <<: *default-app + depends_on: + - db + - cache + - chrome-server + network_mode: 'host' + environment: + 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 + ports: + - "4444:4444" + - "7900:7900" + volumes: - db-v: + mysql-data: bundle: + rails_cache: + assets: node: diff --git a/lib/log.rb b/lib/log.rb index 00c815d60..edef5506f 100644 --- a/lib/log.rb +++ b/lib/log.rb @@ -12,13 +12,7 @@ class LOG # :concept_id # :concept_name def self.add(level, message, request = nil, remote_params = nil) - if request - if !$REMOTE_LOGGING.nil? && $REMOTE_LOGGING - remote(level, message, request, remote_params) - end - else - local(level, message) - end + local(level, message) end private diff --git a/package.json b/package.json index 94282d913..d5003c910 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,20 @@ "dependencies": { "@hotwired/stimulus": "^3.0.1", "@hotwired/turbo-rails": "^7.1.1", + "debounce": "^1.2.1", "esbuild": "^0.14.41", "flatpickr": "^4.6.13", "split.js": "^1.6.5", "stimulus-flatpickr": "^3.0.0-0", "stimulus-rails-nested-form": "^4.0.0", "stimulus-read-more": "^4.1.0", - "tom-select": "^2.2.2" + "stimulus-reveal-controller": "^4.1.0", + "stimulus-timeago": "^4.1.0", + "tippy.js": "^6.3.7", + "tom-select": "^2.2.2", + "vis-data": "^7.1.6", + "vis-network": "^9.1.6", + "vis-util": "^5.0.3" }, "scripts": { "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds" diff --git a/spec/mailers/notifier_spec.rb b/spec/mailers/notifier_spec.rb new file mode 100644 index 000000000..4c8edc5df --- /dev/null +++ b/spec/mailers/notifier_spec.rb @@ -0,0 +1,35 @@ +require 'rails_helper' + +RSpec.describe Notifier, type: :mailer do + let(:support_email) { 'agroportal-support@lirmm.fr' } + let(:site) { 'Agroportal' } + + describe 'error' do + let(:error) { StandardError.new('Test error message') } + subject(:error_mail) { Notifier.error(error) } + + it 'sends an error email' do + expect(error_mail.to).to eq([support_email]) + expect(error_mail.from).to eq([support_email]) + expect(error_mail.subject).to eq("[#{site}] Exception Mailer: #{error.message}") + expect(error_mail.body.encoded).to include(error.backtrace.join("\n")) + end + end + + describe 'feedback' do + let(:name) { 'John Doe' } + let(:email) { 'user@lirmm.fr' } + let(:comment) { 'This is a test comment.' } + let(:location) { 'Test Location' } + let(:tags) { 'tag1, tag2' } + + subject(:feedback_mail) { Notifier.feedback(name, email, comment, location, tags) } + + it 'sends a feedback email' do + expect(feedback_mail.to).to eq(["#{support_email}, #{email}"]) + expect(feedback_mail.from).to eq([support_email]) + expect(feedback_mail.subject).to eq("[#{site}] Feedback from #{name}") + expect(feedback_mail.body.encoded).to include(name, email, comment, location, tags) + end + end +end diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb new file mode 100644 index 000000000..b4457f965 --- /dev/null +++ b/test/application_system_test_case.rb @@ -0,0 +1,196 @@ +require "test_helper" +require_relative 'helpers/application_test_helpers' + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + include ApplicationTestHelpers::Ontologies + include ApplicationTestHelpers::Users + include ApplicationTestHelpers::Users + include ApplicationTestHelpers::Categories + include ApplicationTestHelpers::Groups + include ApplicationTestHelpers::Agents + + driven_by :selenium, using: ENV['CI'].present? ? :headless_chrome : :chrome, screen_size: [1400, 1400], options: { + browser: :remote, + url: "http://localhost:4444" + } + + def wait_for(selector, tries = 60) + tries.times.each do + puts "waiting for #{selector}" + break if page.has_selector?(selector) + sleep 1 + end + end + + def wait_for_text(text, tries = 60) + tries.times.each do + sleep 1 + puts "waiting for #{text}" + break if page.has_text?(text) + end + assert_text text + end + + def login_in_as(user, admin: false) + create_user(user, admin: admin) + + visit login_index_url + + # Fill in the login form + fill_in 'user_username', with: user.username + fill_in 'user_password', with: user.password + + # Click the login button + click_button 'Login' + end + + def assert_date(date) + assert_text I18n.l(DateTime.parse(date), format: '%B %-d, %Y') + end + + def search_input(selector, value) + within "#{selector}" do + find(".search-inputs .input-field-component").last.set(value) + page.execute_script("document.querySelector('#{selector} > .search-inputs .input-field-component').dispatchEvent(new Event('input'))") + sleep 1 + find(".search-inputs .search-content", text: value).click + sleep 1 + find("input", text: 'Save').click + end + end + + def list_checks(selected_values, all_values = []) + all_values.each do |val| + uncheck val, allow_label_click: true + end + + selected_values.each do |val| + check val, allow_label_click: true + end + end + + def list_inputs(parent_selector, selector, values, &block) + within parent_selector do + all('.delete').each { |x| x.click } + find('.add-another-object', text: 'Add another').click + last_index = values.size - 1 + values.each_with_index do |value, index| + if value.is_a?(Hash) + value.each do |key, val| + all("[name^='#{selector}'][name$='[#{key}]']").last.set(val) + end + else + if block_given? + block.call(selector, value, index) + else + all("[name^='#{selector}']").last.set(value) + end + end + find('.add-another-object', text: 'Add another').click unless index.eql?(last_index) + end + end + end + + def tom_select(selector, values, open_to_add: false) + + multiple = values.is_a?(Array) + + real_select = "[name='#{selector}']" + + ts_wrapper_selector = "#{real_select} + div.ts-wrapper" + assert_selector ts_wrapper_selector + + # Click on the Tom Select input to open the dropdown + element = find(ts_wrapper_selector) + element.click + + + return unless page.has_selector?("#{ts_wrapper_selector} > .ts-dropdown") + + if multiple + # reset the input to empty + all("#{ts_wrapper_selector} > .ts-control > .item .remove").each do |element| + element.click + end + else + values = Array(values) + end + + values.each do |value| + find("#{ts_wrapper_selector} input").set(value) if open_to_add + within "#{ts_wrapper_selector} > .ts-dropdown" do + if page.has_selector?('.option', text: value) + find('.option', text: value).click + elsif open_to_add + find('.create').click + end + end + end + + if multiple + find("#{ts_wrapper_selector} input").click + end + end + + def date_picker_fill_in(selector, value, index = 0) + page.execute_script("document.querySelectorAll(\"[name^='#{selector}']\")[#{index}].flatpickr().setDate('#{value}')") + end + + def agent_search(name) + within(".search-inputs:last-of-type") do + input = find("input[name^='agent']") + agent_id = input[:name].split('agent').last + input.set(name) + sleep 2 + links = all('a', text: name) + links_size = links.size + sleep 1 + first(:link, name).click + return links_size.eql?(1) ? agent_id : nil + end + + end + + def agent_fill(agent, parent_id: nil, enable_affiliations: true) + id = agent.id ? "/#{agent.id}": '' + form = all("form[action=\"/agents#{id}\"]").first + within form do + choose "", option: agent.agentType, allow_label_click: true if enable_affiliations + fill_in 'name', with: agent.name + + if agent.agentType.eql?('organization') + refute_selector('input[name="email"]') + fill_in 'acronym', with: agent.acronym + fill_in 'homepage', with: agent.homepage + else + refute_selector('input[name="acronym"]') + refute_selector('input[name="homepage"]') + fill_in 'email', with: agent.email + end + + list_inputs ".agents-identifiers", + "[identifiers]", agent.identifiers + + unless enable_affiliations + refute_selector ".agents-affiliations" + return + end + + within '.agents-affiliations' do + all('.delete').each { |x| x.click } + Array(agent.affiliations).each do |aff| + aff = OpenStruct.new(aff) + find('.add-another-object', text: 'Add another').click + agent_id = agent_search(aff.name) + id = parent_id && !parent_id.eql?('NEW_RECORD') ? "#{parent_id}_#{agent_id}" : agent_id + within "turbo-frame[id=\"#{id}\"]" do + agent_fill(aff, enable_affiliations: false) + click_on "Save" + sleep 1 + end + end + end + click_on "Save" + end + end +end diff --git a/test/controllers/application_controller_test.rb b/test/controllers/application_controller_test.rb new file mode 100644 index 000000000..fe33ef551 --- /dev/null +++ b/test/controllers/application_controller_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ApplicationControllerTest < ActionDispatch::IntegrationTest + test 'should show home page' do + get '' + assert_response :success + end + + test 'should show projects page' do + get '/projects' + assert_response :success + end + + test 'should show annotator page' do + get '/annotator' + assert_response :success + end + + test 'should show recommender page' do + get '/recommender' + assert_response :success + end + + test 'should show mapping page' do + get '/mappings' + assert_response :success + end + + test 'should show feedback page' do + get '/feedback' + assert_response :success + end +end diff --git a/test/controllers/landscape_controller_test.rb b/test/controllers/landscape_controller_test.rb index 31a32b842..619c82dc9 100644 --- a/test/controllers/landscape_controller_test.rb +++ b/test/controllers/landscape_controller_test.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'test_helper' class LandscapeControllerTest < ActionController::TestCase - test "should get index" do + test 'should get index' do + skip('take too much time') get :index assert_response :success end - end diff --git a/test/controllers/ontologies_controller_test.rb b/test/controllers/ontologies_controller_test.rb new file mode 100644 index 000000000..b5998de7a --- /dev/null +++ b/test/controllers/ontologies_controller_test.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'test_helper' + +class OntologiesControllerTest < ActionDispatch::IntegrationTest + ONTOLOGIES = LinkedData::Client::Models::Ontology.all(include: 'acronym') + PAGES = %w[summary classes properties notes mappings schemes collections widgets].freeze + + test 'should return all the ontologies' do + get ontologies_path + assert_response :success + end + + ONTOLOGIES.flat_map { |ont| PAGES.map { |page| [ont, page] } }.each do |ont, page| + test "should get page #{page} of #{ont.acronym} ontology" do + path = "#{ontologies_path}/#{ont.acronym}?p=#{page}" + get path + if response.redirect? + follow_redirect! + end + assert_response :success, "GET #{path} returned #{response.status}" + end + end +end diff --git a/test/fixtures/agents.yml b/test/fixtures/agents.yml new file mode 100644 index 000000000..ce32845e0 --- /dev/null +++ b/test/fixtures/agents.yml @@ -0,0 +1,173 @@ +agent1: + name: "John Doe" + agentType: "person" + email: "jonh@test.com" + identifiers: + - notation: "0000-0001-2345-6789" + - notation: "0000-0001-2345-6788" + - notation: "0000-0001-2345-6788" + affiliations: + - name: "Research Institute X" + agentType: "organization" + identifiers: + - notation: "1234-5678" + affiliations: [ ] + email: "x@example.com" + acronym: "RI-X" + homepage: "https://www.rix-institute.org/" + + - name: "Research Institute Y" + agentType: "organization" + identifiers: + - notation: "1234-5679" + affiliations: [ ] + email: "y@example.com" + acronym: "RI-Y" + homepage: "https://www.riy-institute.org/" + +agent2: + name: "Alice Smith" + agentType: "person" + identifiers: + - notation: "0000-0002-9876-5432" + affiliations: + - name: "Tech Innovators Ltd." + agentType: "organization" + identifiers: + - notation: "9876-5432" + affiliations: [ ] + email: "alice.smith@example.com" + acronym: "TI Ltd." + homepage: "https://www.techinnovators.com/" + +agent3: + name: "Robert Johnson" + agentType: "person" + identifiers: + - schemaAgency: "ORCID" + notation: "0000-0003-4567-8901" + affiliations: + - name: "Science Hub Inc." + agentType: "organization" + identifiers: + - schemaAgency: "ROR" + notation: "3456-7890" + affiliations: [ ] + email: "robert.johnson@example.com" + acronym: "SH Inc." + homepage: "https://www.sciencehubinc.com/" + +agent4: + name: "Emily Brown" + agentType: "person" + identifiers: + - schemaAgency: "ORCID" + notation: "0000-0004-5678-9012" + affiliations: + - name: "Health Solutions Co." + agentType: "organization" + identifiers: + - schemaAgency: "ROR" + notation: "5678-9012" + affiliations: [ ] + email: "emily.brown@example.com" + acronym: "HSC" + homepage: "https://www.healthsolutionsco.com/" + +agent5: + name: "Michael Taylor" + agentType: "person" + identifiers: + - schemaAgency: "ORCID" + notation: "0000-0005-6789-0123" + affiliations: + - name: "Environmental Research Foundation" + creator: + agentType: "organization" + identifiers: + - schemaAgency: "ROR" + notation: "6789-0123" + affiliations: [ ] + email: "michael.taylor@example.com" + acronym: "ERF" + homepage: "https://www.enviro-research.org/" + +organization1: + name: "Tech Solutions Group" + agentType: "organization" + identifiers: + - notation: "1234-5678" + affiliations: + - name: "Research Consortium A" + agentType: "organization" + identifiers: + - notation: "9876-5432" + affiliations: [ ] + email: "contact@researchconsortiumA.com" + acronym: "RCA" + homepage: "https://www.researchconsortiumA.com/" + + - name: "Innovation Hub B" + agentType: "organization" + identifiers: + - notation: "3456-7890" + affiliations: [ ] + email: "info@innovationhubb.com" + acronym: "IHB" + homepage: "https://www.innovationhubb.com/" + + email: "info@techsolutionsgroup.com" + acronym: "TSG" + homepage: "https://www.techsolutionsgroup.com/" + +organization2: + name: "Quantum Dynamics Solutions" + agentType: "organization" + identifiers: + - notation: "5678-9012" + affiliations: + - name: "Space Exploration Consortium" + agentType: "organization" + identifiers: + - notation: "1234-1234" + affiliations: [] + email: "contact@spaceconsortium.com" + acronym: "SEC" + homepage: "https://www.spaceconsortium.com/" + - name: "Tech Innovators Guild" + agentType: "organization" + identifiers: + - notation: "7890-1234" + affiliations: [] + email: "info@techinnovatorsguild.com" + acronym: "TIG" + homepage: "https://www.techinnovatorsguild.com/" + email: "info@quantumdynamics.com" + acronym: "QDS" + homepage: "https://www.quantumdynamics.com/" + +organization3: + name: "BioTech Solutions Inc." + agentType: "organization" + identifiers: + - notation: "2345-6789" + affiliations: + - name: "Health Research Alliance" + agentType: "organization" + identifiers: + - notation: "5678-9012" + affiliations: [] + email: "contact@healthresearchalliance.com" + acronym: "HRA" + homepage: "https://www.healthresearchalliance.com/" + - name: "GreenTech Innovations" + agentType: "organization" + identifiers: + - notation: "8901-2345" + affiliations: [] + email: "info@greentechinnovations.com" + acronym: "GTI" + homepage: "https://www.greentechinnovations.com/" + email: "info@biotechsolutions.com" + acronym: "BSI" + homepage: "https://www.biotechsolutions.com/" diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml new file mode 100644 index 000000000..e0669133d --- /dev/null +++ b/test/fixtures/categories.yml @@ -0,0 +1,29 @@ +category1: + acronym: "CAT1" + name: "Category One" + description: "This is the first category." + ontologies: + +category2: + acronym: "CAT2" + name: "Category Two" + description: "This is the second category." + ontologies: + +category3: + acronym: "CAT3" + name: "Category Three" + description: "This is the third category." + ontologies: + +category4: + acronym: "CAT4" + name: "Category Four" + description: "This is the fourth category." + ontologies: + +category5: + acronym: "CAT5" + name: "Category Five" + description: "This is the fifth category." + ontologies: \ No newline at end of file diff --git a/test/fixtures/groups.yml b/test/fixtures/groups.yml new file mode 100644 index 000000000..58e3ff91e --- /dev/null +++ b/test/fixtures/groups.yml @@ -0,0 +1,17 @@ +group1: + acronym: "GRP1" + name: "Group One" + description: "This is the first group." + ontologies: + +group2: + acronym: "GRP2" + name: "Group Two" + description: "This is the second group." + ontologies: + +group3: + acronym: "GRP3" + name: "Group Three" + description: "This is the third group." + ontologies: \ No newline at end of file diff --git a/test/fixtures/ontologies.yml b/test/fixtures/ontologies.yml new file mode 100644 index 000000000..28d345e73 --- /dev/null +++ b/test/fixtures/ontologies.yml @@ -0,0 +1,63 @@ +ontology1: + acronym: "ONT1" + name: "Ontology One" + notes: "This is the first ontology." + administeredBy: + group: + viewingRestriction: "public" + hasDomain: + summaryOnly: false + acl: + +ontology2: + acronym: "ONT2" + name: "Ontology Two" + notes: "This is the second ontology." + administeredBy: + group: + viewingRestriction: "private" + hasDomain: + acl: + +ontology3: + acronym: "ONT3" + name: "Ontology Three" + notes: "This is the third ontology." + administeredBy: + group: + viewingRestriction: "public" + doNotUpdate: false + hasDomain: + acl: + +ontology4: + acronym: "ONT4" + name: "Ontology Four" + notes: "This is the fourth ontology." + administeredBy: + group: + viewingRestriction: "public" + hasDomain: + summaryOnly: true + acl: + +ontology_private: + acronym: "ontology_private" + name: "Ontology private" + notes: "This is the fourth ontology." + administeredBy: + group: + viewingRestriction: "private" + hasDomain: + acl: + +ontology_view: + acronym: "ontology_view" + name: "View of ONT1" + notes: "This is a view ontology." + administeredBy: + group: + viewingRestriction: "public" + hasDomain: + acl: + viewOf: "ONT1" diff --git a/test/fixtures/submissions.yml b/test/fixtures/submissions.yml new file mode 100644 index 000000000..5d066008e --- /dev/null +++ b/test/fixtures/submissions.yml @@ -0,0 +1,315 @@ +submission1: + URI: "http://example.org/ontologies/ontology1" + versionIRI: "http://example.org/ontologies/ontology1/v1" + version: "1.0" + status: "production" + deprecated: false + hasOntologyLanguage: "OWL" + hasFormalityLevel: "http://example.org/formality#Informal" + hasOntologySyntax: "http://www.w3.org/2002/07/owl#FunctionalSyntax" + naturalLanguage: + - "http://lexvo.org/id/iso639-1/en" + isOfType: "http://example.org/ontologyType#DomainOntology" + identifier: + - "http://example.org/ontologies/ontology1/identifier1" + - "http://example.org/ontologies/ontology1/identifier2" + description: "An example ontology submission" + homepage: "http://example.org/ontologies/ontology1/homepage" + documentation: "http://example.org/ontologies/ontology1/documentation" + notes: + - "Note 1" + - "Note 2" + keywords: + - "Keyword1" + - "Keyword2" + hiddenLabel: + - "HiddenLabel1" + - "HiddenLabel2" + alternative: + - "Alternative1" + - "Alternative2" + abstract: "An abstract for the ontology" + publication: + - "http://example.org/publication1" + - "http://example.org/publication2" + hasLicense: "http://example.org/licenses/license1" + useGuidelines: "Guidelines for use" + morePermissions: "Additional permissions" + released: "2023-01-01T00:00:00Z" + valid: "2023-01-31T00:00:00Z" + curatedOn: + - "2023-01-15T12:00:00Z" + creationDate: "2023-01-01T00:00:00Z" + modificationDate: "2023-01-15T16:45:00Z" + contact: [ + {name: 'contact1' , email: "contact1@test.com"}, + {name: 'contact2' , email: "contact2@test.com"}, + ] + pullLocation: 'https://raw.githubusercontent.com/owlcs/pizza-ontology/master/pizza.owl' + +submission2: + URI: "http://example.org/ontologies/ontology2" + versionIRI: "http://example.org/ontologies/ontology2/v1" + version: "2.0" + status: "beta" + deprecated: true + hasOntologyLanguage: "SKOS" + hasFormalityLevel: "Ontology" + hasOntologySyntax: "OWL Manchester Syntax" + naturalLanguage: + - "French" + isOfType: "Application Ontology" + identifier: + - "http://example.org/ontologies/ontology2/identifier1" + - "http://example.org/ontologies/ontology2/identifier2" + description: "Another example ontology submission" + homepage: "http://example.org/ontologies/ontology2/homepage" + documentation: "http://example.org/ontologies/ontology2/documentation" + notes: + - "Note 3" + - "Note 4" + keywords: + - "Keyword3" + - "Keyword4" + hiddenLabel: + - "HiddenLabel3" + - "HiddenLabel4" + alternative: + - "Alternative3" + - "Alternative4" + abstract: "Abstract for the second ontology" + publication: + - "http://example.org/publication3" + - "http://example.org/publication4" + hasLicense: "http://example.org/licenses/license2" + useGuidelines: "Usage guidelines" + morePermissions: "More permissions" + released: "2023-02-01T00:00:00Z" + valid: "2023-02-28T00:00:00Z" + curatedOn: + - "2023-02-15T12:00:00Z" + - "2023-03-15T12:00:00Z" + creationDate: "2023-02-01T00:00:00Z" + modificationDate: "2023-02-15T16:45:00Z" + contact: [ + {name: 'contact3' , email: "contact3@test.com"}, + {name: 'contact4' , email: "contact4@test.com"}, + ] + pullLocation: 'https://www.w3.org/2009/08/skos-reference/skos.rdf' + source: + - "source 2.1" + - "source 2.2" + endpoint: + - "https://endpoint.2.1.com" + - "https://endpoint.2.2.com" + includedInDataCatalog: + - "AgroPortal" + - "MedPortal" + associatedMedia: + - "https://associatedMedia.2.1.com" + - "https://associatedMedia.2.2.com" + - "https://associatedMedia.2.3.com" + depiction: + - "https://depiction.2.1.com" + - "https://depiction.2.2.com" + logo: "https://logo.2.com" + audience: "audience 2" + repository: "https://repository.2.com" + bugDatabase: "https://bugDatabase.2.com" + mailingList: "mailingList2" + toDoList: + - 'todo2.1' + - 'todo2.2' + - 'todo2.3' + - 'todo2.4' + award: + - 'award2.1' + - 'award2.2' + knownUsage: + - 'usage2.1' + - 'usage2.2' + - 'usage2.3' + designedForOntologyTask: + - 'Search Task' + - 'Indexing Task' + hasDomain: + - "hasDomain2.1" + - "hasDomain2.2" + - "hasDomain2.3" + coverage: "coverage2" + example: + - "exmaple2.1" + - "exmaple2.2" + hasPriorVersion: "https://hasPriorVersion.2.com" + # hasPart: TODO: auto complete this programmatically + obsoleteParent: "https://obsoleteParent.2.com" + uriRegexPattern: "https://uriRegexPattern.2.com" + preferredNamespaceUri: "https://preferredNamespaceUri.2.com" + preferredNamespacePrefix: "prefix2" + exampleIdentifier: "https://exampleIdentifier.2.com" + keyClasses: + - "https://keyClasses.2.1.com" + - "https://keyClasses.2.2.com" + - "https://keyClasses.2.3.com" + metadataVoc: + - "RDF Schema (RDFS)" + - "Dublin core (DCTERMS)" + conformsToKnowledgeRepresentationParadigm: "conformsToKnowledgeRepresentationParadigm2" + usedOntologyEngineeringMethodology: "usedOntologyEngineeringMethodology2" + usedOntologyEngineeringTool: + - "Protégé" + - "SWOOP" + accrualMethod: + - "https://accrualMethod.2.1.com" + - "https://accrualMethod.2.2.com" + - "https://accrualMethod.2.3.com" + accrualPeriodicity: "Annual" + accrualPolicy: "accrualPolicy2" + + + + + +submission3: + URI: "http://example.org/ontologies/ontology3" + versionIRI: "http://example.org/ontologies/ontology3/v1" + version: "1.5" + status: "production" + deprecated: false + hasOntologyLanguage: "OWL" + hasFormalityLevel: "http://example.org/formality#Informal" + hasOntologySyntax: "http://www.w3.org/2002/07/owl#FunctionalSyntax" + naturalLanguage: + - "http://lexvo.org/id/iso639-1/en" + isOfType: "http://example.org/ontologyType#DomainOntology" + identifier: + - "http://example.org/ontologies/ontology3/identifier1" + - "http://example.org/ontologies/ontology3/identifier2" + description: "Yet another example ontology submission" + homepage: "http://example.org/ontologies/ontology3/homepage" + documentation: "http://example.org/ontologies/ontology3/documentation" + notes: + - "Note 5" + - "Note 6" + keywords: + - "Keyword5" + - "Keyword6" + hiddenLabel: + - "HiddenLabel5" + - "HiddenLabel6" + alternative: + - "Alternative5" + - "Alternative6" + abstract: "Abstract for the third ontology" + publication: + - "http://example.org/publication5" + - "http://example.org/publication6" + hasLicense: "http://example.org/licenses/license3" + useGuidelines: "Guidelines for use" + morePermissions: "Additional permissions" + released: "2023-03-01T00:00:00Z" + valid: "2023-03-31T00:00:00Z" + curatedOn: "2023-03-15T12:00:00Z" + creationDate: "2023-02-01T00:00:00Z" + modificationDate: "2023-02-15T16:45:00Z" + contact: [ + {name: 'contact5' , email: "contact5@test.com"}, + {name: 'contact6' , email: "contact6@test.com"}, + ] + pullLocation: 'http://example.org/pull3' + +submission4: + URI: "http://example.org/ontologies/ontology4" + versionIRI: "http://example.org/ontologies/ontology4/v1" + version: "1.2" + status: "alpha" + deprecated: true + hasOntologyLanguage: "SKOS" + hasFormalityLevel: "http://example.org/formality#Formal" + hasOntologySyntax: "http://www.w3.org/2002/07/owl#ManchesterSyntax" + naturalLanguage: + - "http://lexvo.org/id/iso639-1/fr" + isOfType: "http://example.org/ontologyType#KnowledgeBase" + identifier: + - "http://example.org/ontologies/ontology4/identifier1" + - "http://example.org/ontologies/ontology4/identifier2" + description: "A different example ontology submission" + homepage: "http://example.org/ontologies/ontology4/homepage" + documentation: "http://example.org/ontologies/ontology4/documentation" + notes: + - "Note 7" + - "Note 8" + keywords: + - "Keyword7" + - "Keyword8" + hiddenLabel: + - "HiddenLabel7" + - "HiddenLabel8" + alternative: + - "Alternative7" + - "Alternative8" + abstract: "Abstract for the fourth ontology" + publication: + - "http://example.org/publication7" + - "http://example.org/publication8" + hasLicense: "http://example.org/licenses/license4" + useGuidelines: "Usage guidelines 2" + morePermissions: "More permissions 2" + released: "2023-04-01T00:00:00Z" + valid: "2023-04-30T00:00:00Z" + curatedOn: "2023-04-15T12:00:00Z" + creationDate: "2023-04-01T00:00:00Z" + modificationDate: "2023-04-15T16:45:00Z" + contact: [ + {name: 'contact7' , email: "contact7@test.com"}, + {name: 'contact8' , email: "contact8@test.com"}, + ] + pullLocation: 'http://example.org/pull4' + +submission5: + URI: "http://example.org/ontologies/ontology5" + versionIRI: "http://example.org/ontologies/ontology5/v1" + version: "1.8" + status: "production" + deprecated: false + hasOntologyLanguage: "OWL" + hasFormalityLevel: "http://example.org/formality#Informal" + hasOntologySyntax: "http://www.w3.org/2002/07/owl#FunctionalSyntax" + naturalLanguage: + - "http://lexvo.org/id/iso639-1/en" + isOfType: "http://example.org/ontologyType#DomainOntology" + identifier: + - "http://example.org/ontologies/ontology5/identifier1" + - "http://example.org/ontologies/ontology5/identifier2" + description: "Yet another different example ontology submission" + homepage: "http://example.org/ontologies/ontology5/homepage" + documentation: "http://example.org/ontologies/ontology5/documentation" + notes: + - "Note 9" + - "Note 10" + keywords: + - "Keyword9" + - "Keyword10" + hiddenLabel: + - "HiddenLabel9" + - "HiddenLabel10" + alternative: + - "Alternative9" + - "Alternative10" + abstract: "Abstract for the fifth ontology" + publication: + - "http://example.org/publication9" + - "http://example.org/publication10" + hasLicense: "http://example.org/licenses/license5" + useGuidelines: "Guidelines for use" + morePermissions: "Additional permissions" + released: "2023-05-01T00:00:00Z" + valid: "2023-05-31T00:00:00Z" + curatedOn: "2023-05-15T12:00:00Z" + creationDate: "2023-05-01T00:00:00Z" + modificationDate: "2023-05-15T16:45:00Z" + contact: [ + {name: 'contact9' , email: "contact9@test.com"}, + {name: 'contact10' , email: "contact10@test.com"}, + ] + pullLocation: 'http://example.org/pull5' diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 000000000..c4a633c02 --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,26 @@ +john: + first_name: John + last_name: Doe + username: johndoe + orcid_id: '0000-0001-2345-6789' + github_id: 'johndoe_github' + email: john.doe@example.com + password: password + +jane: + first_name: Jane + last_name: Smith + username: janesmith + orcid_id: '0000-0002-3456-7890' + github_id: 'janesmith_github' + email: jane.smith@example.com + password: password + +bob: + first_name: Bob + last_name: Jones + username: bobjones + orcid_id: '0000-0003-4567-8901' + github_id: 'bobjones_github' + email: bob.jones@example.com + password: password diff --git a/test/helpers/application_test_helpers.rb b/test/helpers/application_test_helpers.rb new file mode 100644 index 000000000..80c76e2b3 --- /dev/null +++ b/test/helpers/application_test_helpers.rb @@ -0,0 +1,108 @@ +require 'rails/test_help' +module ApplicationTestHelpers + + def self.transform_models_to_ids(object) + object.each_pair do |key, value| + if value.is_a?(Array) && value.first.is_a?(LinkedData::Client::Base) + object[key] = value.map(&:id) + elsif value.is_a?(LinkedData::Client::Base) + object[key] = value.id + end + end + object + end + + module Users + def sign_in_as(username) + user = fixtures(:users)[username] + logged_in_user = LinkedData::Client::Models::User.authenticate(user.username, user.password) + if logged_in_user && !logged_in_user.errors + logged_in_user = create_user(user) + end + session[:user] = logged_in_user + end + + def create_user(user , admin: false) + unless (existent_user = LinkedData::Client::Models::User.find_by_username(user.username).first) + values = user.to_h + values[:role] = ["ADMINISTRATOR"] if admin + existent_user = LinkedData::Client::Models::User.new(values: values).save + end + + existent_user.password = user.password + existent_user + end + + def delete_user(user) + LinkedData::Client::Models::User.find_by_username(user.username).first&.delete + end + end + + module Ontologies + def create_ontology(ontology, submission) + ontology = LinkedData::Client::Models::Ontology.new(values: ApplicationTestHelpers.transform_models_to_ids(ontology).to_h).save + if ontology.errors + puts "Ontology creation error: #{ontology.errors}" + delete_ontologies([ontology]) + ontology = LinkedData::Client::Models::Ontology.new(values: ApplicationTestHelpers.transform_models_to_ids(ontology).to_h).save + end + submission[:ontology] = ontology.id + submission.curatedOn = nil # TODO fix the curatedOn not saving + submission.naturalLanguage = Array(submission.naturalLanguage).map{|x| x.gsub('iso639-1','iso639-3')} + submission = LinkedData::Client::Models::OntologySubmission.new(values: ApplicationTestHelpers.transform_models_to_ids(submission).to_h).save + [ontology, submission] + end + + def delete_ontologies(ontologies = @ontologies) + Array(ontologies).each do |o| + LinkedData::Client::Models::Ontology.find_by_acronym(o.acronym).first&.delete + end + end + end + + module Categories + def create_category(category) + created = LinkedData::Client::Models::Category.new(values: category.to_h).save + return LinkedData::Client::Models::Category.find_by_acronym(category.acronym).first if created.errors + created + end + + def create_categories(categories_data = fixtures(:categories)) + @categories = [] + categories_data.to_a.each do |name, category| + @categories << create_category(category) + end + @categories + end + + def delete_categories(categories = LinkedData::Client::Models::Category.all) + Array(categories).each { |g| g.delete } + end + end + + module Groups + def create_group(group) + created = LinkedData::Client::Models::Group.new(values: group.to_h).save + return LinkedData::Client::Models::Group.find_by_acronym(group.acronym).first if created.errors + created + end + + def create_groups(groups_data = fixtures(:groups)) + @groups = [] + groups_data.to_a.each do |name, group| + @groups << create_group(group) + end + @groups + end + + def delete_groups(groups = LinkedData::Client::Models::Group.all) + Array(groups).each { |g| g.delete } + end + end + + module Agents + def delete_agents(agents = LinkedData::Client::Models::Agent.all) + Array(agents).each { |g| g.delete } + end + end +end \ No newline at end of file diff --git a/test/integration/login_flows_test.rb b/test/integration/login_flows_test.rb new file mode 100644 index 000000000..b19ef95f1 --- /dev/null +++ b/test/integration/login_flows_test.rb @@ -0,0 +1,91 @@ +# In test/integration/login_flows_test.rb + +require 'test_helper' + +require_relative '../helpers/application_test_helpers' + +class LoginFlowsTest < ActionDispatch::IntegrationTest + include ApplicationTestHelpers::Users + + + setup do + @user_john = fixtures(:users)[:john] + @user_bob = fixtures(:users)[:bob] + @user_bob = create_user(@user_bob) + end + + teardown do + delete_user(@user_bob) + delete_user(@user_john) + end + + test 'go to sign up page, save, and see account details' do + get root_url + assert_response :success + + get login_index_url + assert_response :success + + get new_user_path + + new_user = @user_john + delete_user(new_user) + + post users_path, params: { + user: { + firstName: new_user.first_name, + lastName: new_user.last_name, + username: new_user.username, + orcidId: new_user.orcid_id, + githubId: new_user.github_id, + email: new_user.email, + password: new_user.password, + password_confirmation: new_user.password + } + } + + assert_redirected_to ontologies_url + follow_redirect! + + assert_select '.notification', text: 'Account was successfully created' + + get account_path + assert_response :success + + assert_select '.account-page-title', text: 'My account' + + assert_select '.title', text: 'First name:' + assert_select '.info', text: new_user.firstName + + assert_select '.title', text: 'Last name:' + assert_select '.info', text: new_user.lastName + + assert_select '.title', text: 'Email:' + assert_select '.info', text: new_user.email + + assert_select '.title', text: 'Username:' + assert_select '.info', text: new_user.username + + assert_select '.title', text: 'ORCID ID:' + assert_select '.info', text: new_user.orcidId + + assert_select '.title', text: 'GitHub ID:' + assert_select '.info', text: new_user.githubId + end + + test 'go to login page and click save' do + get login_index_url + assert_response :success + post login_index_url, params: { + user: { + username: @user_bob.username, + password: @user_bob.password + } + } + + assert_redirected_to root_url + follow_redirect! + + assert_select '.notification', text: "Welcome #{@user_bob.username}!" + end +end diff --git a/test/integration/submission_flows_test.rb b/test/integration/submission_flows_test.rb new file mode 100644 index 000000000..7da33aa90 --- /dev/null +++ b/test/integration/submission_flows_test.rb @@ -0,0 +1,34 @@ +require 'test_helper' +require_relative '../helpers/application_test_helpers' + +class SubmissionFlowsTest < ActionDispatch::IntegrationTest + include ApplicationTestHelpers::Users + include ApplicationTestHelpers::Groups + include ApplicationTestHelpers::Categories + include ApplicationTestHelpers::Ontologies + + setup do + @logged_user = fixtures(:users)[:john] + @user_bob = fixtures(:users)[:bob] + @new_ontology = fixtures(:ontologies)[:ontology1] + @new_submission = fixtures(:submissions)[:submission1] + @groups = create_groups + @categories = create_categories + @user_bob = create_user(@user_bob) + @logged_user = create_user(@logged_user) + @new_ontology[:administeredBy] = [@logged_user.username, @user_bob.username] + @new_ontology[:hasDomain] = @categories[0..3] + @new_ontology[:group] = @groups[0..3] + @new_submission[:isRemote] = '1' + end + + teardown do + delete_user(@user_bob) + delete_user(@logged_user) + delete_groups + delete_categories + delete_ontologies([@new_ontology]) + end +end + + diff --git a/test/system/agent_flows_test.rb b/test/system/agent_flows_test.rb new file mode 100644 index 000000000..d91433747 --- /dev/null +++ b/test/system/agent_flows_test.rb @@ -0,0 +1,103 @@ +require "application_system_test_case" + +class AgentFlowsTest < ApplicationSystemTestCase + include AgentHelper + + setup do + @logged_user = fixtures(:users)[:john] + @new_person = fixtures(:agents)[:agent1] + @new_organization = fixtures(:agents)[:organization1] + login_in_as(@logged_user, admin: true) + end + + def teardown + delete_agents + delete_user(@logged_user) + end + + test "go admin page and create an agent person and edit it" do + visit admin_index_url + click_on "Persons & organizations" + wait_for_text "Create New Agent" + + # Creation test + create_agent_flow(@new_person, person_count: 1, organization_count: 2) + + # Edition test + @new_person2 = fixtures(:agents)[:agent2] + wait_for_text @new_person.name + edit_link = find("a[data-show-modal-title-value=\"Edit agent #{@new_person.name}\"]") + @new_person2.id = edit_link['href'].split('/')[-2] + edit_link.click + + edit_agent_flow(@new_person2, person_count: 1, organization_count: 3) + + end + + test "go admin page and create an agent organization and edit it" do + visit admin_index_url + click_on "Persons & organizations" + + # Creation test + create_agent_flow(@new_organization, person_count: 0, organization_count: 3) + + # Edition test + @new_organization2 = fixtures(:agents)[:organization2] + wait_for_text @new_organization.name + edit_link = find("a[data-show-modal-title-value=\"Edit agent #{@new_organization.name}\"]") + @new_organization2.id = edit_link['href'].split('/')[-2] + edit_link.click + + edit_agent_flow(@new_organization2, person_count: 0, organization_count: 5) + end + + + private + def create_agent_flow(new_agent, person_count: , organization_count:) + wait_for_text "Create New Agent" + + # Creation test + click_on "Create New Agent" + wait_for_text "TYPE" + agent_fill(new_agent) + sleep 1 + assert_text "New Agent added successfully" + find('.close').click + within "table#adminAgents" do + assert_selector '.human', count: person_count + organization_count # all created agents + assert_text new_agent.name + new_agent.identifiers.map{|x| "https://orcid.org/#{x["notation"]}"}.each do |orcid| + assert_text orcid + end + + assert_text 'person', count: person_count + assert_text 'organization', count: organization_count + + new_agent.affiliations.map do |aff| + aff["identifiers"] = aff["identifiers"].each{|x| x["schemaAgency"] = 'ORCID'} + assert_text display_agent(OpenStruct.new(aff), link: false) + end + end + end + + def edit_agent_flow(agent, person_count: , organization_count: ) + wait_for_text "TYPE" + agent_fill(agent, parent_id: agent.id) + # assert_text "New Agent added successfully" + find('.close').click + within "table#adminAgents" do + assert_selector '.human', count: person_count + organization_count # all created agents + assert_text agent.name + agent.identifiers.map{|x| "https://orcid.org/#{x["notation"]}"}.each do |orcid| + assert_text orcid + end + assert_text 'person', count: person_count + assert_text 'organization', count: organization_count + + agent.affiliations.map do |aff| + aff["identifiers"] = aff["identifiers"].each{|x| x["schemaAgency"] = 'ORCID'} + assert_text display_agent(OpenStruct.new(aff), link: false) + end + end + end +end diff --git a/test/system/login_flows_test.rb b/test/system/login_flows_test.rb new file mode 100644 index 000000000..d7c7caf4e --- /dev/null +++ b/test/system/login_flows_test.rb @@ -0,0 +1,75 @@ +require "application_system_test_case" + +class LoginFlowsTest < ApplicationSystemTestCase + + setup do + @user_john = fixtures(:users)[:john] + @user_bob = create_user(fixtures(:users)[:bob]) + end + + teardown do + delete_user(@user_bob) + delete_user(@user_john) + end + + test "go to sign up page, save and see account details" do + visit root_url + click_on 'Login' + + click_on 'Register' + + new_user = @user_john + delete_user(new_user) + + LinkedData::Client::Models::User.find_by_username(new_user.username).first&.delete + + fill_in 'user_firstName', with: new_user.first_name + fill_in 'user_lastName', with: new_user.last_name + fill_in 'user_username', with: new_user.username + fill_in 'user_orcidId', with: new_user.orcid_id + fill_in 'user_githubId', with: new_user.github_id + 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 + + # Click the save button + click_button 'Register' + + + assert_selector '.notification', text: 'Account was successfully created' + + visit root_url + '/account' + + assert_selector '.account-page-title', text: 'My account' + + assert_selector '.title', text: 'First name:' + assert_selector '.info', text: new_user.firstName + + assert_selector '.title', text: 'Last name:' + assert_selector '.info', text: new_user.lastName + + assert_selector '.title', text: 'Email:' + assert_selector '.info', text: new_user.email + + assert_selector '.title', text: 'Username:' + assert_selector '.info', text: new_user.username + + assert_selector '.title', text: 'ORCID ID:' + assert_selector '.info', text: new_user.orcidId + + assert_selector '.title', text: 'GitHub ID:' + assert_selector '.info', text: new_user.githubId + + assert_selector '.account-page-card-title', text: 'API Key' + assert_selector '.account-page-card-title', text: 'Subscriptions' + assert_selector '.account-page-card-title', text: 'Submitted Semantic Resources' + assert_selector '.account-page-card-title', text: 'Projects Created' + + end + + test "go to login page and click save" do + login_in_as(@user_bob) + + assert_selector '.notification', text: "Welcome #{@user_bob.username}!", wait: 10 + end +end diff --git a/test/system/submission_flows_test.rb b/test/system/submission_flows_test.rb new file mode 100644 index 000000000..b00e39ed7 --- /dev/null +++ b/test/system/submission_flows_test.rb @@ -0,0 +1,652 @@ +require "application_system_test_case" + +class SubmissionFlowsTest < ApplicationSystemTestCase + + setup do + @logged_user = fixtures(:users)[:john] + @user_bob = fixtures(:users)[:bob] + @new_ontology = fixtures(:ontologies)[:ontology1] + @new_submission = fixtures(:submissions)[:submission1] + teardown + @groups = create_groups + @categories = create_categories + @user_bob = create_user(@user_bob) + @new_ontology[:administeredBy] = [@logged_user.username, @user_bob.username] + @new_ontology[:hasDomain] = @categories[0..3] + @new_ontology[:group] = @groups[0..3] + @new_submission[:isRemote] = '1' + + login_in_as(@logged_user, admin: true) + end + + teardown do + delete_user(@user_bob) + delete_user(@logged_user) + delete_ontologies([@new_ontology]) + delete_groups + delete_categories + delete_agents + end + + test "create a new ontology and go to it's summary page" do + visit new_ontology_url + + assert_text 'Submit new ontology', wait: 10 + + fill_ontology(@new_ontology, @new_submission) + + assert_selector 'h2', text: 'Ontology submitted successfully!' + click_on current_url.gsub("/ontologies/success/#{@new_ontology.acronym}", '') + ontology_path(@new_ontology.acronym) + + assert_text "#{@new_ontology.name} (#{@new_ontology.acronym})" + assert_selector '.alert-message', text: "The ontology is processing." + + @new_ontology.hasDomain.each do |cat| + assert_text cat.name + end + + + assert_text @new_submission.URI + assert_text @new_submission.description + assert_text @new_submission.pullLocation + assert_date @new_submission.released + + # check + assert_selector '.fas.fa-key' if @new_submission.status.eql?('private') + + # check + assert_selector '.chip_button_container.chip_button_small', text: @new_submission.hasOntologyLanguage + + @new_submission.contact.each do |contact| + assert_text contact["name"] + assert_text contact["email"] + end + + # Assert relations + open_dropdown "#community" + + @new_ontology.group.each do |group| + assert_text group.name + end + + end + + test "click on button edit submission and change all the fields and save" do + submission_2 = fixtures(:submissions)[:submission2] + ontology_2 = fixtures(:ontologies)[:ontology2] + create_ontology(@new_ontology, @new_submission) + visit ontology_path(@new_ontology.acronym) + + # click edit button + find("a.rounded-button[href=\"#{edit_ontology_path(@new_ontology.acronym)}\"]").click + sleep 1 + + selected_categories = @categories[3..4] + selected_groups = Array(@groups[2]) + + within 'form#ontology_submission_form' do + + # General tab + submission_general_edit_fill(ontology_2, submission_2, + selected_groups: selected_groups, + selected_categories: selected_categories) + # Description tab + click_on "Description" + submission_description_edit_fill(submission_2) + + # Dates tab + click_on "Dates" + submission_date_edit_fill(submission_2) + + # Licencing tab + click_on "Licensing" + submission_licensing_edit_fill(ontology_2, submission_2) + + + # Persons and organizations tab + click_on "Persons and organizations" + submission_agent_edit_fill(submission_2) + + # Links tab + click_on "Links" + submission_links_edit_fill(submission_2) + # Media tab + click_on "Media" + submission_media_edit_fill(submission_2) + + # Community tab + click_on "Community" + submission_community_edit_fill(submission_2) + + # Usage tab + click_on "Usage" + submission_usage_edit_fill(submission_2) + + # Relation tab + click_on "Relation" + submission_relations_edit_fill(submission_2) + + # Content tab + click_on "Content" + submission_content_edit_fill(submission_2) + + # Methodology tab + click_on "Methodology" + submission_methodology_fill(submission_2) + + click_button 'save-button' + end + sleep 1 + wait_for '.notification' + assert_selector '.notification', text: "Submission updated successfully" + assert_text "#{ontology_2.name} (#{@new_ontology.acronym})" + + selected_categories.each do |cat| + assert_text cat.name + end + + assert_text submission_2.URI + assert_text submission_2.versionIRI + assert_selector '#submission-status', text: submission_2.version + assert_selector ".flag-icon-fr" # todo fix this + submission_2.identifier.each do |id| + assert_text id + end + + assert_text submission_2.description + + submission_2.keywords.each do |key| + assert_text key + end + + assert_selector "a[href=\"#{submission_2.homepage}\"]" + assert_selector "a[href=\"#{submission_2.documentation}\"]" + assert_selector "a[href=\"#{Array(submission_2.publication).last}\"]" # TODO the publication display is an array can't be an Icon + assert_text submission_2.abstract + + open_dropdown "#dates" + assert_date submission_2.released + assert_date submission_2.valid + submission_2.curatedOn.each do |date| + assert_date date + end + assert_date submission_2.creationDate + assert_date submission_2.modificationDate + + # Assert media + open_dropdown "#link" + # associatedMedia not displayed for now + # submission_2.associatedMedia.each do |media| + # assert_text media + # end + + submission_2.depiction.map do |d| + assert_selector "img[src=\"#{d}\"]" + end + + assert_selector "img[src=\"#{submission_2.logo}\"]" + + # Assert links + assert_selector "a[href=\"#{submission_2.repository}\"]" + + # Assert persons and organizations + open_dropdown "#person_and_organization" + + agent1 = fixtures(:agents)[:agent1] + agent2 = fixtures(:agents)[:agent2] + + assert_text agent1.name, count: 3 + assert_text agent2.name, count: 3 + + # Assert usage + open_dropdown "#projects_section" + usage_properties = [ + :coverage, :knownUsage, + :hasDomain, :example + ] + usage_properties.each do |property| + Array(submission_2[property]).each { |v| assert_text v } # check + end + + submission_2.designedForOntologyTask.each do |task| + assert_text task.delete(' ') # TODO fix in the UI the disaply of taskes + end + + # Assert Methodology + open_dropdown "#methodology" + methodology_properties = [ + :conformsToKnowledgeRepresentationParadigm, + :usedOntologyEngineeringMethodology, + :accrualPolicy + ] + + methodology_properties.each do |key| + Array(submission_2[key]).map { |x| assert_text x } + end + + [:competencyQuestion, :wasGeneratedBy, :wasInvalidatedBy].each do |key| + 2.times.map { |i| assert_text "#{key}-#{i}" } + end + + assert_text submission_2.accrualPeriodicity.split('/').last.downcase + + # Assert Community + open_dropdown "#community" + assert_text submission_2.bugDatabase + assert_text submission_2.mailingList + [:toDoList, :notes, :award].each do |key| + Array(submission_2[key]).map { |x| assert_text x } + end + + selected_groups.each do |group| + assert_text group.name + end + + # Assert Content + open_dropdown "#content" + assert_text submission_2.obsoleteParent + assert_text submission_2.exampleIdentifier + assert_text submission_2.uriRegexPattern + assert_text submission_2.preferredNamespaceUri + assert_text submission_2.preferredNamespacePrefix + + # assert submission_2.metadataVoc + assert_text "rdfs" + assert_text "dct" + + + open_dropdown "#configuration" + + submission_2.keyClasses.each do |key| + assert_text key + end + + # Assert relations + click_on "See all metadata" + sleep 1 + within "#application_modal_content" do + wait_for 'input[type="search"]' + find('input').set('hasPriorVersion') + assert_text submission_2.hasPriorVersion + + submission_2.alternative.each do |alt| + find('input').set('alternative') + assert_text alt + end + + submission_2.hiddenLabel.each do |alt| + find('input').set('hiddenLabel') + assert_text alt + end + + relations = [:hasPart, :ontologyRelatedTo, :similarTo, :comesFromTheSameDomain, + :isAlignedTo, :isBackwardCompatibleWith, :isIncompatibleWith, + :hasDisparateModelling, :hasDisjunctionsWith, :generalizes, + :explanationEvolution, :useImports, + :usedBy, :workTranslation, :translationOfWork + ] + relations.each do |key| + find('input').set(key) + 2.times.each { |id| assert_text "https://#{key}.2.#{id}.com" } + end + end + + end + + test "click on button add submission, create a new submission and go to it's summary page" do + submission_2 = fixtures(:submissions)[:submission2] + ontology_2 = fixtures(:ontologies)[:ontology2] + ontology_2[:administeredBy] = [@logged_user.username, @user_bob.username] + ontology_2[:hasDomain] = @categories.sample(3) + ontology_2[:group] = @groups.sample(2) + submission_2[:isRemote] = '1' + + new_ontology1 = @new_ontology + existent_ontology = new_ontology1 + existent_submission = @new_submission + existent_submission[:submissionStatus] = %w[ERROR_RDF UPLOADED] + create_ontology(existent_ontology, existent_submission) + visit ontology_path(existent_ontology.acronym) + + # click add button + find("a.rounded-button[href=\"#{new_ontology_submission_path(existent_ontology.acronym)}\"]").click + sleep 1 + # assert existent + + assert_equal existent_ontology.name, find_field('ontology[name]').value + assert_equal existent_ontology.acronym, find_field('ontology[acronym]', disabled: true).value + # assert_equal existent_submission.administeredBy, find_field('ontology[administeredBy]').value + # assert_equal existent_submission.hasDomain, find_field('ontology[viewingRestriction]').value + + click_button 'Next' + + assert_equal existent_submission.URI, find_field('submission[URI]').value + assert_equal existent_submission.description, find_field('submission[description]').value + assert_equal existent_submission.hasOntologyLanguage, find_field('submission[hasOntologyLanguage]', visible: false).value + assert_equal existent_submission.notes.sort, all('[name^="submission[notes]"]').map(&:value).sort + assert_equal existent_submission.pullLocation, find_field('submission[pullLocation]').value + + click_button 'Next' + + assert_equal Date.parse(existent_submission.modificationDate).to_s, find('[name="submission[modificationDate]"]', visible: false).value + assert_equal existent_submission.contact.map(&:values).flatten.sort, all('[name^="submission[contact]"]').map(&:value).sort + + # fill new version metadata + click_button 'Back' + sleep 0.5 + click_button 'Back' + + + fill_ontology(ontology_2, submission_2, add_submission: true) + + + assert_selector 'h2', text: 'Ontology submitted successfully!' + click_on current_url.gsub("/ontologies/success/#{existent_ontology.acronym}", '') + ontology_path(existent_ontology.acronym) + + assert_text "#{ontology_2.name} (#{existent_ontology.acronym})" + assert_selector '.alert-message', text: "The ontology is processing." + + ontology_2.hasDomain.each do |cat| + assert_text cat.name + end + + refute_text 'Version IRI' + assert_text existent_submission.version, count: 1 + + assert_text submission_2.URI + assert_text submission_2.description + assert_text submission_2.pullLocation + + + # check + assert_selector '.fas.fa-key' if submission_2.status.eql?('private') + + # check + assert_selector '.chip_button_container.chip_button_small', text: submission_2.hasOntologyLanguage + + submission_2.contact.each do |contact| + assert_text contact["name"] + assert_text contact["email"] + end + + open_dropdown "#community" + + ontology_2.group.each do |group| + assert_text group.name + end + + + open_dropdown "#dates" + assert_date submission_2.modificationDate + assert_date existent_submission.released + + refute_text 'Validity date' + refute_text 'Curation date' + end + + private + + def submission_general_edit_fill(ontology, submission, selected_categories:, selected_groups:) + wait_for_text 'Acronym' + + assert_text 'Acronym' + assert_selector 'input[name="ontology[acronym]"][disabled="disabled"]' + fill_in 'ontology[name]', with: ontology.name + tom_select 'submission[hasOntologyLanguage]', submission.hasOntologyLanguage + + list_checks selected_categories.map(&:acronym), @categories.map(&:acronym) + list_checks selected_groups.map(&:acronym), @groups.map(&:acronym) + + tom_select 'ontology[administeredBy][]', [@user_bob.username] + + fill_in 'submission[URI]', with: submission.URI + fill_in 'submission[versionIRI]', with: submission.versionIRI + fill_in 'submission[version]', with: submission.version + tom_select 'submission[status]', submission.status + + # TODO test deprecated + + tom_select 'submission[hasFormalityLevel]', submission.hasFormalityLevel + tom_select 'submission[hasOntologySyntax]', submission.hasOntologySyntax + tom_select 'submission[naturalLanguage][]', submission.naturalLanguage + tom_select 'submission[isOfType]', submission.isOfType + + list_inputs "#submissionidentifier_from_group_input", + "submission[identifier]", + submission.identifier + end + + def submission_description_edit_fill(submission) + wait_for '[name="submission[description]"]' + + fill_in 'submission[description]', with: submission.description + fill_in 'submission[abstract]', with: submission.abstract + fill_in 'submission[homepage]', with: submission.homepage + fill_in 'submission[documentation]', with: submission.documentation + + list_inputs "#submissionnotes_from_group_input", + "submission[notes]", submission.notes + + list_inputs "#submissionkeywords_from_group_input", + "submission[keywords]", submission.keywords + + list_inputs "#submissionhiddenLabel_from_group_input", + "submission[hiddenLabel]", submission.hiddenLabel + + list_inputs "#submissionalternative_from_group_input", + "submission[alternative]", submission.alternative + + list_inputs "#submissionpublication_from_group_input", + "submission[publication]", submission.publication + + end + + def submission_date_edit_fill(submission) + wait_for_text "Submission date" + + date_picker_fill_in 'submission[released]', submission.released + date_picker_fill_in 'submission[valid]', submission.valid + list_inputs "#submissioncuratedOn_from_group_input", + "submission[curatedOn]", submission.curatedOn do |selector, value, index| + date_picker_fill_in selector, value, index + 1 + end + + date_picker_fill_in 'submission[creationDate]', submission.creationDate + date_picker_fill_in 'submission[modificationDate]', submission.modificationDate + + end + + def submission_licensing_edit_fill(ontology, submission) + wait_for_text "Visibility" + + tom_select 'ontology[viewingRestriction]', ontology.viewingRestriction + tom_select 'submission[hasLicense]', 'CC Attribution 3.0' + fill_in 'submission[useGuidelines]', with: submission.useGuidelines + fill_in 'submission[morePermissions]', with: submission.morePermissions + + within "#submissioncopyrightHolder_from_group_input" do + new_agent = fixtures(:agents)[:agent1] + agent_id = agent_search(new_agent.name) + agent_fill(new_agent, parent_id: agent_id) + end + + end + + def submission_agent_edit_fill(submission) + # TODO use list_inputs + wait_for_text "Contact" + + list_inputs "#submissioncontact_from_group_input", "submission[contact]", submission.contact + + + agent1 = fixtures(:agents)[:agent1] + agent2 = fixtures(:agents)[:agent2] + + [:hasCreator, :hasContributor, :curatedBy].each do |key| + list_inputs "#submission#{key}_from_group_input", "submission[#{key}]", [agent1, agent2] do |selector, value, index| + element = all("turbo-frame:last-of-type").last + within element do + agent_id = agent_search(value.name) + agent_fill(value, parent_id: agent_id) if agent_id + end + end + end + + # TODO agents test + end + + def submission_links_edit_fill(submission) + wait_for_text "Location" + + choose 'submission[isRemote]', option: '1' + fill_in 'submission[pullLocation]', with: submission.pullLocation + list_inputs "#submissionsource_from_group_input", + "submission[source]", submission.source + list_inputs "#submissionendpoint_from_group_input", + "submission[endpoint]", submission.endpoint + tom_select 'submission[includedInDataCatalog][]', submission.includedInDataCatalog + end + + def submission_media_edit_fill(submission) + wait_for_text "Depiction" + + list_inputs "#submissionassociatedMedia_from_group_input", + "submission[associatedMedia]", submission.associatedMedia + + list_inputs "#submissiondepiction_from_group_input", + "submission[depiction]", submission.depiction + + fill_in 'submission[logo]', with: submission.logo + end + + def submission_community_edit_fill(submission) + wait_for_text "Audience" + + fill_in 'submission[audience]', with: submission.audience + fill_in 'submission[repository]', with: submission.repository + fill_in 'submission[bugDatabase]', with: submission.bugDatabase + fill_in 'submission[mailingList]', with: submission.mailingList + + list_inputs "#submissiontoDoList_from_group_input", + "submission[toDoList]", submission.toDoList + list_inputs "#submissionaward_from_group_input", + "submission[award]", submission.award + + end + + def submission_usage_edit_fill(submission) + wait_for_text "Known usage" + list_inputs "#submissionknownUsage_from_group_input", + "submission[knownUsage]", submission.knownUsage + + tom_select 'submission[designedForOntologyTask][]', submission.designedForOntologyTask + + list_inputs "#submissionhasDomain_from_group_input", + "submission[hasDomain]", submission.hasDomain + + fill_in 'submission[coverage]', with: submission.coverage + + list_inputs "#submissionexample_from_group_input", + "submission[example]", submission.example + end + + def submission_content_edit_fill(submission) + wait_for_text "Root of obsolete branch" + + fill_in "submission[obsoleteParent]", with: submission.obsoleteParent + fill_in "submission[uriRegexPattern]", with: submission.uriRegexPattern + fill_in "submission[preferredNamespaceUri]", with: submission.preferredNamespaceUri + fill_in "submission[preferredNamespacePrefix]", with: submission.preferredNamespacePrefix + fill_in "submission[exampleIdentifier]", with: submission.exampleIdentifier + list_inputs "#submissionkeyClasses_from_group_input", + "submission[keyClasses]", submission.keyClasses + tom_select "submission[metadataVoc][]", submission.metadataVoc + + end + + def submission_relations_edit_fill(submission) + wait_for_text "Prior version" + + # TODO ontology view check in + + fill_in "submission[hasPriorVersion]", with: submission.hasPriorVersion + relations = [:hasPart, :ontologyRelatedTo, :similarTo, :comesFromTheSameDomain, + :isAlignedTo, :isBackwardCompatibleWith, :isIncompatibleWith, + :hasDisparateModelling, :hasDisjunctionsWith, :generalizes, + :explanationEvolution, :useImports, + :usedBy, :workTranslation, :translationOfWork + ] + + relations.each do |key| + tom_select "submission[#{key}][]", 2.times.map { |id| "https://#{key}.2.#{id}.com" }, open_to_add: true + end + end + + def submission_methodology_fill(submission) + wait_for_text "Knowledge representation paradigm" + + fill_in "submission[conformsToKnowledgeRepresentationParadigm]", with: submission.conformsToKnowledgeRepresentationParadigm + fill_in "submission[usedOntologyEngineeringMethodology]", with: submission.usedOntologyEngineeringMethodology + tom_select "submission[usedOntologyEngineeringTool][]", submission.usedOntologyEngineeringTool + + list_inputs "#submissionaccrualMethod_from_group_input", + "submission[accrualMethod]", submission.accrualMethod + + tom_select "submission[accrualPeriodicity]", submission.accrualPeriodicity + + fill_in "submission[accrualPolicy]", with: submission.accrualPolicy + + [:competencyQuestion, :wasGeneratedBy, :wasInvalidatedBy].each do |key| + list_inputs "#submission#{key}_from_group_input", + "submission[#{key}]", 2.times.map { |i| "#{key}-#{i}" } + end + end + + def open_dropdown(target) + find(".dropdown-container .dropdown-title-bar[data-target=\"#{target}\"]").click + sleep 1 + end + + def fill_ontology(new_ontology, new_submission, add_submission: false) + within 'form#ontologyForm' do + # Page 1 + fill_in 'ontology[name]', with: new_ontology.name + fill_in 'ontology[acronym]', with: new_ontology.acronym unless add_submission + + tom_select 'ontology[viewingRestriction]', new_ontology.viewingRestriction + tom_select 'ontology[administeredBy][]', new_ontology.administeredBy + + list_checks new_ontology.hasDomain.map(&:acronym), @categories.map(&:acronym) + list_checks new_ontology.group.map(&:acronym), @groups.map(&:acronym) + + + click_button 'Next' + + # Page 2 + fill_in 'submission[URI]', with: new_submission.URI + fill_in 'submission[description]', with: new_submission.description + + if add_submission + list_inputs "#submissionnotes_from_group_input", "submission[notes]", new_submission.notes + end + tom_select 'submission[hasOntologyLanguage]', new_submission.hasOntologyLanguage + tom_select 'submission[status]', new_submission.status + + choose 'submission[isRemote]', option: new_submission.isRemote + fill_in 'submission[pullLocation]', with: new_submission.pullLocation + + click_button 'Next' + + # Page 3 + if add_submission + date_picker_fill_in 'submission[modificationDate]', new_submission.modificationDate + else + date_picker_fill_in 'submission[released]', new_submission.released + end + + list_inputs "#submissioncontact_from_group_input", "submission[contact]", new_submission.contact + + click_button 'Finish' + end + end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index a21ff2216..1083b2287 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,13 +1,44 @@ ENV['RAILS_ENV'] ||= 'test' require_relative '../config/environment' require 'rails/test_help' +require 'simplecov' + +SimpleCov.start 'rails' do + add_filter '/bin/' + add_filter '/db/' + add_filter '/test/' + add_filter '/vendor/' +end class ActiveSupport::TestCase # Run tests in parallel with specified workers parallelize(workers: 1) - # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. - fixtures :all - # Add more helper methods to be used by all tests here... + + + Capybara.server_host = "0.0.0.0" + Capybara.app_host = "http://#{Socket.gethostname}:#{Capybara.server_port}" +end + +# Define the fixtures helper method +def fixtures(fixture_name) + @global_fixtures ||= load_all_fixtures + @global_fixtures[fixture_name.to_s] +end + +# Load all fixtures method +def load_all_fixtures + fixtures_directory = Rails.root.join('test', 'fixtures') + fixture_files = Dir.glob(File.join(fixtures_directory, '*.yml')) + + fixtures_data = {} + + 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) + end + + fixtures_data end \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a16ad8d21..6ef271967 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@babel/runtime@^7.21.0": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" + integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== + dependencies: + regenerator-runtime "^0.14.0" + "@esbuild/linux-loong64@0.14.54": version "0.14.54" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" @@ -37,10 +44,27 @@ resolved "https://registry.yarnpkg.com/@orchidjs/unicode-variants/-/unicode-variants-1.0.4.tgz#6d2f812e3b19545bba2d81caffff1204de9a6a58" integrity sha512-NvVBRnZNE+dugiXERFsET1JlKZfM5lJDEpSMilKW4bToYJ7pxf0Zne78xyXB2ny2c2aHfJ6WLnz1AaTNHAmQeQ== +"@popperjs/core@^2.9.0": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@rails/actioncable@^7.0": - version "7.0.4" - resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.0.4.tgz#70a3ca56809f7aaabb80af2f9c01ae51e1a8ed41" - integrity sha512-tz4oM+Zn9CYsvtyicsa/AwzKZKL+ITHWkhiu7x+xF77clh2b4Rm+s6xnOgY/sGDWoFWZmtKsE95hxBPkgQQNnQ== + version "7.0.5" + resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.0.5.tgz#bbc11203e0d3d5084002abfcf01d621fdf5f3a9d" + integrity sha512-SOBA2heB9lTw0VYIx8M/ed7inSf4I9sR8OIlJprhgkfQ3WJtrxPJ6DDATR1Z3RYaIR7HlT2Olj08v1lfGIGuHA== + +date-fns@^2.29.3: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + +debounce@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== esbuild-android-64@0.14.54: version "0.14.54" @@ -174,6 +198,11 @@ flatpickr@^4.6.13: resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.13.tgz#8a029548187fd6e0d670908471e43abe9ad18d94" integrity sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + split.js@^1.6.5: version "1.6.5" resolved "https://registry.yarnpkg.com/split.js/-/split.js-1.6.5.tgz#f7f61da1044c9984cb42947df4de4fadb5a3f300" @@ -196,6 +225,25 @@ stimulus-read-more@^4.1.0: resolved "https://registry.yarnpkg.com/stimulus-read-more/-/stimulus-read-more-4.1.0.tgz#f34efb2dcb33fd091936d84c569937bc100506c8" integrity sha512-SJyCJqZrhDSKpfrepnhStBaxtyv6Jnvr+b84GDg3l+/BzL5HaFLYmc6QkSNCeR6y0x+Zw7lwKuzv+XzyAm1KzQ== +stimulus-reveal-controller@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/stimulus-reveal-controller/-/stimulus-reveal-controller-4.1.0.tgz#bf0fb4c2706f22d41544b5b02e2fbd794f608575" + integrity sha512-cPpTLV/+IQgiE+J3iBMjf3kD3H9ZOeoRJjyhvcsjyPE82mdcsuWxlzpI1pwSJPN66qSud4hVkhNH5w4xadyOfA== + +stimulus-timeago@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/stimulus-timeago/-/stimulus-timeago-4.1.0.tgz#5e4b712d9eadd7f0e2b3b142f35f334dba4b3857" + integrity sha512-CHcB2rko2ItmDMIhBTaxEMe7sKqI0DOMU8dmWuQ+gzDfoxcrA8DAW/BElHAAw26HVqLTA43RSUsfug8Q+2Ua+g== + dependencies: + date-fns "^2.29.3" + +tippy.js@^6.3.7: + version "6.3.7" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c" + integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ== + dependencies: + "@popperjs/core" "^2.9.0" + tom-select@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/tom-select/-/tom-select-2.2.2.tgz#8e5f9296e6d80254feccb57f0986bd6c44d126e2" @@ -203,3 +251,18 @@ tom-select@^2.2.2: dependencies: "@orchidjs/sifter" "^1.0.3" "@orchidjs/unicode-variants" "^1.0.4" + +vis-data@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/vis-data/-/vis-data-7.1.6.tgz#81dcf4d024d23183cacb680ad605e644cdd6ee6c" + integrity sha512-lG7LJdkawlKSXsdcEkxe/zRDyW29a4r7N7PMwxCPxK12/QIdqxJwcMxwjVj9ozdisRhP5TyWDHZwsgjmj0g6Dg== + +vis-network@^9.1.6: + version "9.1.6" + resolved "https://registry.yarnpkg.com/vis-network/-/vis-network-9.1.6.tgz#943df07e829248943656a2f19a7ec87cc1b707de" + integrity sha512-Eiwx1JleAsUqfy4pzcsFngCVlCEdjAtRPB/OwCV7PHBm+o2jtE4IZPcPITAEGUlxvL4Fdw7/lZsfD32dL+IL6g== + +vis-util@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/vis-util/-/vis-util-5.0.3.tgz#6a9d67c6ed92f2bfa67e50c20166bb5f59ac07bf" + integrity sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==