Merge remote-tracking branch 'origin/release/9.0' into dev

pull/7384/head
Oliver Günther 6 years ago
commit cc7eeb36b2
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 3
      app/assets/javascripts/onboarding/homescreen_tour.js
  2. 1
      app/assets/stylesheets/content/_table.sass
  3. 2
      app/assets/stylesheets/layout/_toolbar_mobile.sass
  4. 7
      app/assets/stylesheets/layout/work_packages/_details_view.sass
  5. 8
      app/assets/stylesheets/layout/work_packages/_full_view.sass
  6. 16
      app/models/attachment.rb
  7. 2
      app/models/project/copy.rb
  8. 2
      app/views/project_mailer/copy_project_succeeded.html.erb
  9. 2
      app/views/project_mailer/copy_project_succeeded.text.erb
  10. 10
      app/workers/copy_project_job.rb
  11. 24
      app/workers/extract_fulltext_job.rb
  12. 1
      config/locales/crowdin/af.yml
  13. 1
      config/locales/crowdin/ar.yml
  14. 1
      config/locales/crowdin/az.yml
  15. 1
      config/locales/crowdin/bg.yml
  16. 1
      config/locales/crowdin/ca.yml
  17. 1
      config/locales/crowdin/cs.yml
  18. 1
      config/locales/crowdin/da.yml
  19. 1
      config/locales/crowdin/de.yml
  20. 1
      config/locales/crowdin/el.yml
  21. 1
      config/locales/crowdin/es.yml
  22. 1
      config/locales/crowdin/et.yml
  23. 1
      config/locales/crowdin/fa.yml
  24. 1
      config/locales/crowdin/fi.yml
  25. 1
      config/locales/crowdin/fil.yml
  26. 1
      config/locales/crowdin/fr.yml
  27. 1
      config/locales/crowdin/he.yml
  28. 1
      config/locales/crowdin/hi.yml
  29. 1
      config/locales/crowdin/hr.yml
  30. 1
      config/locales/crowdin/hu.yml
  31. 1
      config/locales/crowdin/id.yml
  32. 1
      config/locales/crowdin/it.yml
  33. 1
      config/locales/crowdin/ja.yml
  34. 1
      config/locales/crowdin/ko.yml
  35. 1
      config/locales/crowdin/lol.yml
  36. 1
      config/locales/crowdin/lt.yml
  37. 1
      config/locales/crowdin/lv.yml
  38. 1
      config/locales/crowdin/ne-NP.yml
  39. 1
      config/locales/crowdin/nl.yml
  40. 1
      config/locales/crowdin/no.yml
  41. 1
      config/locales/crowdin/pl.yml
  42. 1
      config/locales/crowdin/pt-BR.yml
  43. 1
      config/locales/crowdin/pt.yml
  44. 1
      config/locales/crowdin/ro.yml
  45. 1
      config/locales/crowdin/ru.yml
  46. 1
      config/locales/crowdin/sk.yml
  47. 1
      config/locales/crowdin/sv-SE.yml
  48. 1
      config/locales/crowdin/th.yml
  49. 1
      config/locales/crowdin/tr.yml
  50. 1
      config/locales/crowdin/uk.yml
  51. 1
      config/locales/crowdin/vi.yml
  52. 1
      config/locales/crowdin/zh-TW.yml
  53. 1
      config/locales/crowdin/zh.yml
  54. 1
      config/locales/en.yml
  55. 23
      docker/mysql-to-postgres/bin/migrate-mysql-to-postgres
  56. 2
      frontend/src/app/angular4-modules.ts
  57. 61
      frontend/src/app/components/forms/forms-cache.service.ts
  58. 2
      frontend/src/app/components/wp-card-view/wp-card-view.component.html
  59. 9
      frontend/src/app/components/wp-card-view/wp-card-view.component.ts
  60. 17
      frontend/src/app/components/wp-edit-form/work-package-changeset.ts
  61. 3
      frontend/src/app/components/wp-table/configuration-modal/tabs/highlighting-tab.component.html
  62. 21
      frontend/src/app/components/wp-table/timeline/cells/timeline-cell-renderer.ts
  63. 3
      frontend/src/app/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts
  64. 1
      frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts
  65. 6
      frontend/src/app/modules/boards/board/board-actions/board-action.service.ts
  66. 4
      frontend/src/app/modules/boards/board/board-actions/status/status-action.service.ts
  67. 17
      frontend/src/app/modules/boards/board/board-actions/version/version-action.service.ts
  68. 2
      frontend/src/app/modules/boards/board/board-list/board-list.component.html
  69. 38
      frontend/src/app/modules/boards/board/board-list/board-list.component.ts
  70. 26
      frontend/src/app/modules/router/openproject.routes.ts
  71. 2
      lib/open_project/version.rb
  72. 9
      lib/tasks/attachments.rake
  73. 1
      modules/boards/config/locales/js-en.yml
  74. 29
      modules/boards/spec/features/action_boards/version_board_spec.rb
  75. 9
      modules/boards/spec/features/support/board_page.rb
  76. 2
      modules/meeting/app/assets/javascripts/meeting/meeting.js
  77. 2
      modules/meeting/app/models/meeting.rb
  78. 112
      modules/reporting/app/assets/stylesheets/reporting/_reporting.sass
  79. 121
      modules/reporting/app/assets/stylesheets/reporting/_reporting_group_by.sass
  80. 112
      modules/reporting/app/assets/stylesheets/reporting/_reporting_table.sass
  81. 7
      modules/reporting/app/assets/stylesheets/reporting/_reporting_table_print.sass
  82. 29
      modules/reporting/app/assets/stylesheets/reporting/reporting.sass
  83. 4
      modules/reporting/app/assets/stylesheets/reporting/reporting_styles.sass
  84. 3
      modules/reporting/app/views/cost_reports/_reporting_header.html.erb
  85. 5
      modules/reporting/lib/open_project/reporting/engine.rb
  86. 433
      modules/reporting_engine/lib/assets/stylesheets/reporting_engine/reporting.css.erb
  87. 23
      modules/reporting_engine/lib/assets/stylesheets/reporting_engine/reporting_engine.css
  88. 2
      modules/reporting_engine/lib/reporting_engine/engine.rb
  89. 4
      modules/reporting_engine/lib/widget/filters.rb
  90. 2
      modules/reporting_engine/lib/widget/settings.rb
  91. 13
      spec/features/work_packages/navigation_spec.rb
  92. 28
      spec/models/copy_project_job_spec.rb

@ -5,7 +5,8 @@
'next #top-menu': I18n.t('js.onboarding.steps.welcome'),
'skipButton': {className: 'enjoyhint_btn-transparent', text: I18n.t('js.onboarding.buttons.skip')},
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
'containerClass': '-hidden-arrow'
'containerClass': '-hidden-arrow',
'bottom': 7
},
{
'description': I18n.t('js.onboarding.steps.project_selection'),

@ -50,6 +50,7 @@ $input-elements: input, 'input.form--text-field', select, 'select.form--select',
overflow:
x: auto
y: auto
@include styled-scroll-bar
.generic-table--action-buttons
margin-top: 2rem

@ -38,7 +38,7 @@
background: #fff
display: flex
flex-wrap: wrap
justify-content: stretch
justify-content: flex-end
width: calc(100% + 10px)
> li

@ -79,8 +79,11 @@ body.router--work-packages-split-view-new
top: 50px
bottom: 55px
width: 100%
+allow-vertical-scrolling
padding: 0 15px 0 20px
overflow-x: hidden
overflow-y: scroll
padding: 0 5px 0 20px
@include styled-scroll-bar
.work-packages--details

@ -79,9 +79,10 @@
overflow-x: hidden
flex: 2
position: relative
@include styled-scroll-bar
.work-packages--panel-inner
padding: 5px 15px 20px 0px
padding: 0px 5px 20px 0
width: 100%
// These styles were taken over from the details tab styling.
@ -97,12 +98,13 @@
.work-packages-full-view--split-right
min-width: 500px
overflow-y: auto
overflow-y: scroll
overflow-x: auto
position: relative
@include styled-scroll-bar
.work-packages--panel-inner
padding: 15px 15px 0px 15px
padding: 15px 5px 0px 15px
.work-package-details-activities-activity-contents ul.work-package-details-activities-messages
padding-left: 0

@ -198,16 +198,26 @@ class Attachment < ActiveRecord::Base
# Extract the fulltext of any attachments where fulltext is still nil.
# This runs inline and not in a asynchronous worker.
def self.extract_fulltext_where_missing
def self.extract_fulltext_where_missing(run_now: true)
return unless OpenProject::Database.allows_tsv?
Attachment.where(fulltext: nil).pluck(:id).each do |id|
Attachment
.where(fulltext: nil)
.pluck(:id)
.each do |id|
job = ExtractFulltextJob.new(id)
job.perform
if run_now
job.perform
else
Delayed::Job.enqueue job, priority: ::ApplicationJob.priority_number(:low)
end
end
end
def self.force_extract_fulltext
return unless OpenProject::Database.allows_tsv?
Attachment.pluck(:id).each do |id|
job = ExtractFulltextJob.new(id)
job.perform

@ -43,6 +43,8 @@ module Project::Copy
def copy_attributes(project)
super
with_model(project) do |project|
# Clear enabled modules
self.enabled_modules = []
self.enabled_module_names = project.enabled_module_names
self.types = project.types
self.work_package_custom_fields = project.work_package_custom_fields

@ -33,5 +33,5 @@ See docs/COPYRIGHT.rdoc for more details.
<% unless @errors.empty? %>
<hr />
<%= render partial: 'errors', locals: { errors: @errors } %>
<%= render partial: 'user_mailer/errors', locals: { errors: @errors } %>
<% end %>

@ -31,5 +31,5 @@ See docs/COPYRIGHT.rdoc for more details.
<%= t('copy_project.text.succeeded', source_project_name: @source_project.name, target_project_name: @target_project.name) %>
<% unless @errors.empty? %>
----------------------------------------
<%= render partial: 'errors', locals: { errors: @errors } %>
<%= render partial: 'user_mailer/errors', locals: { errors: @errors } %>
<% end %>

@ -40,13 +40,14 @@ class CopyProjectJob < ApplicationJob
associations_to_copy:, send_mails: false)
@user_id = user_id
@source_project_id = source_project_id
@target_project_params = target_project_params
@target_project_params = target_project_params.with_indifferent_access
@associations_to_copy = associations_to_copy
@send_mails = send_mails
end
def perform
User.current = user
target_project_name = target_project_params[:name]
target_project, errors = with_locale_for(user) {
create_project_copy(source_project,
@ -58,10 +59,13 @@ class CopyProjectJob < ApplicationJob
if target_project
ProjectMailer.copy_project_succeeded(user, source_project, target_project, errors).deliver_now
else
target_project_name = target_project_params['name']
ProjectMailer.copy_project_failed(user, source_project, target_project_name, errors).deliver_now
end
rescue StandardError => e
logger.error { "Failed to finish copy project job: #{e} #{e.message}" }
errors = [I18n.t('copy_project.failed_internal')]
ProjectMailer.copy_project_failed(user, source_project, target_project_name, errors).deliver_now
end
private

@ -60,7 +60,7 @@ class ExtractFulltextJob < ApplicationJob
resolver = Plaintext::Resolver.new(@file, @attachment.content_type)
@text = resolver.text
end
rescue => e
rescue StandardError => e
Rails.logger.error(
"Failed to extract plaintext from file #{@attachment&.id} (On domain #{Setting.host_name}): #{e}: #{e.message}"
)
@ -68,14 +68,20 @@ class ExtractFulltextJob < ApplicationJob
end
def update
Attachment
.where(id: @attachment_id)
.update_all(['fulltext = ?, fulltext_tsv = to_tsvector(?, ?), file_tsv = to_tsvector(?, ?)',
@text,
@language,
OpenProject::FullTextSearch.normalize_text(@text),
@language,
OpenProject::FullTextSearch.normalize_filename(@filename)])
begin
Attachment
.where(id: @attachment_id)
.update_all(['fulltext = ?, fulltext_tsv = to_tsvector(?, ?), file_tsv = to_tsvector(?, ?)',
@text,
@language,
OpenProject::FullTextSearch.normalize_text(@text),
@language,
OpenProject::FullTextSearch.normalize_filename(@filename)])
rescue StandardError => e
Rails.logger.error(
"Failed to update TSV values for attachment #{@attachment&.id} (On domain #{Setting.host_name}): #{e.message[0..499]}[...]"
)
end
end
def find_attachment(id)

@ -857,6 +857,7 @@ af:
begin. Jy sal deur epos in kennis gestel word sodra "%{target_project_name}"
beskikbaar is.
failed: Kan nie projek %{source_project_name} kopieer nie
failed_internal: Copying failed due to an internal error.
succeeded: Projek %{target_project_name} geskep
errors: Fout
project_custom_fields: Custom fields on project

@ -866,6 +866,7 @@ ar:
started: بدأ بنسخ المشروع "%{source_project_name}" إلى "%{target_project_name}".
سيتم إبلاغك بالبريد الإلكتروني حالما يتوفر "%{target_project_name}".
failed: لا يمكن نسخ المشروع %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: المشروع الذي تم إنشاؤه %{target_project_name}
errors: خطأ
project_custom_fields: Custom fields on project

@ -856,6 +856,7 @@ az:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Cannot copy project %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Created project %{target_project_name}
errors: Error
project_custom_fields: Custom fields on project

@ -858,6 +858,7 @@ bg:
Ще бъдете информирани по пощата веднага, след като "%{target_project_name}"
е достъпен.
failed: Не може да се копира проект %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Създаден проект %{target_project_name}
errors: Грешка
project_custom_fields: Custom fields on project

@ -865,6 +865,7 @@ ca:
Se li comunicarà per correu electrònic tan aviat com "%{target_project_name}"
estigui disponible.
failed: No es pot copiar el projecte %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Projecte %{target_project_name} creat
errors: Error
project_custom_fields: Custom fields on project

@ -864,6 +864,7 @@ cs:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Nebylo možné zkopírovat projekt "%{source_project_name}"
failed_internal: Copying failed due to an internal error.
succeeded: Projekt "%{target_project_name}" vytvořen
errors: Chyba
project_custom_fields: Vlastní pole projektu

@ -854,6 +854,7 @@ da:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Kan ikke kopiere projektet %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Oprettet projektet %{target_project_name}
errors: Fejl
project_custom_fields: Custom fields on project

@ -886,6 +886,7 @@ de:
wurde gestartet. Sie werden per Mail benachrichtigt, sobald "%{target_project_name}"
zur Verfügung steht.
failed: Das Projekt %{source_project_name} konnte nicht kopiert werden
failed_internal: Copying failed due to an internal error.
succeeded: Das Projekt %{target_project_name} wurde erstellt
errors: Fehler
project_custom_fields: Benutzerdefinierte Felder des Projekts

@ -856,6 +856,7 @@ el:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Cannot copy project %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Created project %{target_project_name}
errors: Error
project_custom_fields: Custom fields on project

@ -883,6 +883,7 @@ es:
started: Comenzó la copia del proyecto '%{source_project_name}' a "%{target_project_name}".
Se le informará por correo tan pronto como esté disponible el proyecto "%{target_project_name}".
failed: No se puede copiar proyecto %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Proyecto creado %{target_project_name}
errors: Error
project_custom_fields: Campos personalizados del proyecto

@ -853,6 +853,7 @@ et:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Projekti %{source_project_name} ei saa kopeerida
failed_internal: Copying failed due to an internal error.
succeeded: Projekt %{target_project_name} on loodud
errors: Tõrge
project_custom_fields: Custom fields on project

@ -854,6 +854,7 @@ fa:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Cannot copy project %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Created project %{target_project_name}
errors: Error
project_custom_fields: نمایش فیلد های نمایشی در چاپ

@ -854,6 +854,7 @@ fi:
started: Projektin "%{source_project_name}" kopiointi uudeksi projektiksi "%{target_project_name}"
alkoi. Sinulle ilmoitetaan sähköpostitse, kun "%{target_project_name}" on saatavilla.
failed: Ei voi kopioida projektia %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Projekti %{target_project_name} luotu
errors: Virhe
project_custom_fields: Custom fields on project

@ -884,6 +884,7 @@ fil:
Pinapaalam ka sa pamamagitan ng mail sa lalong madaling panahon na ang "%{target_project_name}"
ay magagamit.
failed: Hindi makopya ang proyekto %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Nilikhang proyekto %{target_project_name}
errors: Mali
project_custom_fields: Ang mga custom na patlang sa proyekto

@ -881,6 +881,7 @@ fr:
a débuté. Vous serez informé par courriel dès que "%{target_project_name}" sera
disponible.
failed: Impossible de copier le projet %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Projet %{target_project_name} créé
errors: Erreur
project_custom_fields: Champs personnalisés du projet

@ -858,6 +858,7 @@ he:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: אין אפשרות להעתיק את הפרויקט %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: נוצר הפרויקט %{target_project_name}
errors: שגיאה
project_custom_fields: Custom fields on project

@ -862,6 +862,7 @@ hi:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: ट करतििि बन समभव नह %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: ट बन %{target_project_name}
errors: ि
project_custom_fields: Custom fields on project

@ -863,6 +863,7 @@ hr:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Nije moguće kopirati projekt %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Dodan je projekt %{target_project_name}
errors: Greška
project_custom_fields: Prilagođena polja na projektu

@ -856,6 +856,7 @@ hu:
a "%{target_project_name}" projektbe. E-mailben értesítjük, amint "%{target_project_name}"
elérhető.
failed: 'Nem lehet másolni a %{source_project_name} projektet '
failed_internal: Copying failed due to an internal error.
succeeded: 'A %{target_project_name} projekt létrehozva '
errors: Hiba
project_custom_fields: Projekt egyéni mezői

@ -852,6 +852,7 @@ id:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Tidak dapat meng-copy Project %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Project %{target_project_name} telah dibuat
errors: Eror
project_custom_fields: Field kustom pada proyek

@ -876,6 +876,7 @@ it:
started: Copia del progetto "%{source_project_name}" a "%{target_project_name}"
iniziata. Sarete informati via mail appena "%{target_project_name}" è disponibile.
failed: Non è possibile copiare il progetto %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Creato il progetto %{target_project_name}
errors: Errore
project_custom_fields: Campi personalizzati sul progetto

@ -779,6 +779,7 @@ ja:
copy_project:
started: プロジェクト「%{source_project_name}」を「%{target_project_name}」にコピーを開始しました。「%{target_project_name}」が利用可能になり次第、メールで通知されます。
failed: プロジェクト「%{source_project_name}」をコピーできません。
failed_internal: Copying failed due to an internal error.
succeeded: "「%{target_project_name}」プロジェクトを作成しました。"
errors: エラー!
project_custom_fields: プロジェクトのカスタムフィールド

@ -801,6 +801,7 @@ ko:
started: 프로젝트 "%{source_project_name}"을(를) "%{target_project_name}"(으)로 복사하기 시작했습니다.
"%{target_project_name}"이(가) 사용 가능하게 되면 메일로 알림이 전송됩니다.
failed: 프로젝트 %{source_project_name}을(를) 복사할 수 없습니다.
failed_internal: Copying failed due to an internal error.
succeeded: 프로젝트 %{target_project_name} 생성함
errors: 오류
project_custom_fields: 프로젝트의 사용자 지정 필드

@ -772,6 +772,7 @@ lol:
copy_project:
started: crwdns123800:0%{source_project_name}crwdnd123800:0%{target_project_name}crwdnd123800:0%{target_project_name}crwdne123800:0
failed: crwdns123802:0%{source_project_name}crwdne123802:0
failed_internal: Copying failed due to an internal error.
succeeded: crwdns123804:0%{target_project_name}crwdne123804:0
errors: crwdns123806:0crwdne123806:0
project_custom_fields: crwdns123808:0crwdne123808:0

@ -864,6 +864,7 @@ lt:
started: Pradėtas kopijuoti „%{source_project_name}“ projektas į „%{target_project_name}“.
Jums bus pranešta laišku, kai tik „%{target_project_name}“ projektas taps pasiekiamas.
failed: Negalima kopijuoti „%{source_project_name}“ projekto
failed_internal: Copying failed due to an internal error.
succeeded: Sukurtas projektas pavadinimu „%{target_project_name}“
errors: Klaida
project_custom_fields: Vartotojo laukai projektui

@ -856,6 +856,7 @@ lv:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Cannot copy project %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Created project %{target_project_name}
errors: Error
project_custom_fields: Custom fields on project

@ -857,6 +857,7 @@ ne-NP:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: Cannot copy project %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Created project %{target_project_name}
errors: Error
project_custom_fields: Custom fields on project

@ -871,6 +871,7 @@ nl:
U wordt zo spoedig mogelijk per email op de hoogte gesteld wanneer "%{target_project_name}"
beschikbaar is.
failed: Kan project niet kopiëren %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Gemaakte project %{target_project_name}
errors: Fout
project_custom_fields: Aangepaste velden in het project

@ -854,6 +854,7 @@
started: Kopiering av prosjektet "%{source_project_name}" til "%{target_project_name}"
er påbegynt. Du får beskjed på e-post så fort "%{target_project_name}" er tilgjengelig.
failed: Kan ikke kopierer prosjektet %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Opprettet prosjektet %{target_project_name}
errors: Feil
project_custom_fields: Custom fields on project

@ -865,6 +865,7 @@ pl:
Poinformujemy Cię przez e-mail tak szybko jak "%{target_project_name}" będzie
dostępny.
failed: Nie można skopiować projektu %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Utworzono projekt %{target_project_name}
errors: Błąd
project_custom_fields: Niestandardowe pola projektu

@ -861,6 +861,7 @@ pt-BR:
iniciada. Você será informado por email tão logo "%{target_project_name}" estiver
disponível.
failed: Não é possível copiar o projeto %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Foi criado o projeto %{target_project_name}
errors: Erro
project_custom_fields: Campos personalizados no projeto

@ -873,6 +873,7 @@ pt:
Será informado por E-mail, assim que o projeto "%{target_project_name}" esteja
disponível.
failed: Não é possível copiar o projecto %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Criado o projecto %{target_project_name}
errors: Erro
project_custom_fields: Campos personalizados no projeto

@ -871,6 +871,7 @@ ro:
started: A început copierea proiectului "%{source_project_name}" în "%{target_project_name}".
Veţi fi informat prin mail de îndată ce "%{target_project_name}" este disponibil.
failed: Proiectul %{source_project_name} nu poate fi copiat
failed_internal: Copying failed due to an internal error.
succeeded: Proiectul %{target_project_name} a fost creat
errors: Eroare
project_custom_fields: Câmpuri personalizate pentru proiect

@ -870,6 +870,7 @@ ru:
началось. Вы будете проинформированы по электронной почте, как только «%{target_project_name}»
будет доступен.
failed: Не удается скопировать проект %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Созданный проект %{target_project_name}
errors: Ошибка
project_custom_fields: Настраиваемые поля проекта

@ -876,6 +876,7 @@ sk:
started: Začalo sa kopírovanie projektu "%{source_project_name}" do projektu "%{target_project_name}".
Budete informovaní mailom hneď ako bude "%{target_project_name}" k dispozícii.
failed: Nie je možné kopírovať projekt %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Projekt %{target_project_name} bol vytvorený
errors: Chyba
project_custom_fields: Vlastné polia projektu

@ -857,6 +857,7 @@ sv-SE:
started: Började kopiera projekt "%{source_project_name}" till "%{target_project_name}".
Du kommer att informeras via E-post när "%{target_project_name}" finns tillgängligt.
failed: Kan inte kopiera projekt %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Skapade projektet %{target_project_name}
errors: Fel
project_custom_fields: Custom fields on project

@ -849,6 +849,7 @@ th:
started: Started to copy project "%{source_project_name}" to "%{target_project_name}".
You will be informed by mail as soon as "%{target_project_name}" is available.
failed: ไมสามารถดำเนนการคดลอกโครงการ %{source_project_name} ได
failed_internal: Copying failed due to an internal error.
succeeded: สรางโครงการ %{target_project_name} แล
errors: อผดพลาด
project_custom_fields: Custom fields on project

@ -856,6 +856,7 @@ tr:
bir projeye kopyalanmaya başladı. "%{target_project_name}" projesi hazır olduğunda
e-posta ile bilgilendirileceksiniz.'
failed: "%{source_project_name} project kopyalanamadı"
failed_internal: Copying failed due to an internal error.
succeeded: "%{target_project_name} project oluşturuldu"
errors: Hata
project_custom_fields: Projedeki özel alanlar

@ -874,6 +874,7 @@ uk:
Вас буде проінформовано за допомогою електронної пошти як тільки "%{target_project_name}"
буде доступний.
failed: Неможливо скопіювати проект %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Створений проект %{target_project_name}
errors: Помилка
project_custom_fields: Спеціальні поля в проекті

@ -856,6 +856,7 @@ vi:
started: Bắt đầu sao chép dự án "%{source_project_name}" sang "%{target_project_name}".
Bạn sẽ được thông báo bằng thư ngay sau khi "%{target_project_name}" sẵn sàng.
failed: Không thể sao chép dự án %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: Lập dự án %{target_project_name}
errors: Lỗi
project_custom_fields: Custom fields on project

@ -785,6 +785,7 @@ zh-TW:
started: 開始複製專案從「%{source_project_name}」到 「%{target_project_name}」。當「%{target_project_name}」
可以使用以後,我們會盡快用郵件通知您。
failed: 無法複製專案 %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: 專案 %{target_project_name} 建立成功
errors: 錯誤
project_custom_fields: 專案上的自訂欄位

@ -778,6 +778,7 @@ zh:
copy_project:
started: 开始将项目“%{source_project_name}”复制到“%{target_project_name}“。“%{target_project_name}”可用后您将会立即收到电子邮件通知。
failed: 无法复制项目 %{source_project_name}
failed_internal: Copying failed due to an internal error.
succeeded: 创建的项目 %{target_project_name}
errors: 错误
project_custom_fields: 项目上的自定义字段

@ -839,6 +839,7 @@ en:
copy_project:
started: "Started to copy project \"%{source_project_name}\" to \"%{target_project_name}\". You will be informed by mail as soon as \"%{target_project_name}\" is available."
failed: "Cannot copy project %{source_project_name}"
failed_internal: "Copying failed due to an internal error."
succeeded: "Created project %{target_project_name}"
errors: "Error"
project_custom_fields: 'Custom fields on project'

@ -156,6 +156,15 @@ if migrate_status != 0
puts "\nMigration failed. See above."
end
puts "\nRe-building DAG to create Postgres-specific indices"
_, dag_status = run "bundle exec rake db:migrate:redo VERSION=20180105130053", record_output: false
if dag_status != 0
puts "\nRebuild DAG migration failed. See above."
exit 1
end
if needs_fulltext_migration
drop_version_cmd = "PGPASSWORD=#{db_password} psql -U #{db_user} -h #{db_host} -p #{db_port} -d #{db_name} -c \"delete from schema_migrations where version = '20180122135443'\""
drop_version_output, drop_version_status = run drop_version_cmd, silent: true
@ -177,22 +186,16 @@ if needs_fulltext_migration
exit 1
end
_, extract_status = run "bundle exec rake attachments:extract_fulltext_where_missing", record_output: false
_, extract_status = run "bundle exec rake attachments:schedule_fulltext_extraction_where_missing", record_output: false
if extract_status != 0
if extract_status == 0
puts "\nFull-text extraction for attachments have successfully been requested in background jobs."
else
puts "\nFull-text extraction failed. See above."
exit 1
end
end
puts "\nRe-building DAG to create Postgres-specific indices"
_, dag_status = run "bundle exec rake db:migrate:redo VERSION=20180105130053", record_output: false
if dag_status != 0
puts "\nRebuild DAG migration failed. See above."
exit 1
end
puts "Migration from MySQL to Postgres completed successfully!"

@ -85,6 +85,7 @@ import {MainMenuToggleComponent} from "core-components/main-menu/main-menu-toggl
import {MainMenuNavigationService} from "core-components/main-menu/main-menu-navigation.service";
import {StatusCacheService} from "core-components/statuses/status-cache.service";
import {VersionCacheService} from "core-components/versions/version-cache.service";
import {FormsCacheService} from "core-components/forms/forms-cache.service";
@NgModule({
imports: [
@ -135,6 +136,7 @@ import {VersionCacheService} from "core-components/versions/version-cache.servic
OpTitleService,
UrlParamsHelperService,
ProjectCacheService,
FormsCacheService,
UserCacheService,
StatusCacheService,
VersionCacheService,

@ -0,0 +1,61 @@
import {Injectable} from '@angular/core';
import {SchemaResource} from 'core-app/modules/hal/resources/schema-resource';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {HalResourceService} from 'core-app/modules/hal/services/hal-resource.service';
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// 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-2013 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 doc/COPYRIGHT.rdoc for more details.
// ++
import {input, InputState, multiInput, MultiInputState, State} from 'reactivestates';
import {States} from '../states.service';
import {FormResource} from "core-app/modules/hal/resources/form-resource";
import {StateCacheService} from "core-components/states/state-cache.service";
@Injectable()
export class FormsCacheService extends StateCacheService<FormResource> {
private $formCache = multiInput<FormResource>();
constructor(private readonly halResourceService:HalResourceService) {
super();
}
protected load(href:string):Promise<FormResource> {
return this.halResourceService
.post<FormResource>(href, {})
.toPromise();
}
protected loadAll(ids:string[]):Promise<undefined> {
return Promise
.all(ids.map(id => this.load(id)))
.then(_ => undefined);
}
protected get multiState():MultiInputState<FormResource> {
return this.$formCache;
}
}

@ -13,7 +13,7 @@
[attr.data-is-new]="wp.isNew || undefined"
[attr.data-work-package-id]="wp.id"
(doubleClickOrTap)="handleDblClick(wp)"
[ngClass]="{'-draggable': dragOutOf, '-new' : wp.isNew }">
[ngClass]="{'-draggable': canDragOutOf(wp), '-new' : wp.isNew }">
<div class="wp-card--highlighting"
[ngClass]="cardHighlightingClass(wp)">

@ -40,8 +40,8 @@ import {RequestSwitchmap} from "core-app/helpers/rxjs/request-switchmap";
changeDetection: ChangeDetectionStrategy.OnPush
})
export class WorkPackageCardViewComponent implements OnInit {
@Input('dragOutOfHandler') public canDragOutOf:(wp:WorkPackageResource) => boolean;
@Input() public dragInto:boolean;
@Input() public dragOutOf:boolean;
@Input() public highlightingMode:CardHighlightingMode;
@Input() public workPackageAddedHandler:(wp:WorkPackageResource) => Promise<unknown>;
@Input() public showStatusButton:boolean = true;
@ -174,7 +174,12 @@ export class WorkPackageCardViewComponent implements OnInit {
this.dragService.register({
dragContainer: this.container.nativeElement,
scrollContainers: [this.container.nativeElement],
moves: (card:HTMLElement) => this.dragOutOf && !card.dataset.isNew,
moves: (card:HTMLElement) => {
const wpId:string = card.dataset.workPackageId!;
const workPackage = this.states.workPackages.get(wpId).value!;
return this.canDragOutOf(workPackage) && !card.dataset.isNew;
},
accepts: () => this.dragInto,
onMoved: (card:HTMLElement) => {
const wpId:string = card.dataset.workPackageId!;

@ -82,6 +82,7 @@ export class WorkPackageChangeset {
return this.changes[attribute];
}
public clear() {
this.changes = {};
this.resetForm();
@ -371,6 +372,22 @@ export class WorkPackageChangeset {
return (this.form || this.workPackage).schema;
}
/**
* Check whether the given attribute is writable.
* @param attribute
*/
public isWritable(attribute:string):boolean {
const schemaName = this.getSchemaName(attribute);
const fieldSchema = this.schema[schemaName] as IFieldSchema;
return fieldSchema && fieldSchema.writable;
}
public humanName(attribute:string):string {
const fieldSchema = this.schema[attribute] as IFieldSchema;
return fieldSchema.name || attribute;
}
public getSchemaName(attribute:string):string {
if (this.schema.hasOwnProperty('date') && (attribute === 'startDate' || attribute === 'dueDate')) {
return 'date';

@ -45,9 +45,6 @@
<option [textContent]="text.highlighting_mode.status"
[selected]="lastEntireRowAttribute === 'status'"
value="status"></option>
<option [textContent]="text.highlighting_mode.type"
[selected]="lastEntireRowAttribute === 'type'"
value="type"></option>
<option [textContent]="text.highlighting_mode.priority"
[selected]="lastEntireRowAttribute === 'priority'"
value="priority"></option>

@ -252,6 +252,7 @@ export class TimelineCellRenderer {
this.checkForActiveSelectionMode(renderInfo, bar);
this.checkForSpecialDisplaySituations(renderInfo, bar);
this.applyTypeColor(renderInfo, bar);
return true;
}
@ -327,7 +328,7 @@ export class TimelineCellRenderer {
// create center label
const labelCenter = document.createElement('div');
labelCenter.classList.add(classNameBarLabel);
this.applyTypeColor(renderInfo.workPackage, labelCenter);
this.applyTypeColor(renderInfo, labelCenter);
element.appendChild(labelCenter);
// create left label
@ -366,15 +367,25 @@ export class TimelineCellRenderer {
return labels;
}
protected applyTypeColor(wp:WorkPackageResource, bg:HTMLElement):void {
protected applyTypeColor(renderInfo:RenderInfo, bg:HTMLElement):void {
let wp = renderInfo.workPackage;
let type = wp.type;
let selectionMode = renderInfo.viewParams.activeSelectionMode;
if (!type) {
if (!type && !selectionMode) {
bg.style.backgroundColor = this.fallbackColor;
}
bg.style.backgroundColor = '';
// Don't apply the class in selection mode
const id = type.id;
bg.classList.add(Highlighting.backgroundClass('type', id!));
if (renderInfo.viewParams.activeSelectionMode) {
bg.classList.remove(Highlighting.backgroundClass('type', id!));
return;
} else {
bg.classList.add(Highlighting.backgroundClass('type', id!));
}
}
protected assignDate(changeset:WorkPackageChangeset, attributeName:string, value:moment.Moment) {
@ -408,7 +419,7 @@ export class TimelineCellRenderer {
bar.style.background = 'none';
} else {
// Apply the background color
this.applyTypeColor(renderInfo.workPackage, bar);
this.applyTypeColor(renderInfo, bar);
}
}

@ -47,7 +47,6 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
const diamond = document.createElement('div');
diamond.className = 'diamond';
diamond.style.backgroundColor = '#DDDDDD';
diamond.style.left = '0.5em';
diamond.style.height = '1em';
diamond.style.width = '1em';
@ -130,7 +129,7 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
diamond.style.width = 15 + 'px';
diamond.style.height = 15 + 'px';
diamond.style.marginLeft = -(15 / 2) + (renderInfo.viewParams.pixelPerDay / 2) + 'px';
this.applyTypeColor(renderInfo.workPackage, diamond);
this.applyTypeColor(renderInfo, diamond);
// offset left
const offsetStart = date.diff(viewParams.dateDisplayStart, 'days');

@ -347,6 +347,7 @@ export class WorkPackageTimelineTableController implements AfterViewInit, OnDest
this.selectionParams.notification = this.NotificationsService.addNotice(this.text.selectionMode);
this.$element.addClass('active-selection-mode');
this.refreshView();
}

@ -59,6 +59,12 @@ export interface BoardActionService {
*/
dragIntoAllowed(query:QueryResource, value:HalResource|undefined):boolean;
/**
* Determine whether we can create new items for this action attribute
*/
canAddToQuery(query:QueryResource):Promise<boolean>;
/**
* Get the specific component for the autocompleter (e.g versionAutocompleter)
* @returns {Component}

@ -56,6 +56,10 @@ export class BoardStatusActionService implements BoardActionService {
}
}
public canAddToQuery(query:QueryResource):Promise<boolean> {
return Promise.resolve(true);
}
public addActionQueries(board:Board):Promise<Board> {
return this.getStatuses()
.then((results) =>

@ -15,9 +15,11 @@ import {OpContextMenuItem} from "core-components/op-context-menu/op-context-menu
import {LinkHandling} from "core-app/modules/common/link-handling/link-handling";
import {StateService} from "@uirouter/core";
import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service";
import {StatusResource} from "core-app/modules/hal/resources/status-resource";
import {VersionCacheService} from "core-components/versions/version-cache.service";
import {VersionBoardHeaderComponent} from "core-app/modules/boards/board/board-actions/version/version-board-header.component";
import {FormResource} from "core-app/modules/hal/resources/form-resource";
import {FormsCacheService} from "core-components/forms/forms-cache.service";
import {CallableHalLink} from "core-app/modules/hal/hal-link/hal-link";
@Injectable()
export class BoardVersionActionService implements BoardActionService {
@ -29,6 +31,7 @@ export class BoardVersionActionService implements BoardActionService {
protected currentProject:CurrentProjectService,
protected wpNotifications:WorkPackageNotificationService,
protected state:StateService,
protected formCache:FormsCacheService,
protected pathHelper:PathHelperService) {
}
@ -67,6 +70,18 @@ export class BoardVersionActionService implements BoardActionService {
}
}
public canAddToQuery(query:QueryResource):Promise<boolean> {
const formLink = _.get(query, 'results.createWorkPackage.href', null) ;
if (!formLink) {
return Promise.resolve(false);
}
return this.formCache
.require(formLink)
.then((form:FormResource) => form.schema.version.writable);
}
public addActionQueries(board:Board):Promise<Board> {
return this.getVersions()
.then((results) => {

@ -50,7 +50,7 @@
</button>
</div>
<wp-card-view [dragOutOf]="canDragOutOf"
<wp-card-view [dragOutOfHandler]="canDragOutOfHandler"
[dragInto]="canDragInto"
[workPackageAddedHandler]="workPackageAddedHandler"
[cardsRemovable]="board.isFree && canDragOutOf"

@ -1,4 +1,5 @@
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
@ -44,6 +45,8 @@ import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notific
import {BoardActionsRegistryService} from "core-app/modules/boards/board/board-actions/board-actions-registry.service";
import {BoardActionService} from "core-app/modules/boards/board/board-actions/board-action.service";
import {ComponentType} from "@angular/cdk/portal";
import {IFieldSchema} from "core-app/modules/fields/field.base";
import {withLatestFrom} from "rxjs/operators";
export interface DisabledButtonPlaceholder {
text:string;
@ -103,7 +106,6 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
/** Are we allowed to remove and drag & drop elements ? */
public canDragInto:boolean = false;
public canDragOutOf:boolean = false;
/** Initially focus the list */
public initiallyFocused:boolean = false;
@ -111,6 +113,10 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
/** Editing handler to be passed into card component */
public workPackageAddedHandler = (workPackage:WorkPackageResource) => this.addWorkPackage(workPackage);
/** Move check to be passed into card component */
public canDragOutOf:boolean = false;
public canDragOutOfHandler = (workPackage:WorkPackageResource) => this.canMove(workPackage);
public buttonPlaceholder:DisabledButtonPlaceholder|undefined;
constructor(private readonly QueryDm:QueryDmService,
@ -143,7 +149,9 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
this.authorisationService
.observeUntil(componentDestroyed(this))
.subscribe(() => {
this.showAddButton = this.canDragInto && (this.wpInlineCreate.canAdd || this.canReference);
if (!this.board.isAction) {
this.showAddButton = this.canDragInto && (this.wpInlineCreate.canAdd || this.canReference);
}
});
this.querySpace.query
@ -176,6 +184,15 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
return this.I18n.t('js.boards.error_loading_the_list', { error_message: this.loadingError });
}
public canMove(workPackage:WorkPackageResource) {
if (this.board.isAction) {
const fieldSchema = workPackage.schema[this.board.actionAttribute!] as IFieldSchema;
return this.canDragOutOf && fieldSchema.writable;
} else {
return this.canDragOutOf;
}
}
public get canReference() {
return this.wpInlineCreate.canReference;
}
@ -265,13 +282,15 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
}
// Load the resource
this.actionService.getLoadedFilterValue(query).then(resource => {
this.actionService.getLoadedFilterValue(query).then(async resource => {
this.actionResource = resource;
this.headerComponent = this.actionService.headerComponent();
this.buttonPlaceholder = this.actionService.disabledAddButtonPlaceholder(resource);
this.actionResourceClass = this.boardListActionColorClass(resource);
this.canDragInto = this.actionService.dragIntoAllowed(query, resource);
this.showAddButton = this.canDragInto && (this.wpInlineCreate.canAdd || this.canReference);
const canWriteAttribute = await this.actionService.canAddToQuery(query);
this.showAddButton = this.canDragInto && this.wpInlineCreate.canAdd && canWriteAttribute;
});
}
@ -289,8 +308,17 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
*/
private addWorkPackage(workPackage:WorkPackageResource) {
let query = this.querySpace.query.value!;
const changeset = this.wpEditing.changesetFor(workPackage);
// Ensure attribute remains writable in the form
const actionAttribute = this.board.actionAttribute;
if (actionAttribute && !changeset.isWritable(actionAttribute)) {
throw this.I18n.t(
'js.boards.error_attribute_not_writable',
{ attribute: changeset.humanName(actionAttribute)}
);
}
const filter = new WorkPackageFilterValues(this.injector, changeset, query.filters);
filter.applyDefaultsFromFilters();

@ -73,20 +73,26 @@ export function bodyClass(className:string|null|undefined, action:'add'|'remove'
}
}
export function updateMenuItem(menuItemClass:string|undefined, action:'add'|'remove' = 'add') {
if (menuItemClass) {
let menuItem = jQuery('#main-menu .' + menuItemClass)[0];
if (!menuItemClass) {
return;
}
// Update Class
menuItem.classList[action]('selected');
let menuItem = jQuery('#main-menu .' + menuItemClass)[0];
// Update accessibility label
let menuItemTitle = (menuItem.getAttribute('title') || '').split(':').slice(-1)[0];
if (action === 'add') {
menuItemTitle = I18n.t('js.description_current_position') + menuItemTitle;
}
if (!menuItem) {
return;
}
menuItem.setAttribute('title', menuItemTitle);
// Update Class
menuItem.classList[action]('selected');
// Update accessibility label
let menuItemTitle = (menuItem.getAttribute('title') || '').split(':').slice(-1)[0];
if (action === 'add') {
menuItemTitle = I18n.t('js.description_current_position') + menuItemTitle;
}
menuItem.setAttribute('title', menuItemTitle);
}
export function uiRouterConfiguration(uiRouter:UIRouter, injector:Injector, module:StatesModule) {

@ -34,7 +34,7 @@ module OpenProject
module VERSION #:nodoc:
MAJOR = 9
MINOR = 0
PATCH = 0
PATCH = 2
TINY = PATCH # Redmine compat
# Used by semver to define the special version (if any).

@ -107,9 +107,14 @@ namespace :attachments do
target_attachment.store_all! attachments
end
desc 'Extract text content from attachment that were not extracted yet.'
desc 'Extract text content from attachment that were not extracted yet synchronously.'
task extract_fulltext_where_missing: :environment do
Attachment.extract_fulltext_where_missing
Attachment.extract_fulltext_where_missing(run_now: true)
end
desc 'Extract text content from attachment that were not extracted yet.'
task schedule_fulltext_extraction_where_missing: :environment do
Attachment.extract_fulltext_where_missing(run_now: false)
end
desc 'Extracts fulltext of all attachments and provide it for attachment filter even if that attachment has been \

@ -30,6 +30,7 @@ en:
add_list: 'Add list'
add_card: 'Add card'
error_loading_the_list: "Error loading the list: %{error_message}"
error_attribute_not_writable: "Cannot move the work package, %{attribute} is not writable."
click_to_remove_list: "Click to remove this list"
board_type:
free: 'Basic board'

@ -266,6 +266,35 @@ describe 'Version action board', type: :feature, js: true do
end
end
context 'a user with edit_work_packages, but missing assign_versions permissions' do
let(:no_version_edit_user) do
FactoryBot.create(:user,
member_in_projects: [project],
member_through_role: no_version_edit_role)
end
let(:no_version_edit_role) { FactoryBot.create(:role, permissions: no_version_edit_permissions) }
let(:no_version_edit_permissions) do
%i[show_board_views manage_board_views add_work_packages manage_versions
edit_work_packages view_work_packages manage_public_queries]
end
it 'can not move cards or add cards' do
# Create version board first
login_as user
board_page = create_new_version_board
# Login in with restricted user
login_as no_version_edit_user
# Reload the page
board_page.visit!
board_page.expect_editable_board(true)
board_page.expect_editable_list(false)
expect(page).to have_no_selector('.wp-card.-draggable')
end
end
context 'with limited permissions' do
before do
login_as(second_user)

@ -252,8 +252,13 @@ module Pages
end
def expect_editable_list(editable)
# Add new / existing card
expect(page).to have_conditional_selector(editable, '.board-list--card-dropdown-button')
# Add list button
if action?
expect(page).to have_conditional_selector(!editable, '.board-list--add-button[disabled]')
expect(page).to have_conditional_selector(editable, '.board-list--add-button:not([disabled])')
else
expect(page).to have_conditional_selector(editable, '.board-list--card-dropdown-button')
end
end
def rename_board(new_name, through_dropdown: false)

@ -25,6 +25,8 @@ jQuery(function($) {
jQuery('.button--edit-agenda').toggleClass('-active', edit);
jQuery('.button--edit-agenda').attr('disabled', edit);
jQuery('.meeting_content ~ attachments').toggle(!edit);
}
$('.button--edit-agenda').click(function() {

@ -189,7 +189,7 @@ class Meeting < ActiveRecord::Base
minutes = create_minutes(text: original_text, comment: 'Minutes created', attachments: attachments.map(&:last))
# substitute attachment references in text to use the respective copied attachments
updated_text = text.gsub(/(?<=\(\/api\/v3\/attachments\/)\d+(?=\/content\))/) do |id|
updated_text = original_text.gsub(/(?<=\(\/api\/v3\/attachments\/)\d+(?=\/content\))/) do |id|
old_id = id.to_i
new_id = attachments.select { |a, _| a.id == old_id }.map { |_, a| a.id }.first

@ -0,0 +1,112 @@
@import "fonts/openproject_icon_definitions"
@mixin sort-icons
font-family: "openproject-icon-font" !important
font-weight: normal !important
speak: none
margin-left: 5px
font-size: 1.2em
line-height: 1
vertical-align: text-bottom
// -------------------------- Generics --------------------------
.tablesorter-header
cursor: pointer
.tablesorter-headerDesc .generic-table--sort-header span
&:after
@include sort-icons
@include icon-mixin-sort-descending
.tablesorter-headerAsc .generic-table--sort-header span
&:after
@include sort-icons
@include icon-mixin-sort-ascending
.cost_types
padding-bottom: 3px
.cost_types a.active
color: #000
font-weight: bold
/* Details view*/
.detail-report td
text-align: left
vertical-align: top
#query_form fieldset.header_collapsible.collapsible
padding-bottom: 10px
// -------------------------- Save and Delete Reports --------------------------
#save_as_form, #delete_form
z-index: 999
// -------------------------- Buttons --------------------------
.form--buttons.-with-button-form
position: relative
.form--buttons.-with-button-form .button
margin-bottom: 0
div.button_form
background-color: white
border: 1px solid gray
-moz-border-radius: 3px
border-radius: 3px
left: 100px
position: absolute
padding: 1.0rem
width: 400px
// -------------------------- Filter --------------------------
.filter
-webkit-border-radius: 5px
-moz-border-radius: 5px
border-radius: 5px
.inactive-filter
background-color: #FCE29A !important
.advanced-filters--filter-value
white-space: nowrap
.filter_radio_option
padding-left: 5px
padding-right: 5px
#add_filter_block
margin-top: 6px
#add_filter_select
margin-bottom: 10px
.advanced-filters--filter-value.-binary
display: flex
// -------------------------- Mobile --------------------------
@media only screen and (max-width: 679px)
.group-by--control
margin-top: 10px
#group-by--rows .group-by--caption,
#group-by--columns .group-by--caption
padding: 0 7px
.group-by--selected-element
display: inline-block
padding-left: 7px
.group-by--selected-element:before,
.group-by--selected-element:after
border-width: 18px 0px 18px 14px
margin-top: -18px
.group-by--selected-element:first-of-type
margin: 0
.group-by--selected-element:first-of-type:before
border: none
left: 0

@ -0,0 +1,121 @@
// -------------------------- Group by --------------------------
#group-by--area
margin: 5px 0 10px 0
#group-by--area fieldset
border: none
padding: 0px
margin-bottom: 1em
.in_row
display: inline-block
list-style: none
border-width: 0px
.group-by--selected-element
cursor: move
position: relative
background-color: #767676
padding-left: 14px
margin-left: 18px
min-width: 145px
fieldset.collapsible.header_collapsible legend.in_row
width: inherit
background-image: inherit
.group-by--container
overflow: hidden
.group-by--label
margin: 0px
padding: 0px 18px 0 0
min-width: 60px
text-align: center
white-space: nowrap
font-weight: bold
color: #fff
height: 36px
line-height: 36px
cursor: move
.group-by--selected-element:after, .group-by--selected-element:before
border: solid transparent
content: " "
height: 0
width: 0
position: absolute
pointer-events: none
top: 50%
border-width: 30px 0px 30px 14px
margin-top: -30px
.group-by--selected-element:after
border-color: rgba(118, 118, 118, 0)
border-left-color: rgba(118, 118, 118, 1)
left: 100%
.group-by--selected-element:before
border-color: rgba(118, 118, 118, 1)
border-left-color: rgba(118, 118, 118, 0)
left: -14px
.group-by--selected-element:hover:after
border-color: rgba(52, 147, 179, 0)
border-left-color: rgba(52, 147, 179, 1)
.group-by--selected-element:hover:before
border-color: rgba(52, 147, 179, 1)
border-left-color: rgba(52, 147, 179, 0)
.group-by--selected-element:hover
background-color: #3493B3
.group-by--remove
line-height: normal
cursor: pointer
color: #FFFFFF
.group-by--remove:hover
background-color: #3493B3 !important
text-decoration: none
color: #FFFFFF
.group-by--control
margin: 0
padding: 0
.group-by--selected-elements
background-color: #EEE
overflow: hidden
.group-by--caption
position: relative
color: #FFFFFF
background-color: #4B4B4B
font-weight: bold
padding: 0 7px
margin: 0
height: inherit
line-height: 36px
min-width: 55px
overflow: visible
.group-by--caption:after
left: 100%
top: 50%
border: solid transparent
content: " "
height: 0
width: 0
position: absolute
pointer-events: none
.group-by--caption:after
border-color: rgba(75, 75, 75, 0)
border-left-color: #4B4B4B
border-width: 30px 0px 30px 14px
margin-top: -30px
z-index: 10

@ -0,0 +1,112 @@
#result-table
margin: 10px 0
overflow-x: auto
// -------------------------- Normal (generic) table --------------------------
.generic-table--results-container tfoot
td
width: 150px
.generic-table--footer-outer
width: inherit
// -------------------------- Special table --------------------------
.report
text-align: center
border-collapse: collapse
border: solid 1px #ccc !important
width: auto !important
td, th
min-width: 90px
white-space: nowrap
td
border: dotted 1px #ddd
color: #666
text-align: center
padding: 0 8px 0 8px
line-height: 2rem
vertical-align: middle
tbody th, tbody td, .inner
max-width: 300px
white-space: normal !important
td:hover
color: #000
outline: #ccc 1px solid
outline-offset: 1px
td.empty:hover
outline: none
th
border: solid 1px #ccc
background-color: #e3e3e3
text-align: center
font-size: 0.875rem
line-height: 34px
padding: 0 8px 0 8px
.odd th.inner
background-color: #e8e8e8
.even th.inner
background-color: #e3e3e3
th.inner
border: solid 1px #ccc
background-color: #efefef
text-align: right
.odd td.inner
background-color: #e8e8e8
.even td.inner
background-color: #e3e3e3
tr:hover .inner,
tr:hover .bottom,
tr:hover .empty,
tr:hover .right
background-color: #f5f5c5 !important
.top
border-top-style: solid
border-top-color: #ccc
.bottom
border-bottom-style: solid
border-bottom-color: #ccc
td.penultimate
border-right-style: solid
thead .inner, tfoot .inner
text-align: right
padding-right: 5px
.result
font-size: 120%
text-align: right
.thead tr:hover .inner, tfoot tr:hover .inner
background-color: #efefef
.left
text-align: left !important
padding-left: 5px
.right
text-align: right !important
padding-right: 5px
td .drill_down, th .drill_down
font-size: 8px
display: block
float: right
font-weight: bold
visibility: hidden
td:hover .drill_down, th:hover .drill_down
visibility: visible

@ -0,0 +1,7 @@
@media print
#query_form
.form--fieldset:not(#filters)
display: none
#main
overflow: visible

@ -1,29 +0,0 @@
@import "fonts/openproject_icon_definitions"
@mixin sort-icons
font-family: "openproject-icon-font" !important
font-weight: normal !important
speak: none
margin-left: 5px
font-size: 1.2em
line-height: 1
vertical-align: text-bottom
.tablesorter-header
cursor: pointer
.tablesorter-headerDesc .generic-table--sort-header span
&:after
@include sort-icons
@include icon-mixin-sort-descending
.tablesorter-headerAsc .generic-table--sort-header span
&:after
@include sort-icons
@include icon-mixin-sort-ascending
#result-table .generic-table--results-container tfoot
td
width: 150px
.generic-table--footer-outer
width: inherit

@ -0,0 +1,4 @@
@import _reporting
@import _reporting_group_by
@import _reporting_table
@import _reporting_table_print

@ -21,6 +21,5 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
<% content_for :header_tags do %>
<%= javascript_include_tag "reporting_engine/reporting_engine" %>
<%= javascript_include_tag "reporting/reporting" %>
<%= stylesheet_link_tag 'reporting_engine/reporting_engine' %>
<%= stylesheet_link_tag "reporting/reporting" %>
<%= stylesheet_link_tag "reporting/reporting_styles", media: 'all' %>
<% end %>

@ -24,7 +24,7 @@ module OpenProject::Reporting
include OpenProject::Plugins::ActsAsOpEngine
register 'openproject-reporting',
author_url: 'http://finn.de',
author_url: 'https://www.openproject.org',
bundled: true do
view_actions = [:index, :show, :drill_down, :available_values, :display_report_list]
@ -87,7 +87,6 @@ module OpenProject::Reporting
initializer 'reporting.precompile_assets' do
Rails.application.config.assets.precompile += %w(
reporting_engine/reporting_engine.css
reporting_engine/reporting_engine.js
)
@ -104,7 +103,7 @@ module OpenProject::Reporting
require_dependency 'cost_query/group_by'
end
assets %w(reporting/reporting.css
assets %w(reporting/reporting_styles.css
reporting/reporting.js)
patches %i[TimelogController CustomFieldsController OpenProject::Configuration]

@ -1,433 +0,0 @@
/*-- copyright
ReportingEngine
Copyright (C) 2010 - 2014 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3.
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.
++*/
.cost_types {
padding-bottom: 3px;
}
.cost_types a.active {
color: #000;
font-weight: bold;
}
.report {
text-align: center;
border-collapse: collapse;
border: solid 1px #ccc !important;
width: auto !important;
}
.report td, .report th {
min-width: 90px;
white-space: nowrap;
}
.report td {
border: dotted 1px #ddd;
color: #666;
text-align: center;
padding: 0 8px 0 8px;
line-height: 2rem;
vertical-align: middle;
}
.report tbody th, .report tbody td, .inner {
max-width: 300px;
white-space: normal !important;
}
.report td:hover {
color: #000;
outline: #ccc 1px solid;
outline-offset: 1px;
}
.report td.empty:hover {
outline: none;
}
.report th {
border: solid 1px #ccc;
background-color: #e3e3e3;
text-align: center;
font-size: 0.875rem;
line-height: 34px;
padding: 0 8px 0 8px;
}
.report .odd th.inner {
background-color: #e8e8e8;
}
.report .even th.inner {
background-color: #e3e3e3;
}
.report th.inner {
border: solid 1px #ccc;
background-color: #efefef;
text-align: right;
}
.report .odd td.inner {
background-color: #e8e8e8;
}
.report .even td.inner {
background-color: #e3e3e3;
}
.report tr.even:hover .inner,
.report tr.even:hover .bottom,
.report tr.even:hover .empty,
.report tr.even:hover .right {
background-color: #f5f5c5 !important;
}
/* IE7 made me do it! */
.report tr.odd:hover .inner,
.report tr.odd:hover .bottom,
.report tr.odd:hover .empty,
.report tr.odd:hover .right {
background-color: #f5f5c5 !important;
}
.report .top {
border-top-style: solid;
border-top-color: #ccc;
/* border-top: 2px solid #ccc !important; */
}
.report .bottom {
border-bottom-style: solid;
border-bottom-color: #ccc;
/* border-bottom: 2px solid #ccc !important; */
}
.report td.penultimate {
border-right-style: solid;
}
.report thead .inner, .report tfoot .inner {
text-align: right;
padding-right: 5px;
}
.report .result {
font-size: 120%;
text-align: right;
}
#result-table {
margin: 10px 0;
overflow-x: auto;
}
.report thead tr:hover .inner, .report tfoot tr:hover .inner {
background-color: #efefef;
}
.report .left {
text-align: left !important;
padding-left: 5px;
}
.report .right {
text-align: right !important;
padding-right: 5px;
}
/* Details view*/
.detail-report td {
text-align: left;
vertical-align: top;
}
#query_form fieldset.header_collapsible.collapsible {
padding-bottom: 10px;
}
/* Overwriting styling for headlines within the query. */
/* TODO: Font-size seems to be a bit odd. Needs some love. */
.new_report fieldset h3 {
font-size: 1.17em;
border: none;
}
.filter {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.inactive-filter {
background-color: #FCE29A !important;
}
.advanced-filters--filter-value {
white-space: nowrap;
}
.filter_radio_option {
padding-left: 5px;
padding-right: 5px;
}
#add_filter_block {
margin-top: 6px;
}
#add_filter_select {
margin-bottom: 10px;
}
/* ----- group by --- */
#group-by--area {
margin: 5px 0 10px 0;
}
#group-by--area fieldset {
border: none;
padding: 0px;
margin-bottom: 1em;
}
.in_row {
display: inline-block;
list-style: none;
border-width: 0px;
}
.group-by--selected-element {
cursor: move;
position: relative;
background-color: #767676;
padding-left: 14px;
margin-left: 18px;
min-width: 145px;
}
fieldset.collapsible.header_collapsible legend.in_row {
width: inherit;
background-image: inherit;
}
.group-by--container {
overflow: hidden;
}
.group-by--label {
margin: 0px;
padding: 0px 18px 0 0;
min-width: 60px;
text-align: center;
white-space: nowrap;
font-weight: bold;
color: #fff;
height: 36px;
line-height: 36px;
cursor: move;
}
.group-by--selected-element:after, .group-by--selected-element:before {
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
top: 50%;
border-width: 30px 0px 30px 14px;
margin-top: -30px;
}
.group-by--selected-element:after {
border-color: rgba(118, 118, 118, 0);
border-left-color: rgba(118, 118, 118, 1);
left: 100%;
}
.group-by--selected-element:before {
border-color: rgba(118, 118, 118, 1);
border-left-color: rgba(118, 118, 118, 0);
left: -14px;
}
.group-by--selected-element:hover:after {
border-color: rgba(52, 147, 179, 0);
border-left-color: rgba(52, 147, 179, 1);
}
.group-by--selected-element:hover:before {
border-color: rgba(52, 147, 179, 1);
border-left-color: rgba(52, 147, 179, 0);
}
.group-by--selected-element:hover {
background-color: #3493B3;
}
.group-by--remove {
line-height: normal;
cursor: pointer;
color: #FFFFFF;
}
.group-by--remove:hover {
background-color: #3493B3 !important;
text-decoration: none;
color: #FFFFFF;
}
.group-by--control {
margin: 0;
padding: 0;
}
.group-by--selected-elements {
background-color: #EEE;
overflow: hidden;
}
.group-by--caption {
position: relative;
color: #FFFFFF;
background-color: #4B4B4B;
font-weight: bold;
padding: 0 7px;
margin: 0;
height: inherit;
line-height: 36px;
min-width: 55px;
overflow: visible;
}
.group-by--caption:after {
left: 100%;
top: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.group-by--caption:after {
border-color: rgba(75, 75, 75, 0);
border-left-color: #4B4B4B;
border-width: 30px 0px 30px 14px;
margin-top: -30px;
z-index: 10;
}
/* -- end group-by -- */
td .drill_down, th .drill_down {
font-size: 8px;
display: block;
float: right;
font-weight: bold;
visibility: hidden;
}
td:hover .drill_down, th:hover .drill_down {
visibility: visible;
}
/*Buttons*/
.form--buttons.-with-button-form {
position: relative;
}
.form--buttons.-with-button-form .button {
margin-bottom: 0;
}
div.button_form {
/* TODO IE Compatibility! */
background-color: white;
border: 1px solid gray;
-moz-border-radius: 3px;
border-radius: 3px;
left: 100px;
position: absolute;
padding: 1.0rem;
width: 400px;
}
/***** Save and Delete Reports ****/
#save_as_form, #delete_form {
z-index: 999;
}
/* Calendar Fixes */
div.calendar /* let calendar expand properly */
{
font-size: medium;
line-height: normal;
}
div.calendar table div { /* make sure the nested divs are large enough, too */
font-size: medium;
line-height: normal;
}
.calendar .combo .label, .calendar .combo .label-IEfix { /* set the proper size for the combo boxes with months and years */
font-size: 10px;
line-height: normal;
}
.calendar tbody .day { /* avoid jitter during quick mouse-overs over days */
border: 1px dotted transparent;
padding: 1px 3px 1px 1px;
}
.advanced-filters--filter-value.-binary {
display: flex;
}
@media only screen and (max-width: 679px) {
.group-by--control {
margin-top: 10px;
}
#group-by--rows .group-by--caption,
#group-by--columns .group-by--caption {
padding: 0 7px;
}
.group-by--selected-element {
display: inline-block;
padding-left: 7px;
}
.group-by--selected-element:before,
.group-by--selected-element:after {
border-width: 18px 0px 18px 14px;
margin-top: -18px;
}
.group-by--selected-element:first-of-type {
margin: 0;
}
.group-by--selected-element:first-of-type:before {
border: none;
left: 0;
}
}

@ -1,23 +0,0 @@
/*-- copyright
ReportingEngine
Copyright (C) 2010 - 2014 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3.
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.
++*/
/* *
*= require reporting_engine/reporting
*/

@ -26,7 +26,7 @@ module ReportingEngine
config.autoload_paths += Dir["#{config.root}/lib/"]
initializer 'reportingengine.precompile_assets' do
Rails.application.config.assets.precompile += %w(reporting_engine.css reporting_engine.js)
Rails.application.config.assets.precompile += %w(reporting_engine.js)
end
config.to_prepare do

@ -20,9 +20,9 @@
require_dependency 'widget/base'
class Widget::Filters < ::Widget::Base
def render
spacer = content_tag :li, '', class: 'advanced-filters--spacer'
spacer = content_tag :li, '', class: 'advanced-filters--spacer hide-when-print'
add_filter = content_tag :li, id: 'add_filter_block', class: 'advanced-filters--add-filter' do
add_filter = content_tag :li, id: 'add_filter_block', class: 'advanced-filters--add-filter hide-when-print' do
add_filter_label = label_tag 'add_filter_select', l(:label_filter_add),
class: 'advanced-filters--add-filter-label'
add_filter_label += label_tag 'add_filter_select', I18n.t('js.filter.description.text_open_filter') + ' ' +

@ -37,7 +37,7 @@ class Widget::Settings < Widget::Base
end
def render_controls_settings
content_tag :div, class: 'form--buttons -with-button-form' do
content_tag :div, class: 'form--buttons -with-button-form hide-when-print' do
widgets = ''.html_safe
render_widget(Widget::Controls::Apply, @subject, to: widgets)
render_widget(Widget::Controls::Save, @subject, to: widgets,

@ -173,4 +173,17 @@ RSpec.feature 'Work package navigation', js: true, selenium: true do
full_page.ensure_page_loaded
end
scenario 'double clicking my page (Regression #30343)' do
work_package.author = user
work_package.subject = 'Foobar'
work_package.save!
visit my_page_path
page.find('.wp-table--cell-td.id a', text: work_package.id).click
full_page = ::Pages::FullWorkPackage.new work_package, work_package.project
full_page.ensure_page_loaded
end
end

@ -47,7 +47,7 @@ describe CopyProjectJob, type: :model do
let(:copy_job) {
CopyProjectJob.new user_id: user_de.id,
source_project_id: source_project.id,
target_project_params: target_project,
target_project_params: {},
associations_to_copy: []
}
@ -118,6 +118,32 @@ describe CopyProjectJob, type: :model do
end
end
describe 'copy project fails with internal error' do
let(:admin) { FactoryBot.create(:admin) }
let(:source_project) { FactoryBot.create(:project) }
let(:copy_job) {
CopyProjectJob.new user_id: admin.id,
source_project_id: source_project.id,
target_project_params: params,
associations_to_copy: [:work_packages]
} # send mails
let(:params) { { name: 'Copy', identifier: 'copy' } }
before do
allow(User).to receive(:current).and_return(admin)
allow(ProjectMailer).to receive(:copy_project_succeeded).and_raise 'error message not meant for user'
end
it 'renders a error when unexpected errors occur' do
expect(ProjectMailer)
.to receive(:copy_project_failed)
.with(admin, source_project, 'Copy', [I18n.t('copy_project.failed_internal')])
.and_return maildouble
expect { copy_job.perform }.not_to raise_error
end
end
shared_context 'copy project' do
before do
copy_project_job = CopyProjectJob.new(user_id: user.id,

Loading…
Cancel
Save