diff --git a/Dockerfile b/Dockerfile index 53d637d382..b59db0a409 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ ARG GITHUB_OAUTH_TOKEN ARG DEBIAN_FRONTEND=noninteractive ENV NODE_VERSION="12.18.3" -ENV BUNDLER_VERSION="2.0.2" +ENV BUNDLER_VERSION="2.1.4" ENV BUNDLE_PATH__SYSTEM=false ENV APP_USER=app ENV APP_PATH=/app diff --git a/app/models/design_color.rb b/app/models/design_color.rb index 315bc40dc5..3198b3849b 100644 --- a/app/models/design_color.rb +++ b/app/models/design_color.rb @@ -28,10 +28,10 @@ class DesignColor < ApplicationRecord after_commit -> do - # CustomStyle.current.updated_at determins the cache key for inline_css + # CustomStyle.current.updated_at determines the cache key for inline_css # in which the CSS color variables will be overwritten. That is why we need # to ensure that a CustomStyle.current exists and that the time stamps change - # whenever we chagen a color_variable. + # whenever we change a color_variable. if CustomStyle.current CustomStyle.current.touch else diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index be45836816..2e60fc9928 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -2654,7 +2654,7 @@ el: pre_authorization: status: 'Πριν την εξουσιοδότηση' auth_url: 'Auth URL' - access_token_url: 'Access token URL' + access_token_url: 'Token πρόσβασης' errors: messages: #Common error messages diff --git a/config/locales/crowdin/js-el.yml b/config/locales/crowdin/js-el.yml index 120c8dbb69..af55150f8f 100644 --- a/config/locales/crowdin/js-el.yml +++ b/config/locales/crowdin/js-el.yml @@ -200,7 +200,7 @@ el: status_confirmed: "επιβεβαιωμένο" status_waiting: "το email απεστάλη - αναμονή για επιβεβαίωση" test_ee: "Δοκιμάστε την Έκδοση για Επιχειρήσεις για 14 ημέρες δωρεάν " - quick_overview: "Get a quick overview of project management and team collaboration with OpenProject Enterprise Edition." + quick_overview: "Λάβετε μια γρήγορη επισκόπηση της διαχείρισης έργων και της ομαδικής συνεργασίας με το OpenProject." upsale: become_hero: "Γίνετε ήρωας!" benefits: diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index 910f945f08..885d5ec261 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -538,7 +538,7 @@ ru: invalid_url: 'не является допустимым URL-адресом.' invalid_url_scheme: 'не является поддерживаемым протоколом (разрешены следующие: %{allowed_schemes}).' less_than_or_equal_to: "должно быть меньше или равно %{count}." - not_current_user: "is not the current user." + not_current_user: "не является текущим пользователем." not_a_date: "не является допустимой датой." not_a_datetime: "дата и время не являются допустимыми." not_a_number: "не является числом." diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index 0efdacd4c9..310b70b283 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -537,7 +537,7 @@ tr: invalid_url: 'geçerli bir adres değil.' invalid_url_scheme: 'bu protokol desteklenmiyor (izin: %{allowed_schemes}).' less_than_or_equal_to: "%{count} 'ten küçük veya bu değere eşit olmalıdır." - not_current_user: "is not the current user." + not_current_user: "mevcut kullanıcı değil." not_a_date: "geçerli bir tarih değil." not_a_datetime: "geçerli bir zaman değil." not_a_number: "bir sayı değil." @@ -617,7 +617,7 @@ tr: queries/filters/base: attributes: values: - inclusion: "filter has invalid values." + inclusion: "filtrede geçersiz değerler var." format: "%{message}" relation: typed_dag: @@ -2302,8 +2302,8 @@ tr: text_work_package_category_destroy_question: "Bu kategoriye bazı iş paketleri ( %{count}) atanmıştır. Ne yapmak istiyorsun?" text_work_package_category_reassign_to: "İş paketlerini bu kategoriye yeniden ata" text_work_package_updated: "%{id} iş paketi %{author} tarafından güncellendi." - text_work_package_watcher_added: "%{id} tarafından %{watcher_changer}. Work paketine izleyici olarak eklendi" - text_work_package_watcher_removed: "%{id} tarafından %{watcher_changer}. Work paketi saatlerinden çıkarıldınız" + text_work_package_watcher_added: "%{id} iş paketine %{watcher_changer} tarafından izleyici olarak eklendiniz." + text_work_package_watcher_removed: "%{id} iş paketinden, %{watcher_changer} tarafından izleyicilikten çıkarıldınız." text_work_packages_destroy_confirmation: "Seçilen çalışma paket(ler) ini silmek istediğinizden emin misiniz?" text_work_packages_ref_in_commit_messages: "İş paketlerini taahhüt mesajlarında referans ve sabitleme" text_journal_added: "%{value} %{label} eklendi" diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 552e370a56..e862f89876 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -531,7 +531,7 @@ zh-CN: invalid_url: '不是有效的 URL。' invalid_url_scheme: '不是受支持的协议(允许:%{allowed_schemes})。' less_than_or_equal_to: "必须小于或等于 %{count}。" - not_current_user: "is not the current user." + not_current_user: "不是当前用户。" not_a_date: "不是有效的日期。" not_a_datetime: "不是有效的日期时间。" not_a_number: "不是一个数字。" diff --git a/db/migrate/20200114091135_add_theme_name_to_custom_styles.rb b/db/migrate/20200114091135_add_theme_name_to_custom_styles.rb index 93187deffb..46f29ddf93 100644 --- a/db/migrate/20200114091135_add_theme_name_to_custom_styles.rb +++ b/db/migrate/20200114091135_add_theme_name_to_custom_styles.rb @@ -1,5 +1,8 @@ class AddThemeNameToCustomStyles < ActiveRecord::Migration[6.0] def change - add_column :custom_styles, :theme, :string, default: "OpenProject" + add_column :custom_styles, + :theme, + :string, + default: OpenProject::CustomStyles::ColorThemes::DEFAULT_THEME_NAME end end diff --git a/docs/development/README.md b/docs/development/README.md index 39cbaba751..a0e58063b5 100644 --- a/docs/development/README.md +++ b/docs/development/README.md @@ -4,7 +4,7 @@ We are pleased that you are thinking about contributing to OpenProject! This gui ## Get in touch -Please get in touch with us using our [develompment forum](https://community.openproject.com/projects/openproject/forums/7) or send us an email to info@openproject.org. +Please get in touch with us using our [development forum](https://community.openproject.com/projects/openproject/forums/7) or send us an email to info@openproject.org. @@ -60,11 +60,11 @@ Please also use `[ci skip]` in your commit message to suppress builds which are ## Inactive pull requests -We want to keep the Pull request list as cleaned up as possible - we will aim close pull requests after an **inactivity period of 30 days** (no comments, no further pushes) which are not labelled as `work in progress` by us. +We want to keep the Pull request list as cleaned up as possible - we will aim close pull requests after an **inactivity period of 30 days** (no comments, no further pushes) which are not labeled as `work in progress` by us. ## Security -If you notice a security issue in OpenProject, please send us a gpg encrypted email to security@openproject.com and describe the issue you found. Download our public gpg key BDCF E01E DE84 EA19 9AE1 72CE 7D66 9C6D 4753 3958 [here](https://keys.openpgp.org/vks/v1/by-fingerprint/BDCFE01EDE84EA199AE172CE7D669C6D47533958). +If you notice a security issue in OpenProject, please send us a GPG encrypted email to security@openproject.com and describe the issue you found. Download our public GPG key BDCF E01E DE84 EA19 9AE1 72CE 7D66 9C6D 4753 3958 [here](https://keys.openpgp.org/vks/v1/by-fingerprint/BDCFE01EDE84EA199AE172CE7D669C6D47533958). Please include a description on how to reproduce the issue if possible. Our security team will get your email and will attempt to reproduce and fix the issue as soon as possible. diff --git a/docs/development/report-a-bug/README.md b/docs/development/report-a-bug/README.md index 667cabe368..f10425d71b 100644 --- a/docs/development/report-a-bug/README.md +++ b/docs/development/report-a-bug/README.md @@ -2,12 +2,13 @@ If you find a bug please create a bug report. -1. Login to the [OpenProject developer platform](https://community.openproject.com/login). -2. Open the [bug form](https://community.openproject.com/projects/openproject/work_packages/new?type=1). -3. Add a precise subject. -3. Add a detailed description. -4. Attach a file (optional). -5. Press Create. +1. Login to or register at the [OpenProject community platform](https://community.openproject.com/login). It's fast and free. +2. Look for an existing bug report using the search bar in the header navigation on top. If there's one, please leave a comment or add additional information. Otherwise: +3. Open the [bug form](https://community.openproject.com/projects/openproject/work_packages/new?type=1). +4. Add a precise subject. +5. Add a detailed description. +6. Attach a file (optional). +7. Press Create. # Information you should add to the bug description diff --git a/docs/development/submit-feature-idea/README.md b/docs/development/submit-feature-idea/README.md index 65d74cd87a..a729a812f0 100644 --- a/docs/development/submit-feature-idea/README.md +++ b/docs/development/submit-feature-idea/README.md @@ -2,12 +2,13 @@ ## How to submit a feature idea? -1. Login to the [OpenProject community platform](https://community.openproject.com/login). -2. Open the [feature create form](https://community.openproject.com/projects/openproject/work_packages/create_new?type=6). -3. Add a subject and detailed description. -4. Attach a file (optional). -5. Set version to "Wish List". -6. Press Create. +1. Login to or register at the [OpenProject community platform](https://community.openproject.com/login). It's fast and free. +2. Use the search bar in the header navigation on top to look for similar feature requests. If there's one, please leave a comment or add additional information. Otherwise: +3. Open the [feature create form](https://community.openproject.com/projects/openproject/work_packages/create_new?type=6). +4. Add a subject and detailed description. +5. Attach a file (optional). +6. Set version to "Wish List". +7. Press Create. ## Feature idea guideline diff --git a/docs/installation-and-operations/installation/docker/README.md b/docs/installation-and-operations/installation/docker/README.md index cfaabdc747..cfa9bad07b 100644 --- a/docs/installation-and-operations/installation/docker/README.md +++ b/docs/installation-and-operations/installation/docker/README.md @@ -17,27 +17,40 @@ installed. OpenProject with Docker can be launched in two ways: 1. Multiple containers (recommended), each with a single process inside, using a Compose file. Allows to easily choose which services you want to run, and simplifies scaling and monitoring aspects. + 2. One container with all the processes inside. Easy but not recommended for production. This is the legacy behaviour. ## One container per process (recommended) ### Quick Start -First, you must clone the OpenProject repository: +First, you must clone the [openproject-deploy](https://github.com/opf/openproject-deploy/tree/stable/11/compose) repository: + +```bash +git clone https://github.com/opf/openproject-deploy --depth=1 --branch=stable/11 openproject +``` + +Then, go into the compose folder: + +```bash +cd openproject/compose +``` + +Make sure you are using the latest version of the Docker images: ```bash -git clone --depth=1 --branch=stable/11 https://github.com/opf/openproject +docker-compose pull ``` -Then, go into the OpenProject folder and you can launch all the services required by OpenProject with docker-compose: +Launch the containers: ```bash docker-compose up -d ``` -After some time, you will be able to access OpenProject on http://localhost:8080. The default username and password is login: `admin`, and password: `admin`. +After a while, OpenProject should be up and running on . The default username and password is login: `admin`, and password: `admin`. -Note that the official `docker-compose.yml` file present in the repository can be adjusted to your convenience. For instance you could mount specific configuration files, override environment variables, or switch off services you don't need. Please refer to the official docker-compose documentation for more details. +Note that the `docker-compose.yml` file present in the repository can be adjusted to your convenience. For instance you could mount specific configuration files, override environment variables, or switch off services you don't need. Please refer to the official [Docker Compose documentation](https://docs.docker.com/compose/extends/) for more details. You can stop the Compose stack by running: diff --git a/docs/installation-and-operations/installation/packaged/README.md b/docs/installation-and-operations/installation/packaged/README.md index 0fb1d6cd49..a29f5d9e77 100644 --- a/docs/installation-and-operations/installation/packaged/README.md +++ b/docs/installation-and-operations/installation/packaged/README.md @@ -75,6 +75,8 @@ sudo apt-get install openproject Then finish the installation by reading the [*Initial configuration*][initial-config] section. + + ### Ubuntu 18.04 Import the PGP key used to sign our packages: @@ -286,6 +288,7 @@ This wizard step is only available on the following distributions: * RHEL/CentOS 8 * Ubuntu 20.04 +* Ubuntu 18.04 * Debian 10 On older distributions, this wizard step won't be displayed, and the installation will default to the default edition. diff --git a/docs/release-notes/11-0-3/README.md b/docs/release-notes/11-0-3/README.md new file mode 100644 index 0000000000..ba5ab369c8 --- /dev/null +++ b/docs/release-notes/11-0-3/README.md @@ -0,0 +1,22 @@ +--- +title: OpenProject 11.0.3 +sidebar_navigation: + title: 11.0.3 +release_version: 11.0.3 +release_date: 2020-11-16 +--- + +# OpenProject 11.0.3 + +We released [OpenProject 11.0.3](https://community.openproject.com/versions/1456). +The release contains several bug fixes and we recommend updating to the newest version. + + +#### Bug fixes and changes + +- Fixed: Bulk edit: Custom field long text cut off \[[#34829](https://community.openproject.com/wp/34829)\] +- Fixed: "Estimates and time" not translated to German when not logged in on community.openproject.com \[[#35009](https://community.openproject.com/wp/35009)\] +- Fixed: IFC viewer can't load models when OP installed on subpath \[[#35129](https://community.openproject.com/wp/35129)\] +- Fixed: Elder BIM instances get "OpenProject" theme instead of the "OpenProject BIM" theme after migration. \[[#35131](https://community.openproject.com/wp/35131)\] +- Fixed: Transitive Relations embedded in work package representer -> poor performance \[[#35168](https://community.openproject.com/wp/35168)\] +- Fixed: LDAP Group sync deletes users when removed from group \[[#35265](https://community.openproject.com/wp/35265)\] diff --git a/docs/release-notes/README.md b/docs/release-notes/README.md index 5374bd505c..c2615d5450 100644 --- a/docs/release-notes/README.md +++ b/docs/release-notes/README.md @@ -12,6 +12,13 @@ Stay up to date and get an overview of the new features included in the releases +## 11.0.3 + +Release date: 2020-11-16 + +[Release Notes](11-0-3/) + + ## 11.0.2 Release date: 2020-11-06 diff --git a/docs/system-admin-guide/attribute-help-texts/README.md b/docs/system-admin-guide/attribute-help-texts/README.md index 1f8ea17e17..f980b49e24 100644 --- a/docs/system-admin-guide/attribute-help-texts/README.md +++ b/docs/system-admin-guide/attribute-help-texts/README.md @@ -28,6 +28,7 @@ This way you will reduce wrong entries for attributes. This is especially releva | [Editing or deleting Attribute help texts](#editing-or-deleting-attribute-help-texts) | How to edit and how to delete an Attribute help text. | | [Work packages](#work-packages) | Where will Attribute help texts for work packages be used? | | [Projects](#projects) | Where will Attribute help texts for projects be used? | +| [WYSIWYG editor](#wysiwyg-editor) | Where can Attribute help texts be displayed? | Navigate to -> *Administration* -> *Attribute help texts* to set up help texts for attributes and custom fields in work packages and projects. Here you can add, edit and delete Attribute help texts. @@ -81,8 +82,14 @@ The Attribute help texts for projects will be displayed in the Project details w They will help the users (e.g. the project managers) understand what kind of information to put in which fields. -![image-20201007112035870](image-20201007112035870.png) +![Attribute help texts project overview](image-20201007112035870.png) -![image-20201007112741049](image-20201007112741049.png) \ No newline at end of file +![Attribute help texts project information](image-20201007112741049.png) + + + +## WYSIWYG editor + +The Attribute help texts can also be displayed in the WYSIWYG text editor by using a certain syntax. Find out more [here](../../user-guide/wysiwyg/#embedding-of-work-package-attributes-and-project-attributes). \ No newline at end of file diff --git a/docs/user-guide/meetings/README.md b/docs/user-guide/meetings/README.md index 0700cc2693..e788758c88 100644 --- a/docs/user-guide/meetings/README.md +++ b/docs/user-guide/meetings/README.md @@ -2,7 +2,7 @@ sidebar_navigation: title: Meetings priority: 760 -description: Manage meetings with agenda and meeting minutes in OpenProject. +description: Manage meetings with agenda and meeting minutes in OpenProject. robots: index, follow keywords: meetings --- @@ -11,14 +11,16 @@ keywords: meetings Meetings in OpenProject allow you to manage and document your project meetings at one central place, prepare a meeting agenda together with your team and document and share meeting minutes with your meeting attendees at one central place. -
**Meetings** is defined as a module that allows the organization of meetings. The module has to be activated in the project settings in order to be displayed in the side navigation.
+
**Meetings** is defined as a module that allows the organization of meetings. The module has to be activated in the project settings in order to be displayed in the side navigation.
-| Feature | Documentation for | + + +| Topic | Content | | ------------------------------------------------------------ | ------------------------------------------- | | [Meetings in OpenProject](#meetings-in-OpenProject) | How to open meetings in OpenProject? | | [Create a new meeting](#create-a-new-meeting) | How to create a new meeting in OpenProject? | | [Edit a meeting](#edit-a-meeting) | How to edit an existing meeting? | -| [Add meeting participants](#add-meeting-participants) | How to add participants to a meeting? | +| [Add meeting participants](#add-meeting-participants) | How to invite people to a meeting? | | [Create or edit the meeting agenda](#create-or-edit-the-meeting-agenda) | How to create or edit the agenda? | | [Copy a meeting (recurring meetings)](#copy-a-meeting-(recurring meetings)) | How to create recurring meetings? | @@ -26,15 +28,13 @@ Meetings in OpenProject allow you to manage and document your project meetings a By selecting **Meetings** in the project menu on the left, you get an overview of all project meetings sorted by date. By clicking on a meeting name you can view further details of the meeting. -Note: in order to be able to use the meetings plugin, the Meetings module needs to be activated in the [Project Settings](../projects). +**Note:** in order to be able to use the meetings plugin, the **Meetings module needs to be activated** in the [Project Settings](../projects/project-settings/modules/). ![meetings](1567598397931.png) ## Create a new meeting -To create a new meeting, click on Meetings in the project menu (Meetings needs to be activated in the Project settings to be displayed). - -Click the green **+ Meeting** button. +To create a new meeting, click on Meetings in the project menu. Click the green **+ Meeting** button. ![create-meeting](create-meeting.png) @@ -75,18 +75,25 @@ Click on the **Save** button to secure the changes made by you. After creating a meeting, you can set up a **meeting agenda**. 1. Prior to starting the meeting all participants can add their input to the agenda by simply clicking the Edit button. -2. With the toolbar you can make changes to the text format or edit macros, e.g. table of contents or work package tables (see [Wiki](#wiki) for details). + +2. With the toolbar you can make changes to the text format or edit macros, e.g. table of contents or work package tables (see the [documentation for the WYSIWYG editor](../wysiwyg) for details). + 3. All changes made to the agenda are tracked. You can click the **History** button to get an overview of all the changes including the persons who made the changes. + 4. Click the **Send for review** button to quickly notify participants about the meeting and send them a link to the meeting. + 5. Press the **Send iCalendar** button to send a calendar entry to the participants. The users can then quickly add the meeting to their calendar by accepting the invitation / importing the iCal file attached to the email notification. + 6. At the beginning of the meeting, **Close** the agenda to prevent any other changes and provide the same basis for all meeting participants. After closing the agenda, the meeting minutes are displayed to capture the results of the meeting. -7. ![meeting-agenda](meeting-agenda.png) + + + ![meeting-agenda](meeting-agenda.png) ## Create or edit meeting minutes The **meeting minutes** are automatically created when closing the agenda in the details view of the meeting and selecting the [Close](#create-or-edit-the-meeting-agenda) option. -The agenda is closed and copied to the meeting minutes page as a basis. You can start editing the minutes now. The same as in the [wiki](#wiki) pages, you can format the text, link minutes to work packages, documents, include work package lists or other macros. +The agenda is closed and copied to the meeting minutes page as a basis. You can start editing the minutes now. The same way as in the [wiki](../wiki) pages, you can format the text, link minutes to work packages, documents, include work package lists or other macros. Don’t forget to **Save** your data. diff --git a/docs/user-guide/projects/README.md b/docs/user-guide/projects/README.md index d3cc6d1f2c..93ac77fb3b 100644 --- a/docs/user-guide/projects/README.md +++ b/docs/user-guide/projects/README.md @@ -149,6 +149,9 @@ Navigate to the [project settings](project-settings) and click **Set as template You can create a new project by using an existing template. This causes the properties of the project template to be copied to the new project. Find out in our Getting started guide how to [create a new project](../../getting-started/projects/#create-a-new-project) in OpenProject. Another way for using a template project would be to [copy it](#copy-a-project). + + + ### Copy a project diff --git a/docs/user-guide/work-packages/README.md b/docs/user-guide/work-packages/README.md index 3505abd272..3f755f4cba 100644 --- a/docs/user-guide/work-packages/README.md +++ b/docs/user-guide/work-packages/README.md @@ -15,10 +15,12 @@ keywords: work packages Work packages have a **type**, an **ID**, a **subject** and may have various additional attributes, such as **status**, **assignee**, **priority**, **due date**. -
**Work package ID** is defined as a unique integer assigned to a newly created work package. Work package IDs cannot be changed and are numbered across all projects of an OpenProject instance (therefore, the numbering within a project may not be sequential).
+
**Work package ID** is defined as a unique integer assigned to a newly created work package. Work package IDs cannot be changed and are numbered across all projects of an OpenProject instance (therefore, the numbering within a project may not be sequential).
+
-**Types** are the different items a work package can represent, such as task, feature, bug, phase, milestone. The work package types can be configured in the system administration. -
+**Types** are the different items a work package can represent, such as task, feature, bug, phase, milestone. The work package types can be configured in the system administration. + + Work packages can be displayed in a projects timeline, e.g. as a milestone or a phase. In order to use the work packages, the work package module has to be activated in the project settings. diff --git a/docs/user-guide/work-packages/create-work-package/README.md b/docs/user-guide/work-packages/create-work-package/README.md index 0db28dd3b2..4af2c07ab9 100644 --- a/docs/user-guide/work-packages/create-work-package/README.md +++ b/docs/user-guide/work-packages/create-work-package/README.md @@ -9,7 +9,7 @@ keywords: create Work packages # Create Work packages -| Feature | Documentation for | +| Topic | Content | | ------------------------------------------------------------ | ---------------------------------------------- | | [Create Work packages](#how-to-create-work-packages) | How to create a new Work package in a project? | | [Add attachments to a Work package](#add-attachments-to-work-packages) | How to add attachments to a Work package? | diff --git a/docs/user-guide/wysiwyg/README.md b/docs/user-guide/wysiwyg/README.md index f56353a48f..b171ca9b3b 100644 --- a/docs/user-guide/wysiwyg/README.md +++ b/docs/user-guide/wysiwyg/README.md @@ -3,29 +3,43 @@ sidebar_navigation: title: Rich text editor description: Find out about the CKEditor5 WYSIWYG editor in OpenProject robots: index, follow -keywords: WYSIWYG, CKEditor +keywords: WYSIWYG, CKEditor, Links to OpenProject resources --- # Rich text (WYSIWYG) editor in OpenProject -Starting with version 8.0.0, OpenProject features a quasi-WYSIWYG editor, powered by [CKSource CKEditor5](https://ckeditor5.github.io/). The underlying format is GitHub-flavored CommonMark ([GFM](https://github.github.com/gfm/)). All previous textile-based content will be migrated when upgrading to OpenProject 8.0. +Starting with version 8.0.0, OpenProject features a quasi-WYSIWYG editor, powered by [CKSource CKEditor5](https://ckeditor5.github.io/). The underlying format is GitHub-flavored CommonMark ([GFM](https://github.github.com/gfm/)). All previous textile-based content will be migrated when upgrading to OpenProject 8.0. + + + + + +| Topic | Content | +| ------------------------------------------------------------ | -------------------------------------------------------- | +| [Basic formating](#basic-formatting) | Basic formatting elements in the WYSIWYG editor | +| [Image handling](#image-handling) | How to add images in the WYSIWYG editor? | +| [Macros](#macros) | Available macros in the WYSIWYG editor | +| [Links to OpenProject resources](#links-to-openproject-resources) | How to link to resources like wikis, projects, meetings? | +| [Embedding of work package attributes and project attributes](#embedding-of-work-package-attributes-and-project-attributes) | How to embed attributes and attribute help texts? | ## Basic formatting The CKEditor5 build in OpenProject supports basic text styles, such as bold and italic formatting, headings, -strikethrough, inline code, and quotes as well as inline image handling. Pasting content such as images or rich text is also supported, while unsupported styling will be stripped by the editor. +strike-through, inline code, and quotes as well as inline image handling. Pasting content such as images or rich text is also supported, while unsupported styling will be stripped by the editor. -### Linebreaks +### Line breaks Instead of creating a new paragraph with Enter, you can also press `SHIFT+Enter` to create a line break without creating a new paragraph. ### Links -Create hyperlinks by pressing the toolbar (optionally with some selected text), or by pressing `CTRL+k` to open a popup to enter the link href. +Create hyperlinks by pressing the tool-bar (optionally with some selected text), or by pressing `CTRL+k` to open a popup to enter the link href. ### Widgets and Newlines -CKEditor usese widgets to display block elements such as images, tables, and other elements that are not inline. You can select most widgets by pressing on it - The only exception to that is the table widget, it has a little select knob at the top left to select the entire table. +CKEditor uses widgets to display block elements such as images, tables, and other elements that are not inline. You can select most widgets by pressing on it - The only exception to that is the table widget, it has a little select knob at the top left to select the entire table. When you have a widget selected, you can remove or cut it. You can create a newline below it by selecting the widget and pressing `ENTER` or `↓ (ARROW DOWN)`, or a newline above it by pressing `SHIFT+enter` or `↑ (ARROW UP)`. This is especially needed when the widget is the first or last element on the page to insert a line below or above it. @@ -34,14 +48,10 @@ When you have a widget selected, you can remove or cut it. You can create a newl As CKEditor5 currently does not provide support for code blocks, OpenProject can display, but not edit code blocks within the CKEditor instance. A code block can be edited through a modal window within a `CodeMirror` editor instance. This has the advantage of providing syntax highlighting and code sensing ([for supported languages](https://codemirror.net/mode/)). - - ### Tables The GFM extension of the CommonMark specs adds a definition for table syntax which the CKEditor build of OpenProject supports. This definition requires all tables to have a heading row. For tables created with CKEditor without heading rows, a HTML table is output instead. This matches the behavior of, e.g., GitHub. - - ### Autoformatting CKEditor5 allows certain CommonMark-like [autoformatting keyboard strokes](https://ckeditor5.github.io/docs/nightly/ckeditor5/latest/features/autoformat.html): @@ -51,7 +61,6 @@ CKEditor5 allows certain CommonMark-like [autoformatting keyboard strokes](https - Create a bulleted list by starting the line with `* ` or `-` and a space - Create a numbered list by starting the line with `1.` or `1)` and a space - ## Image handling @@ -71,6 +80,9 @@ The attachment will be automatically uploaded and stored as an attachment. OpenProject has supported macros on textile formatted pages and continues to do so with the WYSIWYG editor. Note that macros are not expanded while editing the page, instead a placeholder is shown. +You can find the macros here in the text editor: +![Macros text editor](image-20201109183018255.png) + ### Table of contents @@ -102,9 +114,6 @@ The rendered page will then fetch the work package table results dynamically, re Use it to embed views in other pages, create reporting of multiple results, or to embed a Gantt chart view. -## Full vs constrained editor - -In some resources such as work packages or comments, the editor does not exhibit all functionality such as macros or image upload. ## Links to OpenProject resources @@ -130,7 +139,7 @@ As with the textile formatting syntax, you can link to other resources within Op - **commit by hash:** `commit:f30e13e4` - **To a source file in the repository**: `source:"some/file"` -To avoid processing these items, precede them with a bang `!` character such as `!#12` will prevent linking to a work package with ID 12. +To avoid processing these items, preceding them with a bang `!` character such as `!#12` will prevent linking to a work package with ID 12. @@ -140,16 +149,15 @@ For work packages and users, typing `#` or `@` will open an autocompleter for vi -## Embedding of work package and project attributes +## Embedding of work package attributes and project attributes -You can embed specific attributes of work packages or projects using the following syntax: +You can embed specific attributes of work packages or projects using a certain syntax. +Examples: - **Linking to the subject of work package with id #1234**: `workPackageValue:1234:subject` - **Linking to the current project's status**: `projectValue:status` - **Linking to the subject of work package with subject "Project start"**: `workPackageValue:"Project start":subject` - - +### Embedding attribute help texts +You can also embed attribute values and [their help texts](../../system-admin-guide/attribute-help-texts/) by using `workPackageLabel` instead: `workPackageLabel:1234:status` would output the translated label for "Status" and (if exists), the corresponding help text for it. + -You can also embed attribute values and [their help texts](https://docs.openproject.org/system-admin-guide/manage-work-packages/attribute-help-texts/#manage-attribute-help-texts-premium-feature) by using `workPackageLabel` instead: `workPackageLabel:1234:status` would output the translated label for "Status" and (if exists), the corresponding help text for it. -Note that these macros will only be expanded in the frontend. For each individual user, the correct permissions will be checked and the macro will result in an error if the user is not allowed to view the respective resource. +**Note**: these macros will only be expanded in the frontend. For each individual user, the correct permissions will be checked and the macro will result in an error if the user is not allowed to view the respective resource. + -**Available attributes for work packages** -The following list contains all suppported attribute names for the `workPackageValue` and `workPackageLabel` macros. +### Available attributes for work packages + +The following list contains all supported attribute names for the `workPackageValue` and `workPackageLabel` macros, where `1234` stands for the [work package ID](../work-packages). | **Attribute** | Usage example | | ------------------- | ------------------------------------------------------------ | +| *Custom Fields* | `workPackageValue:1234:"Name of the work package custom field"` | | Assigned user | `workPackageValue:1234:assignee` | | Author | `workPackageValue:1234:author` | | Category | `workPackageValue:1234:category` | @@ -179,7 +192,7 @@ The following list contains all suppported attribute names for the `workPackageV | Parent work package | `workPackageValue:1234:parent` | | Priority | `workPackageValue:1234:priority` | | Project | `workPackageValue:1234:project` | -| Remaining Time | `workPackageValue:1234:remainingTime` | +| Remaining hours | `workPackageValue:1234:remainingTime` (sic!) | | Responsible user | `workPackageValue:1234:responsible` | | Spent time | `workPackageValue:1234:spentTime` | | Start date | `workPackageValue:1234:startDate` | @@ -188,14 +201,14 @@ The following list contains all suppported attribute names for the `workPackageV | Work package type | `workPackageValue:1234:type` | | Date of last update | `workPackageValue:1234:updatedAt` | | Version | `workPackageValue:1234:version` | -| *Custom Fields* | `workPackageValue:1234:"Name of the work package custom field"` | -**Available attributes for projects** +### Available attributes for projects -The following list contains all suppported attribute names for the `projectValue` and `projectLabel` macros. The examples all show references to the _current_ project the document is rendered in. They can also reference another project with `projectValue:"Identifier of the project":attribute`. +The following list contains all supported attribute names for the `projectValue` and `projectLabel` macros. The examples all show references to the _current_ project the document is rendered in. They can also reference another project with `projectValue:"Identifier of the project":attribute`. | **Attribute** | Usage example | | ------------------------- | ------------------------------------------------- | +| *Custom Fields* | `projectValue:"Name of the project custom field"` | | Project active? (boolean) | `projectValue:active` | | Description | `projectValue:description` | | Identifier of the project | `projectValue:identifier` | @@ -204,5 +217,4 @@ The following list contains all suppported attribute names for the `projectValue | Status description | `projectValue:statusExplanation` | | Parent project | `projectValue:parent` | | Project public? (boolean) | `projectValue:public` | -| *Custom Fields* | `projectValue:"Name of the project custom field"` | diff --git a/docs/user-guide/wysiwyg/image-20201109183018255.png b/docs/user-guide/wysiwyg/image-20201109183018255.png new file mode 100644 index 0000000000..03a7383eeb Binary files /dev/null and b/docs/user-guide/wysiwyg/image-20201109183018255.png differ diff --git a/frontend/src/app/modules/bim/ifc_models/ifc-viewer/ifc-viewer.service.ts b/frontend/src/app/modules/bim/ifc_models/ifc-viewer/ifc-viewer.service.ts index 20d0668943..0241a21fba 100644 --- a/frontend/src/app/modules/bim/ifc_models/ifc-viewer/ifc-viewer.service.ts +++ b/frontend/src/app/modules/bim/ifc_models/ifc-viewer/ifc-viewer.service.ts @@ -47,7 +47,7 @@ export class IFCViewerService extends ViewerBridgeService { public newViewer(elements:XeokitElements, projects:any[]) { import('@xeokit/xeokit-bim-viewer/dist/main').then((XeokitViewerModule:any) => { - let server = new XeokitServer(); + let server = new XeokitServer(this.pathHelper); let viewerUI = new XeokitViewerModule.BIMViewer(server, elements); viewerUI.on("queryPicked", (event:any) => { diff --git a/frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.d.ts b/frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.d.ts deleted file mode 100644 index b2fc7ba970..0000000000 --- a/frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.d.ts +++ /dev/null @@ -1 +0,0 @@ -export class XeokitServer {} \ No newline at end of file diff --git a/frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.js b/frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.js deleted file mode 100644 index 8bcc63f012..0000000000 --- a/frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.js +++ /dev/null @@ -1,78 +0,0 @@ -import {utils} from "@xeokit/xeokit-sdk/src/viewer/scene/utils"; -/** - * Default server client which loads content via HTTP from the file system. - */ -class XeokitServer { - - /** - * - * @param cfg - * @param.cfg.dataDir Base directory for content. - */ - constructor(cfg = {}) { - this._dataDir = cfg.dataDir || ""; - } - - /** - * Gets the manifest of all projects. - * @param done - * @param error - */ - getProjects(done, _error) { - done({ projects: window.gon.ifc_models.projects }); - } - - /** - * Gets a manifest for a project. - * @param projectId - * @param done - * @param error - */ - getProject(projectData, done, _error) { - var manifestData = { - id: projectData[0].id, - name: projectData[0].name, - models: window.gon.ifc_models.models, - viewerContent: { - modelsLoaded: window.gon.ifc_models.shown_models - }, - viewerConfigs: { - saoEnabled: true // Needs to be enabled by default if we want to use it selectively on the available models. - } - }; - - done(manifestData); - } - - /** - * Gets metadata for a model within a project. - * @param projectId - * @param modelId - * @param done - * @param error - */ - getMetadata(_projectId, modelId, done, error) { - const attachmentId = window.gon.ifc_models.metadata_attachment_ids[modelId]; - console.log(`Loading model metadata for: ${attachmentId}`); - utils.loadJSON(this.attachmentUrl(attachmentId), done, error); - } - - /** - * Gets geometry for a model within a project. - * @param projectId - * @param modelId - * @param done - * @param error - */ - getGeometry(projectId, modelId, done, error) { - const attachmentId = window.gon.ifc_models.xkt_attachment_ids[modelId]; - console.log(`Loading model geometry for: ${attachmentId}`); - utils.loadArraybuffer(this.attachmentUrl(attachmentId), done, error); - } - - attachmentUrl(attachmentId) { - return "/api/v3/attachments/" + attachmentId + "/content"; - } -} - -export {XeokitServer}; \ No newline at end of file diff --git a/frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.ts b/frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.ts new file mode 100644 index 0000000000..77b5066e78 --- /dev/null +++ b/frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.ts @@ -0,0 +1,76 @@ +// @ts-ignore +import {utils} from "@xeokit/xeokit-sdk/src/viewer/scene/utils"; +import {PathHelperService} from "../../../common/path-helper/path-helper.service"; +import {IFCGonDefinition} from "../pages/viewer/ifc-models-data.service"; + +/** + * Default server client which loads content via HTTP from the file system. + */ +export class XeokitServer { + private ifcModels:IFCGonDefinition; + /** + * + * @param config + * @param.config.pathHelper instance of PathHelperService. + */ + constructor(private pathHelper:PathHelperService) { + this.ifcModels = window.gon.ifc_models; + } + + /** + * Gets the manifest of all projects. + * @param done + * @param error + */ + getProjects(done:Function, _error:Function) { + done({ projects: this.ifcModels.projects }); + } + + /** + * Gets a manifest for a project. + * @param projectId + * @param done + * @param error + */ + getProject(projectData:any, done:Function, _error:Function) { + var manifestData = { + id: projectData[0].id, + name: projectData[0].name, + models: this.ifcModels.models, + viewerContent: { + modelsLoaded: this.ifcModels.shown_models + }, + viewerConfigs: { + saoEnabled: true // Needs to be enabled by default if we want to use it selectively on the available models. + } + }; + + done(manifestData); + } + + /** + * Gets metadata for a model within a project. + * @param projectId + * @param modelId + * @param done + * @param error + */ + getMetadata(_projectId:string, modelId:number, done:Function, error:Function) { + const attachmentId = this.ifcModels.metadata_attachment_ids[modelId]; + console.log(`Loading model metadata for: ${attachmentId}`); + utils.loadJSON(this.pathHelper.attachmentContentPath(attachmentId), done, error); + } + + /** + * Gets geometry for a model within a project. + * @param projectId + * @param modelId + * @param done + * @param error + */ + getGeometry(projectId:string, modelId:number, done:Function, error:Function) { + const attachmentId = this.ifcModels.xkt_attachment_ids[modelId]; + console.log(`Loading model geometry for: ${attachmentId}`); + utils.loadArraybuffer(this.pathHelper.attachmentContentPath(attachmentId), done, error); + } +} diff --git a/frontend/src/app/modules/common/gon/gon.service.ts b/frontend/src/app/modules/common/gon/gon.service.ts index c2d02661c4..2c046150e7 100644 --- a/frontend/src/app/modules/common/gon/gon.service.ts +++ b/frontend/src/app/modules/common/gon/gon.service.ts @@ -27,6 +27,7 @@ // ++ import {Injectable} from "@angular/core"; +import {IFCGonDefinition} from "../../bim/ifc_models/pages/viewer/ifc-models-data.service"; declare global { interface Window { @@ -36,6 +37,7 @@ declare global { export interface GonType { [key:string]:unknown; + ifc_models:IFCGonDefinition; } @Injectable({ providedIn: 'root' }) diff --git a/frontend/src/app/modules/common/path-helper/path-helper.service.ts b/frontend/src/app/modules/common/path-helper/path-helper.service.ts index 10e2e553cf..d999b8cb3d 100644 --- a/frontend/src/app/modules/common/path-helper/path-helper.service.ts +++ b/frontend/src/app/modules/common/path-helper/path-helper.service.ts @@ -45,10 +45,10 @@ class Apiv3Paths { * @param context */ public previewMarkup(context:string) { - let base = this.apiV3Base + '/render/markdown'; + let base = `${this.apiV3Base}/render/markdown`; if (context) { - return base + `?context=${context}`; + return `${base}?context=${context}`; } else { return base; } @@ -85,7 +85,7 @@ class Apiv3Paths { @Injectable({ providedIn: 'root' }) export class PathHelperService { - public readonly appBasePath = window.appBasePath ? window.appBasePath : ''; + public readonly appBasePath = window.appBasePath || ''; public readonly api = { v3: new Apiv3Paths(this.appBasePath) }; @@ -95,21 +95,25 @@ export class PathHelperService { } public attachmentDownloadPath(attachmentIdentifier:string, slug:string|undefined) { - let path = this.staticBase + '/attachments/' + attachmentIdentifier; + let path = `${this.staticBase}/attachments/${attachmentIdentifier}`; if (slug) { - return path + "/" + slug; + return `${path}/${slug}`; } else { return path; } } + public attachmentContentPath(attachmentIdentifier:number|string) { + return `${this.staticBase}/attachments/${attachmentIdentifier}/content`; + } + public ifcModelsPath(projectIdentifier:string) { - return this.staticBase + `/projects/${projectIdentifier}/ifc_models`; + return `${this.staticBase}/projects/${projectIdentifier}/ifc_models`; } public bimDetailsPath(projectIdentifier:string, workPackageId:string, viewpoint:number|string|null = null) { - let path = this.projectPath(projectIdentifier) + `/bcf/split/details/${workPackageId}`; + let path = `${this.projectPath(projectIdentifier)}/bcf/split/details/${workPackageId}`; if (viewpoint !== null) { path += `?viewpoint=${viewpoint}`; @@ -119,95 +123,95 @@ export class PathHelperService { } public highlightingCssPath() { - return this.staticBase + '/highlighting/styles'; + return `${this.staticBase}/highlighting/styles`; } public forumPath(projectIdentifier:string, forumIdentifier:string) { - return this.projectForumPath(projectIdentifier) + '/' + forumIdentifier; + return `${this.projectForumPath(projectIdentifier)}/${forumIdentifier}`; } public keyboardShortcutsHelpPath() { - return this.staticBase + '/help/keyboard_shortcuts'; + return `${this.staticBase}/help/keyboard_shortcuts`; } public messagePath(messageIdentifier:string) { - return this.staticBase + '/topics/' + messageIdentifier; + return `${this.staticBase}/topics/${messageIdentifier}`; } public myPagePath() { - return this.staticBase + '/my/page'; + return `${this.staticBase}/my/page`; } public newsPath(newsId:string) { - return this.staticBase + '/news/' + newsId; + return `${this.staticBase}/news/${newsId}`; } public loginPath() { - return this.staticBase + '/login'; + return `${this.staticBase}/login`; } public projectsPath() { - return this.staticBase + '/projects'; + return `${this.staticBase}/projects`; } public projectPath(projectIdentifier:string) { - return this.projectsPath() + '/' + projectIdentifier; + return `${this.projectsPath()}/${projectIdentifier}`; } public projectActivityPath(projectIdentifier:string) { - return this.projectPath(projectIdentifier) + '/activity'; + return `${this.projectPath(projectIdentifier)}/activity`; } public projectForumPath(projectIdentifier:string) { - return this.projectPath(projectIdentifier) + '/forums'; + return `${this.projectPath(projectIdentifier)}/forums`; } public projectCalendarPath(projectId:string) { - return this.projectPath(projectId) + '/work_packages/calendar'; + return `${this.projectPath(projectId)}/work_packages/calendar`; } public projectMembershipsPath(projectId:string) { - return this.projectPath(projectId) + '/members'; + return `${this.projectPath(projectId)}/members`; } public projectNewsPath(projectId:string) { - return this.projectPath(projectId) + '/news'; + return `${this.projectPath(projectId)}/news`; } public projectTimeEntriesPath(projectIdentifier:string) { - return this.projectPath(projectIdentifier) + '/cost_reports'; + return `${this.projectPath(projectIdentifier)}/cost_reports`; } public projectWikiPath(projectId:string) { - return this.projectPath(projectId) + '/wiki'; + return `${this.projectPath(projectId)}/wiki`; } public projectWorkPackagePath(projectId:string, wpId:string|number) { - return this.projectWorkPackagesPath(projectId) + '/' + wpId; + return `${this.projectWorkPackagesPath(projectId)}/${wpId}`; } public projectWorkPackagesPath(projectId:string) { - return this.projectPath(projectId) + '/work_packages'; + return `${this.projectPath(projectId)}/work_packages`; } public projectWorkPackageNewPath(projectId:string) { - return this.projectWorkPackagesPath(projectId) + '/new'; + return `${this.projectWorkPackagesPath(projectId)}/new`; } public projectBoardsPath(projectIdentifier:string|null) { if (projectIdentifier) { - return this.projectPath(projectIdentifier) + '/boards'; + return `${this.projectPath(projectIdentifier)}/boards`; } else { - return this.staticBase + '/boards'; + return `${this.staticBase}/boards`; } } public projectDashboardsPath(projectIdentifier:string) { - return this.projectPath(projectIdentifier) + '/dashboards'; + return `${this.projectPath(projectIdentifier)}/dashboards`; } public timeEntriesPath(workPackageId:string|number) { - var suffix = '/time_entries'; + let suffix = '/time_entries'; if (workPackageId) { return this.workPackagePath(workPackageId) + suffix; @@ -217,51 +221,50 @@ export class PathHelperService { } public usersPath() { - return this.staticBase + '/users'; + return `${this.staticBase}/users`; } public userPath(id:string|number) { - return this.usersPath() + '/' + id; + return `${this.usersPath()}/${id}`; } public versionsPath() { - return this.staticBase + '/versions'; + return `${this.staticBase}/versions`; } public versionEditPath(id:string|number) { - return this.staticBase + '/versions/' + id + '/edit'; + return `${this.staticBase}/versions/${id}/edit`; } public versionShowPath(id:string|number) { - return this.staticBase + '/versions/' + id; + return `${this.staticBase}/versions/${id}`; } public workPackagesPath() { - return this.staticBase + '/work_packages'; + return `${this.staticBase}/work_packages`; } public workPackagePath(id:string|number) { - return this.staticBase + '/work_packages/' + id; + return `${this.staticBase}/work_packages/${id}`; } public workPackageCopyPath(workPackageId:string|number) { - return this.workPackagePath(workPackageId) + '/copy'; + return `${this.workPackagePath(workPackageId)}/copy`; } public workPackageDetailsCopyPath(projectIdentifier:string, workPackageId:string|number) { - return this.projectWorkPackagesPath(projectIdentifier) + '/details/' + workPackageId + '/copy'; + return `${this.projectWorkPackagesPath(projectIdentifier)}/details/${workPackageId}/copy`; } public workPackagesBulkDeletePath() { - return this.workPackagesPath() + '/bulk'; + return `${this.workPackagesPath()}/bulk`; } public projectLevelListPath() { - return this.projectsPath() + '/level_list.json'; + return `${this.projectsPath()}/level_list.json`; } public textFormattingHelp() { - return this.staticBase + '/help/text_formatting'; + return `${this.staticBase}/help/text_formatting`; } - } diff --git a/frontend/src/global_styles/content/_forms.sass b/frontend/src/global_styles/content/_forms.sass index e231620259..c6abd6aa97 100644 --- a/frontend/src/global_styles/content/_forms.sass +++ b/frontend/src/global_styles/content/_forms.sass @@ -498,6 +498,7 @@ fieldset.form--fieldset %form--field-element-container display: block flex: 1 1 + max-width: 100% &:nth-last-of-type(n+2) padding-right: $block-padding diff --git a/lib/api/v3/work_packages/work_package_representer.rb b/lib/api/v3/work_packages/work_package_representer.rb index 8596390a14..84e5980a45 100644 --- a/lib/api/v3/work_packages/work_package_representer.rb +++ b/lib/api/v3/work_packages/work_package_representer.rb @@ -535,6 +535,7 @@ module API self_path = api_v3_paths.work_package_relations(represented.id) visible_relations = represented .visible_relations(current_user) + .direct .non_hierarchy .includes(::API::V3::Relations::RelationCollectionRepresenter.to_eager_load) diff --git a/lib/open_project/custom_styles/color_themes.rb b/lib/open_project/custom_styles/color_themes.rb index 2454647013..03c448007d 100644 --- a/lib/open_project/custom_styles/color_themes.rb +++ b/lib/open_project/custom_styles/color_themes.rb @@ -29,9 +29,11 @@ module OpenProject::CustomStyles class ColorThemes + OpenProject::CustomStyles::ColorThemes::DEFAULT_THEME_NAME = 'OpenProject'.freeze + THEMES = [ { - theme: 'OpenProject', + theme: OpenProject::CustomStyles::ColorThemes::DEFAULT_THEME_NAME, colors: { 'primary-color' => "#1A67A3", 'primary-color-dark' => "#175A8E", diff --git a/lib/open_project/version.rb b/lib/open_project/version.rb index c687a5cda4..fd0c451e4b 100644 --- a/lib/open_project/version.rb +++ b/lib/open_project/version.rb @@ -34,7 +34,7 @@ module OpenProject module VERSION #:nodoc: MAJOR = 11 MINOR = 0 - PATCH = 2 + PATCH = 3 TINY = PATCH # Redmine compat class << self diff --git a/modules/bim/db/migrate/20201105154216_seed_custom_style_with_bim_theme.rb b/modules/bim/db/migrate/20201105154216_seed_custom_style_with_bim_theme.rb new file mode 100644 index 0000000000..2f26f9a621 --- /dev/null +++ b/modules/bim/db/migrate/20201105154216_seed_custom_style_with_bim_theme.rb @@ -0,0 +1,101 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +# This migration cleans up messed up themes. Sometimes in the past +# the BIM theme was not set where it should have been set. +class SeedCustomStyleWithBimTheme < ActiveRecord::Migration[6.0] + + def up + # When + # migrating BIM instances + # that did not have any custom styles OR + # that do not have any design colors set and no custom logo/touch-icon/favicon + # (this basically means that no theme actually got applied) + # then + # add a custom style with the BIM theme set. This will write the theme's colors + # as DesignColor entries to the DB which is necessary for the theme to actually + # have an effect. + if OpenProject::Configuration.bim? && + (CustomStyle.current.nil? || + (DesignColor.count == 0 && + CustomStyle.current.favicon.nil? && + CustomStyle.current.logo.nil? && + CustomStyle.current.touch_icon.nil?)) + seed_bim_theme + end + end + + def down + # nop + end + + private + + def seed_bim_theme + CustomStyle.transaction do + set_custom_style + set_design_colors + end + end + + def set_design_colors + # There should not be any DesignColors present. However, we want to make sure. + DesignColor.delete_all + + theme[:colors].each do |param_variable, param_hexcode| + DesignColor.create variable: param_variable, hexcode: param_hexcode + end + end + + def set_custom_style + custom_style = (CustomStyle.current || CustomStyle.create!) + custom_style.attributes = { theme: theme[:theme], theme_logo: theme[:logo] } + custom_style.save! + custom_style + end + + def theme + { + theme: 'OpenProject BIM', + colors: { + 'primary-color' => "#3270DB", + 'primary-color-dark' => "#163473", + 'alternative-color' => "#349939", + 'header-bg-color' => "#05002C", + 'header-item-bg-hover-color' => "#163473", + 'content-link-color' => "#275BB5", + 'main-menu-bg-color' => "#0E2045", + 'main-menu-bg-selected-background' => "#3270DB", + 'main-menu-bg-hover-background' => "#163473" + }, + logo: 'bim/logo_openproject_bim_big.png' + } + end +end diff --git a/modules/boards/config/locales/crowdin/js-el.yml b/modules/boards/config/locales/crowdin/js-el.yml index f47c1dd550..d480874d5f 100644 --- a/modules/boards/config/locales/crowdin/js-el.yml +++ b/modules/boards/config/locales/crowdin/js-el.yml @@ -52,7 +52,7 @@ el: action_text_version: > Board with automated columns based on the version attribute. Ideal for planning product development. action_type: - assignee: assignee + assignee: Αναθέτης status: κατάσταση version: έκδοση subproject: subproject @@ -63,7 +63,7 @@ el: version: Έκδοση subproject: Υποέργο subtasks: Parent-Child - basic: Basic + basic: Βασικό select_attribute: "Χαρακτηριστικό ενέργειας" add_list_modal: labels: diff --git a/modules/boards/config/locales/crowdin/js-tr.yml b/modules/boards/config/locales/crowdin/js-tr.yml index 8c4162d64f..468ad92491 100644 --- a/modules/boards/config/locales/crowdin/js-tr.yml +++ b/modules/boards/config/locales/crowdin/js-tr.yml @@ -50,7 +50,7 @@ tr: action_text_assignee: > Atanan kullanıcılara göre otomatik sütunlara sahip pano. İş paketlerini göndermek için ideal. action_text_version: > - Board with automated columns based on the version attribute. Ideal for planning product development. + Sürüm özelliğine göre otomatik sütunlara sahip pano. Ürün geliştirmeyi planlamak için idealdir. action_type: assignee: vekil status: durum diff --git a/modules/boards/config/locales/crowdin/js-zh-CN.yml b/modules/boards/config/locales/crowdin/js-zh-CN.yml index 8e62a18b4c..545409f80a 100644 --- a/modules/boards/config/locales/crowdin/js-zh-CN.yml +++ b/modules/boards/config/locales/crowdin/js-zh-CN.yml @@ -63,7 +63,7 @@ zh-CN: version: 版本 subproject: 子项目 subtasks: 父子 - basic: 基本 + basic: 基础 select_attribute: "操作属性" add_list_modal: labels: @@ -77,7 +77,7 @@ zh-CN: 目前没有可用的状态。
可能没有问题,或者它们已被全部添加到问题板中。 assignee: 没有任何成员与您的筛选值匹配。
- no_member: 此项目当前没有可以添加的任何成员。
+ no_member: 此项目目前没有可以添加的成员。
add_members: 向此项目添加一个新成员以重新选择用户。 configuration_modal: title: '配置此面板' diff --git a/modules/job_status/config/locales/crowdin/js-el.yml b/modules/job_status/config/locales/crowdin/js-el.yml index 1d8edf9b2d..43736dcf2f 100644 --- a/modules/job_status/config/locales/crowdin/js-el.yml +++ b/modules/job_status/config/locales/crowdin/js-el.yml @@ -2,7 +2,7 @@ el: js: job_status: download_starts: 'The download should start automatically.' - click_to_download: 'Or click here to download.' + click_to_download: 'Πατήστε εδώ για λήψη' title: 'Background job status' redirect: 'You are being redirected.' redirect_link: 'Please click here to continue.' diff --git a/modules/ldap_groups/app/models/ldap_groups/membership.rb b/modules/ldap_groups/app/models/ldap_groups/membership.rb index 4a4793d874..c95f8e23c2 100644 --- a/modules/ldap_groups/app/models/ldap_groups/membership.rb +++ b/modules/ldap_groups/app/models/ldap_groups/membership.rb @@ -5,5 +5,7 @@ module LdapGroups class_name: '::LdapGroups::SynchronizedGroup', foreign_key: 'group_id', counter_cache: :users_count + + validates_uniqueness_of :user_id, scope: :group_id end end diff --git a/modules/ldap_groups/app/models/ldap_groups/synchronized_group.rb b/modules/ldap_groups/app/models/ldap_groups/synchronized_group.rb index fc3c28d7e2..5720516c16 100644 --- a/modules/ldap_groups/app/models/ldap_groups/synchronized_group.rb +++ b/modules/ldap_groups/app/models/ldap_groups/synchronized_group.rb @@ -23,31 +23,59 @@ module LdapGroups before_destroy :remove_all_members ## - # Add a set of new members to the internal group + # Add a set of new members to the synchronized group as well as the internal group. + # + # @param new_users [Array | Array] Users (or User IDs) to add to the group. def add_members!(new_users) + return if new_users.empty? + self.class.transaction do - users << new_users.map { |u| Membership.new group: self, user: u } - group.add_members!(new_users) + # create synchronized group memberships + memberships = new_users.map { |user| { group_id: self.id, user_id: user_id(user) } } + # Bulk insert the memberships to improve performance + ::LdapGroups::Membership.insert_all memberships + + # add users to users collection of internal group + group.add_members! new_users end end ## - # Remove a set of users from the internal group + # Remove a set of users from the synchronized group as well as the internal group. + # + # @param users_to_remove [Array | Array] Users (or User IDs) to remove from the group. def remove_members!(users_to_remove) - self.class.transaction do - user_ids = users_to_remove.pluck(:user_id) + return if users_to_remove.empty? - # We don't have access to the join table - # so we need to ensure we delete the users that are still present in the group - # since users MAY want to remove users manually - group.users.where(id: user_ids).destroy_all + self.class.transaction do + # 1) Delete synchronized group MEMBERSHIPS from collection. + # 2) Remove users from users collection of internal group. + if users_to_remove.first.is_a? User + users.delete users.where(user: users_to_remove).select(:id) + group.users.delete users_to_remove + elsif users_to_remove.first.is_a? Integer + users.delete users.where(user_id: users_to_remove).select(:id) + group.users.delete group.users.where(id: users_to_remove).select(:id) + else + raise ArgumentError, "Expected collection of Users or User IDs, got collection of #{users_to_remove.map(&:class).map(&:name).uniq.join(", ")}" + end end end private + def user_id(user) + if user.is_a? Integer + user + elsif user.is_a? User + user.id + else + raise ArgumentError, "Expected User or User ID (Integer) but got #{user}" + end + end + def remove_all_members - remove_members!(users) + remove_members! User.find(users.pluck(:user_id)) end end end diff --git a/modules/ldap_groups/lib/open_project/ldap_groups/synchronization.rb b/modules/ldap_groups/lib/open_project/ldap_groups/synchronization.rb index 8201545e3c..9d14954e87 100644 --- a/modules/ldap_groups/lib/open_project/ldap_groups/synchronization.rb +++ b/modules/ldap_groups/lib/open_project/ldap_groups/synchronization.rb @@ -100,15 +100,7 @@ module OpenProject::LdapGroups Rails.logger.info { "[LDAP groups] Adding #{new_member_ids.length} users to #{sync.dn}" } - # Bulk insert the memberships - memberships = new_member_ids.map do |user_id| - { - group_id: sync.id, - user_id: user_id - } - end - ::LdapGroups::Membership.insert_all memberships - sync.group.add_members! new_member_ids + sync.add_members! new_member_ids end ## @@ -120,8 +112,8 @@ module OpenProject::LdapGroups end Rails.logger.info "[LDAP groups] Removing users #{memberships.pluck(:user_id)} from #{sync.dn}" - sync.remove_members!(memberships) - memberships.delete_all + + sync.remove_members! memberships.pluck(:user_id) end end end diff --git a/modules/ldap_groups/spec/factories/synchronized_group_factory.rb b/modules/ldap_groups/spec/factories/synchronized_group_factory.rb index 16d8de8793..9cf745b0a1 100644 --- a/modules/ldap_groups/spec/factories/synchronized_group_factory.rb +++ b/modules/ldap_groups/spec/factories/synchronized_group_factory.rb @@ -5,4 +5,3 @@ FactoryBot.define do auth_source factory: :ldap_auth_source end end - diff --git a/modules/ldap_groups/spec/models/synchronized_group_spec.rb b/modules/ldap_groups/spec/models/synchronized_group_spec.rb index 49d7602b4a..e66c2c2199 100644 --- a/modules/ldap_groups/spec/models/synchronized_group_spec.rb +++ b/modules/ldap_groups/spec/models/synchronized_group_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe LdapGroups::SynchronizedGroup, type: :model do - subject { FactoryBot.build :ldap_synchronized_group } - describe 'validations' do + subject { FactoryBot.build :ldap_synchronized_group } + context 'correct attributes' do it 'saves the record' do expect(subject.save).to eq true @@ -20,4 +20,91 @@ describe LdapGroups::SynchronizedGroup, type: :model do end end end -end \ No newline at end of file + + describe 'manipulating members' do + let(:users) { [user_1, user_2] } + let(:user_1) { FactoryBot.create :user } + let(:user_2) { FactoryBot.create :user } + + describe '.add_members!' do + let(:synchronized_group) { FactoryBot.create :ldap_synchronized_group, group: group } + let(:group) { FactoryBot.create :group } + + shared_examples 'it adds users to the synchronized group and the internal one' do + let(:members) { raise "define me!" } + + before do + expect(synchronized_group.users).to be_empty + expect(group.users).to be_empty + + User.system.run_given do + synchronized_group.add_members! members + end + end + + it 'adds the user(s) to the internal group' do + expect(group.reload.users).to eq users + end + + it 'adds the user(s) to the synchronized group' do + expect(synchronized_group.reload.users.map(&:user)).to eq users + end + end + + context 'called with user records' do + it_behaves_like 'it adds users to the synchronized group and the internal one' do + let(:members) { users } + end + end + + context 'called just with user IDs' do + it_behaves_like 'it adds users to the synchronized group and the internal one' do + let(:members) { users.pluck(:id) } + end + end + end + + describe '.remove_members!' do + let(:synchronized_group) do + FactoryBot.create(:ldap_synchronized_group, group: group).tap do |sg| + group.users.each do |user| + sg.users.create user: user + end + end + end + let(:group) { FactoryBot.create :group, members: users } + + shared_examples 'it removes the users from the synchronized group and the internal one' do + let(:members) { raise "define me!" } + + before do + synchronized_group.remove_members! members + end + + it 'removes the user(s) from the internal group' do + expect(group.reload.users).to be_empty + end + + it 'removes the users(s) from the synchronized group' do + expect(synchronized_group.users).to be_empty + end + + it 'does not, however, delete the actual users!' do + expect(User.find(users.map(&:id))).to eq users + end + end + + context 'called with user records' do + it_behaves_like 'it removes the users from the synchronized group and the internal one' do + let(:members) { group.users } + end + end + + context 'called just with user IDs' do + it_behaves_like 'it removes the users from the synchronized group and the internal one' do + let(:members) { group.users.pluck(:id) } + end + end + end + end +end diff --git a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb index 7a3a73c98d..3e7d023788 100644 --- a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb @@ -1073,7 +1073,7 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do before do allow(work_package) - .to receive_message_chain(:visible_relations, :non_hierarchy, :includes) + .to receive_message_chain(:visible_relations, :direct, :non_hierarchy, :includes) .and_return([relation]) end diff --git a/spec/requests/api/v3/work_package_resource_spec.rb b/spec/requests/api/v3/work_package_resource_spec.rb index 01db2aba6b..e3daa475bd 100644 --- a/spec/requests/api/v3/work_package_resource_spec.rb +++ b/spec/requests/api/v3/work_package_resource_spec.rb @@ -310,6 +310,35 @@ describe 'API v3 Work package resource', .at_path('derivedDueDate') end end + + describe 'relations' do + let(:directly_related_wp) do + FactoryBot.create(:work_package, project_id: project.id) + end + let(:transitively_related_wp) do + FactoryBot.create(:work_package, project_id: project.id) + end + + let(:work_package) do + FactoryBot.create(:work_package, + project_id: project.id, + description: 'lorem ipsum').tap do |wp| + + FactoryBot.create(:relation, relates: 1, from: wp, to: directly_related_wp) + FactoryBot.create(:relation, relates: 1, from: directly_related_wp, to: transitively_related_wp) + end + end + + it 'embeds all direct relations' do + expect(subject) + .to be_json_eql(1.to_json) + .at_path('_embedded/relations/total') + + expect(subject) + .to be_json_eql(api_v3_paths.work_package(directly_related_wp.id).to_json) + .at_path('_embedded/relations/_embedded/elements/0/_links/to/href') + end + end end context 'requesting nonexistent work package' do