Release OpenProject 11.0.3

pull/8872/head v11.0.3
ulferts 4 years ago
commit b0a993b485
No known key found for this signature in database
GPG Key ID: A205708DE1284017
  1. 2
      Dockerfile
  2. 4
      app/models/design_color.rb
  3. 2
      config/locales/crowdin/el.yml
  4. 2
      config/locales/crowdin/js-el.yml
  5. 2
      config/locales/crowdin/ru.yml
  6. 8
      config/locales/crowdin/tr.yml
  7. 2
      config/locales/crowdin/zh-CN.yml
  8. 5
      db/migrate/20200114091135_add_theme_name_to_custom_styles.rb
  9. 6
      docs/development/README.md
  10. 13
      docs/development/report-a-bug/README.md
  11. 13
      docs/development/submit-feature-idea/README.md
  12. 23
      docs/installation-and-operations/installation/docker/README.md
  13. 3
      docs/installation-and-operations/installation/packaged/README.md
  14. 22
      docs/release-notes/11-0-3/README.md
  15. 7
      docs/release-notes/README.md
  16. 11
      docs/system-admin-guide/attribute-help-texts/README.md
  17. 29
      docs/user-guide/meetings/README.md
  18. 3
      docs/user-guide/projects/README.md
  19. 8
      docs/user-guide/work-packages/README.md
  20. 2
      docs/user-guide/work-packages/create-work-package/README.md
  21. 68
      docs/user-guide/wysiwyg/README.md
  22. BIN
      docs/user-guide/wysiwyg/image-20201109183018255.png
  23. 2
      frontend/src/app/modules/bim/ifc_models/ifc-viewer/ifc-viewer.service.ts
  24. 1
      frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.d.ts
  25. 78
      frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.js
  26. 76
      frontend/src/app/modules/bim/ifc_models/xeokit/xeokit-server.ts
  27. 2
      frontend/src/app/modules/common/gon/gon.service.ts
  28. 89
      frontend/src/app/modules/common/path-helper/path-helper.service.ts
  29. 1
      frontend/src/global_styles/content/_forms.sass
  30. 1
      lib/api/v3/work_packages/work_package_representer.rb
  31. 4
      lib/open_project/custom_styles/color_themes.rb
  32. 2
      lib/open_project/version.rb
  33. 101
      modules/bim/db/migrate/20201105154216_seed_custom_style_with_bim_theme.rb
  34. 4
      modules/boards/config/locales/crowdin/js-el.yml
  35. 2
      modules/boards/config/locales/crowdin/js-tr.yml
  36. 4
      modules/boards/config/locales/crowdin/js-zh-CN.yml
  37. 2
      modules/job_status/config/locales/crowdin/js-el.yml
  38. 2
      modules/ldap_groups/app/models/ldap_groups/membership.rb
  39. 50
      modules/ldap_groups/app/models/ldap_groups/synchronized_group.rb
  40. 14
      modules/ldap_groups/lib/open_project/ldap_groups/synchronization.rb
  41. 1
      modules/ldap_groups/spec/factories/synchronized_group_factory.rb
  42. 93
      modules/ldap_groups/spec/models/synchronized_group_spec.rb
  43. 2
      spec/lib/api/v3/work_packages/work_package_representer_spec.rb
  44. 29
      spec/requests/api/v3/work_package_resource_spec.rb

@ -13,7 +13,7 @@ ARG GITHUB_OAUTH_TOKEN
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ENV NODE_VERSION="12.18.3" ENV NODE_VERSION="12.18.3"
ENV BUNDLER_VERSION="2.0.2" ENV BUNDLER_VERSION="2.1.4"
ENV BUNDLE_PATH__SYSTEM=false ENV BUNDLE_PATH__SYSTEM=false
ENV APP_USER=app ENV APP_USER=app
ENV APP_PATH=/app ENV APP_PATH=/app

@ -28,10 +28,10 @@
class DesignColor < ApplicationRecord class DesignColor < ApplicationRecord
after_commit -> do 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 # 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 # 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 if CustomStyle.current
CustomStyle.current.touch CustomStyle.current.touch
else else

@ -2654,7 +2654,7 @@ el:
pre_authorization: pre_authorization:
status: 'Πριν την εξουσιοδότηση' status: 'Πριν την εξουσιοδότηση'
auth_url: 'Auth URL' auth_url: 'Auth URL'
access_token_url: 'Access token URL' access_token_url: 'Token πρόσβασης'
errors: errors:
messages: messages:
#Common error messages #Common error messages

@ -200,7 +200,7 @@ el:
status_confirmed: "επιβεβαιωμένο" status_confirmed: "επιβεβαιωμένο"
status_waiting: "το email απεστάλη - αναμονή για επιβεβαίωση" status_waiting: "το email απεστάλη - αναμονή για επιβεβαίωση"
test_ee: "Δοκιμάστε την Έκδοση για Επιχειρήσεις για 14 ημέρες δωρεάν " test_ee: "Δοκιμάστε την Έκδοση για Επιχειρήσεις για 14 ημέρες δωρεάν "
quick_overview: "Get a quick overview of project management and team collaboration with OpenProject Enterprise Edition." quick_overview: "Λάβετε μια γρήγορη επισκόπηση της διαχείρισης έργων και της ομαδικής συνεργασίας με το OpenProject."
upsale: upsale:
become_hero: "Γίνετε ήρωας!" become_hero: "Γίνετε ήρωας!"
benefits: benefits:

@ -538,7 +538,7 @@ ru:
invalid_url: 'не является допустимым URL-адресом.' invalid_url: 'не является допустимым URL-адресом.'
invalid_url_scheme: 'не является поддерживаемым протоколом (разрешены следующие: %{allowed_schemes}).' invalid_url_scheme: 'не является поддерживаемым протоколом (разрешены следующие: %{allowed_schemes}).'
less_than_or_equal_to: "должно быть меньше или равно %{count}." less_than_or_equal_to: "должно быть меньше или равно %{count}."
not_current_user: "is not the current user." not_current_user: "не является текущим пользователем."
not_a_date: "не является допустимой датой." not_a_date: "не является допустимой датой."
not_a_datetime: "дата и время не являются допустимыми." not_a_datetime: "дата и время не являются допустимыми."
not_a_number: "не является числом." not_a_number: "не является числом."

@ -537,7 +537,7 @@ tr:
invalid_url: 'geçerli bir adres değil.' invalid_url: 'geçerli bir adres değil.'
invalid_url_scheme: 'bu protokol desteklenmiyor (izin: %{allowed_schemes}).' 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." 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_date: "geçerli bir tarih değil."
not_a_datetime: "geçerli bir zaman değil." not_a_datetime: "geçerli bir zaman değil."
not_a_number: "bir sayı değil." not_a_number: "bir sayı değil."
@ -617,7 +617,7 @@ tr:
queries/filters/base: queries/filters/base:
attributes: attributes:
values: values:
inclusion: "filter has invalid values." inclusion: "filtrede geçersiz değerler var."
format: "%{message}" format: "%{message}"
relation: relation:
typed_dag: 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_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_category_reassign_to: "İş paketlerini bu kategoriye yeniden ata"
text_work_package_updated: "%{id} iş paketi %{author} tarafından güncellendi." 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_added: "%{id} iş paketine %{watcher_changer} tarafından izleyici olarak eklendiniz."
text_work_package_watcher_removed: "%{id} tarafından %{watcher_changer}. Work paketi saatlerinden çıkarıldınız" 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_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_work_packages_ref_in_commit_messages: "İş paketlerini taahhüt mesajlarında referans ve sabitleme"
text_journal_added: "%{value} %{label} eklendi" text_journal_added: "%{value} %{label} eklendi"

@ -531,7 +531,7 @@ zh-CN:
invalid_url: '不是有效的 URL。' invalid_url: '不是有效的 URL。'
invalid_url_scheme: '不是受支持的协议(允许:%{allowed_schemes})。' invalid_url_scheme: '不是受支持的协议(允许:%{allowed_schemes})。'
less_than_or_equal_to: "必须小于或等于 %{count}。" less_than_or_equal_to: "必须小于或等于 %{count}。"
not_current_user: "is not the current user." not_current_user: "不是当前用户。"
not_a_date: "不是有效的日期。" not_a_date: "不是有效的日期。"
not_a_datetime: "不是有效的日期时间。" not_a_datetime: "不是有效的日期时间。"
not_a_number: "不是一个数字。" not_a_number: "不是一个数字。"

@ -1,5 +1,8 @@
class AddThemeNameToCustomStyles < ActiveRecord::Migration[6.0] class AddThemeNameToCustomStyles < ActiveRecord::Migration[6.0]
def change 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
end end

@ -4,7 +4,7 @@ We are pleased that you are thinking about contributing to OpenProject! This gui
## Get in touch ## 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 ## 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 ## 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. 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.

@ -2,12 +2,13 @@
If you find a bug please create a bug report. If you find a bug please create a bug report.
1. Login to the [OpenProject developer platform](https://community.openproject.com/login). 1. Login to or register at the [OpenProject community platform](https://community.openproject.com/login). It's fast and free.
2. Open the [bug form](https://community.openproject.com/projects/openproject/work_packages/new?type=1). 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. Add a precise subject. 3. Open the [bug form](https://community.openproject.com/projects/openproject/work_packages/new?type=1).
3. Add a detailed description. 4. Add a precise subject.
4. Attach a file (optional). 5. Add a detailed description.
5. Press Create. 6. Attach a file (optional).
7. Press Create.
# Information you should add to the bug description # Information you should add to the bug description

@ -2,12 +2,13 @@
## How to submit a feature idea? ## How to submit a feature idea?
1. Login to the [OpenProject community platform](https://community.openproject.com/login). 1. Login to or register at the [OpenProject community platform](https://community.openproject.com/login). It's fast and free.
2. Open the [feature create form](https://community.openproject.com/projects/openproject/work_packages/create_new?type=6). 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. Add a subject and detailed description. 3. Open the [feature create form](https://community.openproject.com/projects/openproject/work_packages/create_new?type=6).
4. Attach a file (optional). 4. Add a subject and detailed description.
5. Set version to "Wish List". 5. Attach a file (optional).
6. Press Create. 6. Set version to "Wish List".
7. Press Create.
## Feature idea guideline ## Feature idea guideline

@ -17,27 +17,40 @@ installed.
OpenProject with Docker can be launched in two ways: 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. 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. 2. One container with all the processes inside. Easy but not recommended for production. This is the legacy behaviour.
## One container per process (recommended) ## One container per process (recommended)
### Quick Start ### 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 ```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 ```bash
docker-compose up -d 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 <http://localhost:8080>. 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: You can stop the Compose stack by running:

@ -75,6 +75,8 @@ sudo apt-get install openproject
Then finish the installation by reading the [*Initial configuration*][initial-config] section. Then finish the installation by reading the [*Initial configuration*][initial-config] section.
<iframe width="560" height="315" src="https://www.youtube.com/embed/MTHZVQ_89-k?controls=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
### Ubuntu 18.04 ### Ubuntu 18.04
Import the PGP key used to sign our packages: 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 * RHEL/CentOS 8
* Ubuntu 20.04 * Ubuntu 20.04
* Ubuntu 18.04
* Debian 10 * Debian 10
On older distributions, this wizard step won't be displayed, and the installation will default to the default edition. On older distributions, this wizard step won't be displayed, and the installation will default to the default edition.

@ -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.
<!--more-->
#### 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)\]

@ -12,6 +12,13 @@ Stay up to date and get an overview of the new features included in the releases
<!--- New release notes are generated below. Do not remove comment. --> <!--- New release notes are generated below. Do not remove comment. -->
<!--- RELEASE MARKER --> <!--- RELEASE MARKER -->
## 11.0.3
Release date: 2020-11-16
[Release Notes](11-0-3/)
## 11.0.2 ## 11.0.2
Release date: 2020-11-06 Release date: 2020-11-06

@ -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. | | [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? | | [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? | | [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. 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. 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) ![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).

@ -2,7 +2,7 @@
sidebar_navigation: sidebar_navigation:
title: Meetings title: Meetings
priority: 760 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 robots: index, follow
keywords: meetings 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 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.
<div class="glossary">**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.</div> <div class="glossary">**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. </div>
| Feature | Documentation for |
| Topic | Content |
| ------------------------------------------------------------ | ------------------------------------------- | | ------------------------------------------------------------ | ------------------------------------------- |
| [Meetings in OpenProject](#meetings-in-OpenProject) | How to open meetings in OpenProject? | | [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? | | [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? | | [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? | | [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? | | [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. 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) ![meetings](1567598397931.png)
## Create a new meeting ## 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). To create a new meeting, click on Meetings in the project menu. Click the green **+ Meeting** button.
Click the green **+ Meeting** button.
![create-meeting](create-meeting.png) ![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**. 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. 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. 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. 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. 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. 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 ## 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 **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. Don’t forget to **Save** your data.

@ -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. 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). Another way for using a template project would be to [copy it](#copy-a-project).
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/rhXYBrQQLBg?controls=0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
### Copy a project ### Copy a project

@ -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 packages have a **type**, an **ID**, a **subject** and may have various additional attributes, such as **status**, **assignee**, **priority**, **due date**.
<div class="glossary">**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).</div> <div class="glossary">**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). </div>
<div class="glossary"> <div class="glossary">
**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. </div>
</div>
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. 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.

@ -9,7 +9,7 @@ keywords: create Work packages
# 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? | | [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? | | [Add attachments to a Work package](#add-attachments-to-work-packages) | How to add attachments to a Work package? |

@ -3,29 +3,43 @@ sidebar_navigation:
title: Rich text editor title: Rich text editor
description: Find out about the CKEditor5 WYSIWYG editor in OpenProject description: Find out about the CKEditor5 WYSIWYG editor in OpenProject
robots: index, follow robots: index, follow
keywords: WYSIWYG, CKEditor keywords: WYSIWYG, CKEditor, Links to OpenProject resources
--- ---
# Rich text (WYSIWYG) editor in OpenProject # 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.
<div class="alert alert-info" role="alert">
**Please note:** In some resources such as work packages or comments, the editor does not exhibit all functionality such as macros or image upload.
</div>
| 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 ## Basic formatting
The CKEditor5 build in OpenProject supports basic text styles, such as bold and italic formatting, headings, 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. 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 ### 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 ### 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. 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/)). 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 ### 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. 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 ### Autoformatting
CKEditor5 allows certain CommonMark-like [autoformatting keyboard strokes](https://ckeditor5.github.io/docs/nightly/ckeditor5/latest/features/autoformat.html): 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 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 - Create a numbered list by starting the line with `1.` or `1)` and a space
## Image handling ## 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. 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 ### 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. 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 ## 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` - **commit by hash:** `commit:f30e13e4`
- **To a source file in the repository**: `source:"some/file"` - **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 subject of work package with id #1234**: `workPackageValue:1234:subject`
- **Linking to the current project's status**: `projectValue:status` - **Linking to the current project's status**: `projectValue:status`
- **Linking to the subject of work package with subject "Project start"**: `workPackageValue:"Project start":subject` - **Linking to the subject of work package with subject "Project start"**: `workPackageValue:"Project start":subject`
<div class="alert alert-info" role="alert"> <div class="alert alert-info" role="alert">
**Note**: Referencing a work package by subject results in only looking for work packages with that given subject in the current project (if any). If you need to cross-reference work packages, use their ID to pinpoint the work package you want to reference. **Note**: Referencing a work package by subject results in only looking for work packages with that given subject in the current project (if any). If you need to cross-reference work packages, use their ID to pinpoint the work package you want to reference.
@ -157,19 +165,24 @@ You can embed specific attributes of work packages or projects using the followi
</div> </div>
### 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.
</div>
**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 | | **Attribute** | Usage example |
| ------------------- | ------------------------------------------------------------ | | ------------------- | ------------------------------------------------------------ |
| *Custom Fields* | `workPackageValue:1234:"Name of the work package custom field"` |
| Assigned user | `workPackageValue:1234:assignee` | | Assigned user | `workPackageValue:1234:assignee` |
| Author | `workPackageValue:1234:author` | | Author | `workPackageValue:1234:author` |
| Category | `workPackageValue:1234:category` | | 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` | | Parent work package | `workPackageValue:1234:parent` |
| Priority | `workPackageValue:1234:priority` | | Priority | `workPackageValue:1234:priority` |
| Project | `workPackageValue:1234:project` | | Project | `workPackageValue:1234:project` |
| Remaining Time | `workPackageValue:1234:remainingTime` | | Remaining hours | `workPackageValue:1234:remainingTime` (sic!) |
| Responsible user | `workPackageValue:1234:responsible` | | Responsible user | `workPackageValue:1234:responsible` |
| Spent time | `workPackageValue:1234:spentTime` | | Spent time | `workPackageValue:1234:spentTime` |
| Start date | `workPackageValue:1234:startDate` | | 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` | | Work package type | `workPackageValue:1234:type` |
| Date of last update | `workPackageValue:1234:updatedAt` | | Date of last update | `workPackageValue:1234:updatedAt` |
| Version | `workPackageValue:1234:version` | | 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 | | **Attribute** | Usage example |
| ------------------------- | ------------------------------------------------- | | ------------------------- | ------------------------------------------------- |
| *Custom Fields* | `projectValue:"Name of the project custom field"` |
| Project active? (boolean) | `projectValue:active` | | Project active? (boolean) | `projectValue:active` |
| Description | `projectValue:description` | | Description | `projectValue:description` |
| Identifier of the project | `projectValue:identifier` | | 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` | | Status description | `projectValue:statusExplanation` |
| Parent project | `projectValue:parent` | | Parent project | `projectValue:parent` |
| Project public? (boolean) | `projectValue:public` | | Project public? (boolean) | `projectValue:public` |
| *Custom Fields* | `projectValue:"Name of the project custom field"` |

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@ -47,7 +47,7 @@ export class IFCViewerService extends ViewerBridgeService {
public newViewer(elements:XeokitElements, projects:any[]) { public newViewer(elements:XeokitElements, projects:any[]) {
import('@xeokit/xeokit-bim-viewer/dist/main').then((XeokitViewerModule: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); let viewerUI = new XeokitViewerModule.BIMViewer(server, elements);
viewerUI.on("queryPicked", (event:any) => { viewerUI.on("queryPicked", (event:any) => {

@ -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};

@ -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);
}
}

@ -27,6 +27,7 @@
// ++ // ++
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {IFCGonDefinition} from "../../bim/ifc_models/pages/viewer/ifc-models-data.service";
declare global { declare global {
interface Window { interface Window {
@ -36,6 +37,7 @@ declare global {
export interface GonType { export interface GonType {
[key:string]:unknown; [key:string]:unknown;
ifc_models:IFCGonDefinition;
} }
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })

@ -45,10 +45,10 @@ class Apiv3Paths {
* @param context * @param context
*/ */
public previewMarkup(context:string) { public previewMarkup(context:string) {
let base = this.apiV3Base + '/render/markdown'; let base = `${this.apiV3Base}/render/markdown`;
if (context) { if (context) {
return base + `?context=${context}`; return `${base}?context=${context}`;
} else { } else {
return base; return base;
} }
@ -85,7 +85,7 @@ class Apiv3Paths {
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class PathHelperService { export class PathHelperService {
public readonly appBasePath = window.appBasePath ? window.appBasePath : ''; public readonly appBasePath = window.appBasePath || '';
public readonly api = { public readonly api = {
v3: new Apiv3Paths(this.appBasePath) v3: new Apiv3Paths(this.appBasePath)
}; };
@ -95,21 +95,25 @@ export class PathHelperService {
} }
public attachmentDownloadPath(attachmentIdentifier:string, slug:string|undefined) { public attachmentDownloadPath(attachmentIdentifier:string, slug:string|undefined) {
let path = this.staticBase + '/attachments/' + attachmentIdentifier; let path = `${this.staticBase}/attachments/${attachmentIdentifier}`;
if (slug) { if (slug) {
return path + "/" + slug; return `${path}/${slug}`;
} else { } else {
return path; return path;
} }
} }
public attachmentContentPath(attachmentIdentifier:number|string) {
return `${this.staticBase}/attachments/${attachmentIdentifier}/content`;
}
public ifcModelsPath(projectIdentifier:string) { 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) { 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) { if (viewpoint !== null) {
path += `?viewpoint=${viewpoint}`; path += `?viewpoint=${viewpoint}`;
@ -119,95 +123,95 @@ export class PathHelperService {
} }
public highlightingCssPath() { public highlightingCssPath() {
return this.staticBase + '/highlighting/styles'; return `${this.staticBase}/highlighting/styles`;
} }
public forumPath(projectIdentifier:string, forumIdentifier:string) { public forumPath(projectIdentifier:string, forumIdentifier:string) {
return this.projectForumPath(projectIdentifier) + '/' + forumIdentifier; return `${this.projectForumPath(projectIdentifier)}/${forumIdentifier}`;
} }
public keyboardShortcutsHelpPath() { public keyboardShortcutsHelpPath() {
return this.staticBase + '/help/keyboard_shortcuts'; return `${this.staticBase}/help/keyboard_shortcuts`;
} }
public messagePath(messageIdentifier:string) { public messagePath(messageIdentifier:string) {
return this.staticBase + '/topics/' + messageIdentifier; return `${this.staticBase}/topics/${messageIdentifier}`;
} }
public myPagePath() { public myPagePath() {
return this.staticBase + '/my/page'; return `${this.staticBase}/my/page`;
} }
public newsPath(newsId:string) { public newsPath(newsId:string) {
return this.staticBase + '/news/' + newsId; return `${this.staticBase}/news/${newsId}`;
} }
public loginPath() { public loginPath() {
return this.staticBase + '/login'; return `${this.staticBase}/login`;
} }
public projectsPath() { public projectsPath() {
return this.staticBase + '/projects'; return `${this.staticBase}/projects`;
} }
public projectPath(projectIdentifier:string) { public projectPath(projectIdentifier:string) {
return this.projectsPath() + '/' + projectIdentifier; return `${this.projectsPath()}/${projectIdentifier}`;
} }
public projectActivityPath(projectIdentifier:string) { public projectActivityPath(projectIdentifier:string) {
return this.projectPath(projectIdentifier) + '/activity'; return `${this.projectPath(projectIdentifier)}/activity`;
} }
public projectForumPath(projectIdentifier:string) { public projectForumPath(projectIdentifier:string) {
return this.projectPath(projectIdentifier) + '/forums'; return `${this.projectPath(projectIdentifier)}/forums`;
} }
public projectCalendarPath(projectId:string) { public projectCalendarPath(projectId:string) {
return this.projectPath(projectId) + '/work_packages/calendar'; return `${this.projectPath(projectId)}/work_packages/calendar`;
} }
public projectMembershipsPath(projectId:string) { public projectMembershipsPath(projectId:string) {
return this.projectPath(projectId) + '/members'; return `${this.projectPath(projectId)}/members`;
} }
public projectNewsPath(projectId:string) { public projectNewsPath(projectId:string) {
return this.projectPath(projectId) + '/news'; return `${this.projectPath(projectId)}/news`;
} }
public projectTimeEntriesPath(projectIdentifier:string) { public projectTimeEntriesPath(projectIdentifier:string) {
return this.projectPath(projectIdentifier) + '/cost_reports'; return `${this.projectPath(projectIdentifier)}/cost_reports`;
} }
public projectWikiPath(projectId:string) { public projectWikiPath(projectId:string) {
return this.projectPath(projectId) + '/wiki'; return `${this.projectPath(projectId)}/wiki`;
} }
public projectWorkPackagePath(projectId:string, wpId:string|number) { public projectWorkPackagePath(projectId:string, wpId:string|number) {
return this.projectWorkPackagesPath(projectId) + '/' + wpId; return `${this.projectWorkPackagesPath(projectId)}/${wpId}`;
} }
public projectWorkPackagesPath(projectId:string) { public projectWorkPackagesPath(projectId:string) {
return this.projectPath(projectId) + '/work_packages'; return `${this.projectPath(projectId)}/work_packages`;
} }
public projectWorkPackageNewPath(projectId:string) { public projectWorkPackageNewPath(projectId:string) {
return this.projectWorkPackagesPath(projectId) + '/new'; return `${this.projectWorkPackagesPath(projectId)}/new`;
} }
public projectBoardsPath(projectIdentifier:string|null) { public projectBoardsPath(projectIdentifier:string|null) {
if (projectIdentifier) { if (projectIdentifier) {
return this.projectPath(projectIdentifier) + '/boards'; return `${this.projectPath(projectIdentifier)}/boards`;
} else { } else {
return this.staticBase + '/boards'; return `${this.staticBase}/boards`;
} }
} }
public projectDashboardsPath(projectIdentifier:string) { public projectDashboardsPath(projectIdentifier:string) {
return this.projectPath(projectIdentifier) + '/dashboards'; return `${this.projectPath(projectIdentifier)}/dashboards`;
} }
public timeEntriesPath(workPackageId:string|number) { public timeEntriesPath(workPackageId:string|number) {
var suffix = '/time_entries'; let suffix = '/time_entries';
if (workPackageId) { if (workPackageId) {
return this.workPackagePath(workPackageId) + suffix; return this.workPackagePath(workPackageId) + suffix;
@ -217,51 +221,50 @@ export class PathHelperService {
} }
public usersPath() { public usersPath() {
return this.staticBase + '/users'; return `${this.staticBase}/users`;
} }
public userPath(id:string|number) { public userPath(id:string|number) {
return this.usersPath() + '/' + id; return `${this.usersPath()}/${id}`;
} }
public versionsPath() { public versionsPath() {
return this.staticBase + '/versions'; return `${this.staticBase}/versions`;
} }
public versionEditPath(id:string|number) { public versionEditPath(id:string|number) {
return this.staticBase + '/versions/' + id + '/edit'; return `${this.staticBase}/versions/${id}/edit`;
} }
public versionShowPath(id:string|number) { public versionShowPath(id:string|number) {
return this.staticBase + '/versions/' + id; return `${this.staticBase}/versions/${id}`;
} }
public workPackagesPath() { public workPackagesPath() {
return this.staticBase + '/work_packages'; return `${this.staticBase}/work_packages`;
} }
public workPackagePath(id:string|number) { public workPackagePath(id:string|number) {
return this.staticBase + '/work_packages/' + id; return `${this.staticBase}/work_packages/${id}`;
} }
public workPackageCopyPath(workPackageId:string|number) { public workPackageCopyPath(workPackageId:string|number) {
return this.workPackagePath(workPackageId) + '/copy'; return `${this.workPackagePath(workPackageId)}/copy`;
} }
public workPackageDetailsCopyPath(projectIdentifier:string, workPackageId:string|number) { public workPackageDetailsCopyPath(projectIdentifier:string, workPackageId:string|number) {
return this.projectWorkPackagesPath(projectIdentifier) + '/details/' + workPackageId + '/copy'; return `${this.projectWorkPackagesPath(projectIdentifier)}/details/${workPackageId}/copy`;
} }
public workPackagesBulkDeletePath() { public workPackagesBulkDeletePath() {
return this.workPackagesPath() + '/bulk'; return `${this.workPackagesPath()}/bulk`;
} }
public projectLevelListPath() { public projectLevelListPath() {
return this.projectsPath() + '/level_list.json'; return `${this.projectsPath()}/level_list.json`;
} }
public textFormattingHelp() { public textFormattingHelp() {
return this.staticBase + '/help/text_formatting'; return `${this.staticBase}/help/text_formatting`;
} }
} }

@ -498,6 +498,7 @@ fieldset.form--fieldset
%form--field-element-container %form--field-element-container
display: block display: block
flex: 1 1 flex: 1 1
max-width: 100%
&:nth-last-of-type(n+2) &:nth-last-of-type(n+2)
padding-right: $block-padding padding-right: $block-padding

@ -535,6 +535,7 @@ module API
self_path = api_v3_paths.work_package_relations(represented.id) self_path = api_v3_paths.work_package_relations(represented.id)
visible_relations = represented visible_relations = represented
.visible_relations(current_user) .visible_relations(current_user)
.direct
.non_hierarchy .non_hierarchy
.includes(::API::V3::Relations::RelationCollectionRepresenter.to_eager_load) .includes(::API::V3::Relations::RelationCollectionRepresenter.to_eager_load)

@ -29,9 +29,11 @@
module OpenProject::CustomStyles module OpenProject::CustomStyles
class ColorThemes class ColorThemes
OpenProject::CustomStyles::ColorThemes::DEFAULT_THEME_NAME = 'OpenProject'.freeze
THEMES = [ THEMES = [
{ {
theme: 'OpenProject', theme: OpenProject::CustomStyles::ColorThemes::DEFAULT_THEME_NAME,
colors: { colors: {
'primary-color' => "#1A67A3", 'primary-color' => "#1A67A3",
'primary-color-dark' => "#175A8E", 'primary-color-dark' => "#175A8E",

@ -34,7 +34,7 @@ module OpenProject
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 11 MAJOR = 11
MINOR = 0 MINOR = 0
PATCH = 2 PATCH = 3
TINY = PATCH # Redmine compat TINY = PATCH # Redmine compat
class << self class << self

@ -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

@ -52,7 +52,7 @@ el:
action_text_version: > action_text_version: >
Board with automated columns based on the version attribute. Ideal for planning product development. Board with automated columns based on the version attribute. Ideal for planning product development.
action_type: action_type:
assignee: assignee assignee: Αναθέτης
status: κατάσταση status: κατάσταση
version: έκδοση version: έκδοση
subproject: subproject subproject: subproject
@ -63,7 +63,7 @@ el:
version: Έκδοση version: Έκδοση
subproject: Υποέργο subproject: Υποέργο
subtasks: Parent-Child subtasks: Parent-Child
basic: Basic basic: Βασικό
select_attribute: "Χαρακτηριστικό ενέργειας" select_attribute: "Χαρακτηριστικό ενέργειας"
add_list_modal: add_list_modal:
labels: labels:

@ -50,7 +50,7 @@ tr:
action_text_assignee: > action_text_assignee: >
Atanan kullanıcılara göre otomatik sütunlara sahip pano. İş paketlerini göndermek için ideal. Atanan kullanıcılara göre otomatik sütunlara sahip pano. İş paketlerini göndermek için ideal.
action_text_version: > 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: action_type:
assignee: vekil assignee: vekil
status: durum status: durum

@ -63,7 +63,7 @@ zh-CN:
version: 版本 version: 版本
subproject: 子项目 subproject: 子项目
subtasks: 父子 subtasks: 父子
basic: basic:
select_attribute: "操作属性" select_attribute: "操作属性"
add_list_modal: add_list_modal:
labels: labels:
@ -77,7 +77,7 @@ zh-CN:
目前没有可用的状态。<br> 目前没有可用的状态。<br>
可能没有问题,或者它们已被全部添加到问题板中。 可能没有问题,或者它们已被全部添加到问题板中。
assignee: 没有任何成员与您的筛选值匹配。<br> assignee: 没有任何成员与您的筛选值匹配。<br>
no_member: 此项目当前没有可以添加的任何成员。<br> no_member: 此项目目前没有可以添加的成员。 <br>
add_members: <a href="%{link}">向此项目添加一个新成员</a>以重新选择用户。 add_members: <a href="%{link}">向此项目添加一个新成员</a>以重新选择用户。
configuration_modal: configuration_modal:
title: '配置此面板' title: '配置此面板'

@ -2,7 +2,7 @@ el:
js: js:
job_status: job_status:
download_starts: 'The download should start automatically.' download_starts: 'The download should start automatically.'
click_to_download: 'Or click here to download.' click_to_download: 'Πατήστε εδώ για λήψη'
title: 'Background job status' title: 'Background job status'
redirect: 'You are being redirected.' redirect: 'You are being redirected.'
redirect_link: 'Please click here to continue.' redirect_link: 'Please click here to continue.'

@ -5,5 +5,7 @@ module LdapGroups
class_name: '::LdapGroups::SynchronizedGroup', class_name: '::LdapGroups::SynchronizedGroup',
foreign_key: 'group_id', foreign_key: 'group_id',
counter_cache: :users_count counter_cache: :users_count
validates_uniqueness_of :user_id, scope: :group_id
end end
end end

@ -23,31 +23,59 @@ module LdapGroups
before_destroy :remove_all_members 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<User> | Array<Integer>] Users (or User IDs) to add to the group.
def add_members!(new_users) def add_members!(new_users)
return if new_users.empty?
self.class.transaction do self.class.transaction do
users << new_users.map { |u| Membership.new group: self, user: u } # create synchronized group memberships
group.add_members!(new_users) 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
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<User> | Array<Integer>] Users (or User IDs) to remove from the group.
def remove_members!(users_to_remove) def remove_members!(users_to_remove)
self.class.transaction do return if users_to_remove.empty?
user_ids = users_to_remove.pluck(:user_id)
# We don't have access to the join table self.class.transaction do
# so we need to ensure we delete the users that are still present in the group # 1) Delete synchronized group MEMBERSHIPS from collection.
# since users MAY want to remove users manually # 2) Remove users from users collection of internal group.
group.users.where(id: user_ids).destroy_all 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
end end
private 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 def remove_all_members
remove_members!(users) remove_members! User.find(users.pluck(:user_id))
end end
end end
end end

@ -100,15 +100,7 @@ module OpenProject::LdapGroups
Rails.logger.info { "[LDAP groups] Adding #{new_member_ids.length} users to #{sync.dn}" } Rails.logger.info { "[LDAP groups] Adding #{new_member_ids.length} users to #{sync.dn}" }
# Bulk insert the memberships sync.add_members! new_member_ids
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
end end
## ##
@ -120,8 +112,8 @@ module OpenProject::LdapGroups
end end
Rails.logger.info "[LDAP groups] Removing users #{memberships.pluck(:user_id)} from #{sync.dn}" 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 end
end end

@ -5,4 +5,3 @@ FactoryBot.define do
auth_source factory: :ldap_auth_source auth_source factory: :ldap_auth_source
end end
end end

@ -1,9 +1,9 @@
require 'spec_helper' require 'spec_helper'
describe LdapGroups::SynchronizedGroup, type: :model do describe LdapGroups::SynchronizedGroup, type: :model do
subject { FactoryBot.build :ldap_synchronized_group }
describe 'validations' do describe 'validations' do
subject { FactoryBot.build :ldap_synchronized_group }
context 'correct attributes' do context 'correct attributes' do
it 'saves the record' do it 'saves the record' do
expect(subject.save).to eq true expect(subject.save).to eq true
@ -20,4 +20,91 @@ describe LdapGroups::SynchronizedGroup, type: :model do
end end
end end
end end
end
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

@ -1073,7 +1073,7 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
before do before do
allow(work_package) 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]) .and_return([relation])
end end

@ -310,6 +310,35 @@ describe 'API v3 Work package resource',
.at_path('derivedDueDate') .at_path('derivedDueDate')
end end
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 end
context 'requesting nonexistent work package' do context 'requesting nonexistent work package' do

Loading…
Cancel
Save