Merge branch 'release/9.1' into merge/release_9_1_into_dev

pull/7529/head
ulferts 5 years ago
commit 31d16d7948
No known key found for this signature in database
GPG Key ID: A205708DE1284017
  1. 4
      config/locales/crowdin/js-de.yml
  2. 55
      db/migrate/20190722082648_add_derived_estimated_hours_to_work_packages.rb
  3. 5
      frontend/src/app/components/wp-card-view/wp-card-view.component.html
  4. 10
      frontend/src/app/components/wp-card-view/wp-card-view.component.ts
  5. 3
      frontend/src/app/components/wp-grid/wp-grid.component.ts
  6. 5
      frontend/src/app/modules/common/no-results/no-results.component.html
  7. 4
      frontend/src/app/modules/common/no-results/no-results.component.ts
  8. 2
      modules/avatars/config/locales/crowdin/js-fr.yml

@ -295,7 +295,7 @@ de:
label_total_progress: "%{percent}% Gesamtfortschritt" label_total_progress: "%{percent}% Gesamtfortschritt"
label_total_amount: "Gesamt: %{amount}" label_total_amount: "Gesamt: %{amount}"
label_updated_on: "aktualisiert am" label_updated_on: "aktualisiert am"
label_value_derived_from_children: "(value derived from children)" label_value_derived_from_children: "(aggregierter Wert von Kindelementen)"
label_warning: "Warnung" label_warning: "Warnung"
label_work_package: "Arbeitspaket" label_work_package: "Arbeitspaket"
label_work_package_plural: "Arbeitspakete" label_work_package_plural: "Arbeitspakete"
@ -821,7 +821,7 @@ de:
one: "ein untergeordnetes Arbeitspaket" one: "ein untergeordnetes Arbeitspaket"
other: "%{count} untergeordnete Arbeitspakete" other: "%{count} untergeordnete Arbeitspakete"
hour: hour:
one: "1 h" one: "%{count} h"
other: "%{count} h" other: "%{count} h"
zen_mode: zen_mode:
button_activate: 'Zen-Modus aktivieren' button_activate: 'Zen-Modus aktivieren'

@ -29,18 +29,23 @@ class AddDerivedEstimatedHoursToWorkPackages < ActiveRecord::Migration[5.2]
# estimated hours set based on their children through the UpdateAncestorsService. # estimated hours set based on their children through the UpdateAncestorsService.
# #
# We move this value to the derived_estimated_hours column and clear # We move this value to the derived_estimated_hours column and clear
# the estimated_hours column. In the future users can estimte the time # the estimated_hours column. In the future users can estimate the time
# for parent work pacages separately there while the UpdateAncestorsService # for parent work packages separately while the UpdateAncestorsService
# only touches the derived_estimated_hours column. # only touches the derived_estimated_hours column.
def migrate_to_derived_estimated_hours! def migrate_to_derived_estimated_hours!
last_id = Journal.order(id: :desc).limit(1).pluck(:id).first || 0 last_id = Journal.order(id: :desc).limit(1).pluck(:id).first || 0
wp_journals = "work_package_journals" wp_journals = "work_package_journals"
work_packages = WorkPackageWithRelations.with_children
work_packages = WorkPackageWithRelations.with_children.where("estimated_hours > ?", 0)
work_packages.update_all("derived_estimated_hours = estimated_hours, estimated_hours = NULL") work_packages.update_all("derived_estimated_hours = estimated_hours, estimated_hours = NULL")
work_packages = WorkPackageWithRelations.with_children.where("derived_estimated_hours > ?", 0)
create_journals_for work_packages create_journals_for work_packages
create_work_package_journals last_id: last_id create_work_package_journals last_id: last_id
create_customizable_journals last_id: last_id
create_attachable_journals last_id: last_id
work_packages.each(&:touch) # invalidate cache
end end
## ##
@ -115,4 +120,48 @@ class AddDerivedEstimatedHoursToWorkPackages < ActiveRecord::Migration[5.2]
) AS results ) AS results
") ")
end end
def create_customizable_journals(last_id:)
journals = "journals"
customizable = "customizable_journals"
work_packages = "work_packages"
WorkPackage.connection.execute("
INSERT INTO #{customizable} (journal_id, custom_field_id, value)
SELECT #{journals}.id, #{customizable}.custom_field_id, #{customizable}.value
FROM #{journals} -- take the journal ID from here (ID of newly created journals from above)
LEFT JOIN #{work_packages}
ON #{work_packages}.id = #{journals}.journable_id AND #{journals}.journable_type = 'WorkPackage'
RIGHT JOIN #{customizable} -- keep everything else the same; there can be multiple customizable journals (custom fields)
ON #{customizable}.journal_id = (
SELECT MAX(id)
FROM #{journals}
WHERE #{journals}.journable_id = #{work_packages}.id AND journable_type = 'WorkPackage' AND #{journals}.id <= #{last_id}
-- we are selecting the latest previous (hence <= last_id) customizable journal here to copy its values
)
WHERE #{journals}.id > #{last_id} -- make sure to only create entries for the newly created journals
")
end
def create_attachable_journals(last_id:)
journals = "journals"
attachable = "attachable_journals"
work_packages = "work_packages"
WorkPackage.connection.execute("
INSERT INTO #{attachable} (journal_id, attachment_id, filename)
SELECT #{journals}.id, #{attachable}.attachment_id, #{attachable}.filename
FROM #{journals} -- take the journal ID from here (ID of newly created journals from above)
LEFT JOIN #{work_packages}
ON #{work_packages}.id = #{journals}.journable_id AND #{journals}.journable_type = 'WorkPackage'
RIGHT JOIN #{attachable} -- keep everything else the same; there can be multiple attachable journals (attachments)
ON #{attachable}.journal_id = (
SELECT MAX(id)
FROM #{journals}
WHERE #{journals}.journable_id = #{work_packages}.id AND journable_type = 'WorkPackage' AND #{journals}.id <= #{last_id}
-- we are selecting the latest previous (hence <= last_id) customizable journal here to copy its values
)
WHERE #{journals}.id > #{last_id} -- make sure to only create entries for the newly created journals
")
end
end end

@ -71,3 +71,8 @@
</div> </div>
</div> </div>
<div *ngIf="showEmptyResultsBox && isResultEmpty">
<no-results [title]="text.noResults.title" [description]="text.noResults.description"></no-results>
</div>

@ -53,6 +53,8 @@ export class WorkPackageCardViewComponent implements OnInit {
@Input() public orientation:CardViewOrientation = 'vertical'; @Input() public orientation:CardViewOrientation = 'vertical';
/** Whether cards are removable */ /** Whether cards are removable */
@Input() public cardsRemovable:boolean = false; @Input() public cardsRemovable:boolean = false;
/** Whether a notification box shall be shown when there are no WP to display */
@Input() public showEmptyResultsBox:boolean = false;
/** Container reference */ /** Container reference */
@ViewChild('container', { static: true }) public container:ElementRef; @ViewChild('container', { static: true }) public container:ElementRef;
@ -61,11 +63,16 @@ export class WorkPackageCardViewComponent implements OnInit {
public trackByHref = AngularTrackingHelpers.trackByHrefAndProperty('lockVersion'); public trackByHref = AngularTrackingHelpers.trackByHrefAndProperty('lockVersion');
public query:QueryResource; public query:QueryResource;
private _workPackages:WorkPackageResource[]; private _workPackages:WorkPackageResource[] = [];
public isResultEmpty:boolean = false;
public columns:QueryColumn[]; public columns:QueryColumn[];
public text = { public text = {
removeCard: this.I18n.t('js.card.remove_from_list'), removeCard: this.I18n.t('js.card.remove_from_list'),
addNewCard: this.I18n.t('js.card.add_new'), addNewCard: this.I18n.t('js.card.add_new'),
noResults: {
title: this.I18n.t('js.work_packages.no_results.title'),
description: this.I18n.t('js.work_packages.no_results.description')
},
}; };
/** Inline create / reference properties */ /** Inline create / reference properties */
@ -122,6 +129,7 @@ export class WorkPackageCardViewComponent implements OnInit {
).subscribe((query:QueryResource) => { ).subscribe((query:QueryResource) => {
this.query = query; this.query = query;
this.workPackages = query.results.elements; this.workPackages = query.results.elements;
this.isResultEmpty = this.workPackages.length === 0;
this.cdRef.detectChanges(); this.cdRef.detectChanges();
}); });
} }

@ -43,7 +43,8 @@ import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/iso
[highlightingMode]="highlightingMode" [highlightingMode]="highlightingMode"
[showStatusButton]="false" [showStatusButton]="false"
[orientation]="gridOrientation" [orientation]="gridOrientation"
(onMoved)="switchToManualSorting()"> (onMoved)="switchToManualSorting()"
[showEmptyResultsBox]="true">
</wp-card-view> </wp-card-view>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,

@ -1,4 +1,5 @@
<i class="icon-info1" aria-hidden="true"></i> <i class="icon-info1" aria-hidden="true"></i>
<span class="generic-table--no-results-title" <span class="generic-table--no-results-title">
[textContent]="title"> {{ title }}
<span *ngIf="description" [textContent]="description"></span>
</span> </span>

@ -34,8 +34,8 @@ import {Component, Input, HostBinding} from '@angular/core';
}) })
export class NoResultsComponent { export class NoResultsComponent {
@Input() @Input() title:string;
title:string; @Input() description:string;
@HostBinding('class.generic-table--no-results-container') setHostClass = true; @HostBinding('class.generic-table--no-results-container') setHostClass = true;
} }

@ -3,7 +3,7 @@ fr:
label_preview: 'Aperçu' label_preview: 'Aperçu'
button_update: 'Mettre à jour' button_update: 'Mettre à jour'
avatars: avatars:
label_choose_avatar: "Choose Avatar from file" label_choose_avatar: "Choisir un avatar depuis un fichier"
uploading_avatar: "Uploading your avatar." uploading_avatar: "Uploading your avatar."
text_upload_instructions: | text_upload_instructions: |
Upload your own custom avatar of 128 by 128 pixels. Larger files will be resized and cropped to match. Upload your own custom avatar of 128 by 128 pixels. Larger files will be resized and cropped to match.

Loading…
Cancel
Save