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

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

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

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

@ -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: "не является числом."

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

@ -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: "不是一个数字。"

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

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

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

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

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

@ -75,6 +75,8 @@ sudo apt-get install openproject
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
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.

@ -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. -->
<!--- RELEASE MARKER -->
## 11.0.3
Release date: 2020-11-16
[Release Notes](11-0-3/)
## 11.0.2
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. |
| [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)
![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:
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.
<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? |
| [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.

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

@ -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**.
<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">
**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>
**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>
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
| 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? |

@ -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.
<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
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`
<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.
@ -157,19 +165,24 @@ You can embed specific attributes of work packages or projects using the followi
</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 |
| ------------------- | ------------------------------------------------------------ |
| *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"` |

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[]) {
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) => {

@ -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 {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' })

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

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

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

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

@ -34,7 +34,7 @@ module OpenProject
module VERSION #:nodoc:
MAJOR = 11
MINOR = 0
PATCH = 2
PATCH = 3
TINY = PATCH # Redmine compat
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: >
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:

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

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

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

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

@ -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<User> | Array<Integer>] 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<User> | Array<Integer>] 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

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

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

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

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

Loading…
Cancel
Save