Merge pull request #7479 from opf/feature/custom-text-widget_on_dashboard
Feature/custom text widget on dashboard [ci skip]pull/7491/head
commit
74a9ffbe78
@ -0,0 +1,73 @@ |
||||
import {SchemaResource} from "core-app/modules/hal/resources/schema-resource"; |
||||
import {FormResource} from "core-app/modules/hal/resources/form-resource"; |
||||
import {HalResource} from "core-app/modules/hal/resources/hal-resource"; |
||||
import {Injector} from '@angular/core'; |
||||
|
||||
export abstract class EditChangeset<T extends HalResource|{ [key:string]:unknown; }> { |
||||
// The changeset to be applied to the resource
|
||||
public changes:{ [attribute:string]:any } = {}; |
||||
|
||||
public form:FormResource|null; |
||||
|
||||
constructor(readonly injector:Injector, |
||||
public resource:T, |
||||
form?:FormResource) { |
||||
this.form = form || null; |
||||
} |
||||
|
||||
public get empty() { |
||||
return _.isEmpty(this.changes); |
||||
} |
||||
|
||||
/** |
||||
* Get attributes |
||||
* @returns {string[]} |
||||
*/ |
||||
public get changedAttributes() { |
||||
return _.keys(this.changes); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve the editing value for the given attribute |
||||
* |
||||
* @param {string} key The attribute to read |
||||
* @return {any} Either the value from the overriden change, or the default value |
||||
*/ |
||||
public value(key:string) { |
||||
if (this.isOverridden(key)) { |
||||
return this.changes[key]; |
||||
} else { |
||||
return this.resource[key]; |
||||
} |
||||
} |
||||
|
||||
public setValue(key:string, val:any) { |
||||
this.changes[key] = val; |
||||
} |
||||
|
||||
public getSchemaName(attribute:string):string { |
||||
return attribute; |
||||
} |
||||
|
||||
public clear() { |
||||
this.changes = {}; |
||||
} |
||||
|
||||
/** |
||||
* Return whether a change value exist for the given attribute key. |
||||
* @param {string} key |
||||
* @return {boolean} |
||||
*/ |
||||
public isOverridden(key:string) { |
||||
return this.changes.hasOwnProperty(key); |
||||
} |
||||
|
||||
/** |
||||
* Get the best schema currently available, either the default resource schema (must exist). |
||||
* If loaded, return the form schema, which provides better information on writable status |
||||
* and contains available values. |
||||
*/ |
||||
public get schema():SchemaResource { |
||||
return (this.form || this.resource).schema; |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
import {EditChangeset} from "core-app/modules/fields/changeset/edit-changeset"; |
||||
|
||||
export class CustomTextChangeset extends EditChangeset<{ [key:string]:unknown; }> { |
||||
|
||||
} |
@ -0,0 +1,118 @@ |
||||
import {EditFieldHandler} from "core-app/modules/fields/edit/editing-portal/edit-field-handler"; |
||||
import {ElementRef, Injector, Injectable} from "@angular/core"; |
||||
import {IFieldSchema} from "core-app/modules/fields/field.base"; |
||||
import {BehaviorSubject} from "rxjs"; |
||||
import {GridWidgetResource} from "core-app/modules/hal/resources/grid-widget-resource"; |
||||
import {CustomTextChangeset} from "core-app/modules/grids/widgets/custom-text/custom-text-changeset"; |
||||
|
||||
@Injectable() |
||||
export class CustomTextEditFieldService extends EditFieldHandler { |
||||
public fieldName = 'text'; |
||||
public inEdit = false; |
||||
public inEditMode = false; |
||||
public inFlight = false; |
||||
|
||||
public valueChanged$:BehaviorSubject<string>; |
||||
|
||||
public changeset:CustomTextChangeset; |
||||
|
||||
constructor(protected elementRef:ElementRef, |
||||
protected injector:Injector) { |
||||
super(); |
||||
} |
||||
|
||||
errorMessageOnLabel:string; |
||||
|
||||
onFocusOut():void { |
||||
// interface
|
||||
} |
||||
|
||||
public initialize(value:GridWidgetResource) { |
||||
this.changeset = new CustomTextChangeset(this.injector, value.options); |
||||
this.valueChanged$ = new BehaviorSubject(value.options['text'] as string); |
||||
} |
||||
|
||||
public reinitialize(value:GridWidgetResource) { |
||||
this.changeset = new CustomTextChangeset(this.injector, value.options); |
||||
} |
||||
|
||||
/** |
||||
* Handle saving the text |
||||
*/ |
||||
public handleUserSubmit():Promise<any> { |
||||
return this.update(); |
||||
} |
||||
|
||||
public reset(withText:string = '') { |
||||
if (withText.length > 0) { |
||||
withText += '\n'; |
||||
} |
||||
|
||||
this.changeset.setValue('text', { raw: withText }); |
||||
} |
||||
|
||||
public get schema():IFieldSchema { |
||||
return { |
||||
name: I18n.t('js.grid.widgets.custom_text.title'), |
||||
writable: true, |
||||
required: false, |
||||
type: 'Formattable', |
||||
hasDefault: false |
||||
}; |
||||
} |
||||
|
||||
private async update() { |
||||
return this |
||||
.onSubmit() |
||||
.then(() => { |
||||
this.valueChanged$.next(this.rawText); |
||||
this.deactivate(); |
||||
}); |
||||
} |
||||
|
||||
public get rawText() { |
||||
return _.get(this.textValue, 'raw', ''); |
||||
} |
||||
|
||||
public get htmlText() { |
||||
return _.get(this.textValue, 'html', ''); |
||||
} |
||||
|
||||
public get textValue() { |
||||
return this.changeset.value('text'); |
||||
} |
||||
|
||||
public handleUserCancel() { |
||||
this.deactivate(); |
||||
} |
||||
|
||||
public get active() { |
||||
return this.inEdit; |
||||
} |
||||
|
||||
public activate(withText?:string) { |
||||
this.inEdit = true; |
||||
} |
||||
|
||||
deactivate():void { |
||||
this.changeset.clear(); |
||||
this.inEdit = false; |
||||
} |
||||
|
||||
focus():void { |
||||
const trigger = this.elementRef.nativeElement.querySelector('.inplace-editing--trigger-container'); |
||||
trigger && trigger.focus(); |
||||
} |
||||
|
||||
handleUserKeydown(event:JQueryEventObject, onlyCancel?:boolean):void { |
||||
// interface
|
||||
} |
||||
|
||||
isChanged():boolean { |
||||
return !this.changeset.empty; |
||||
} |
||||
|
||||
stopPropagation(evt:JQueryEventObject):boolean { |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
<widget-header |
||||
[name]="widgetName" |
||||
[icon]="'quote'" |
||||
(onRenamed)="renameWidget($event)"> |
||||
|
||||
<widget-menu |
||||
[resource]="resource"> |
||||
</widget-menu> |
||||
</widget-header> |
||||
|
||||
<div class="grid--widget-content -custom-text"> |
||||
<div class="wp-edit-field inplace-edit"> |
||||
<edit-form-portal *ngIf="active" |
||||
[schemaInput]="schema" |
||||
[changesetInput]="changeset" |
||||
[editFieldHandler]="handler"> |
||||
</edit-form-portal> |
||||
<div *ngIf="!active" |
||||
class="inplace-edit--read"> |
||||
<accessible-by-keyboard |
||||
class="inplace-editing--trigger-container" |
||||
[spanClass]="inplaceEditClasses" |
||||
[linkClass]="'inplace-editing--trigger-link'" |
||||
(execute)="activate()"> |
||||
|
||||
<span class="inplace-edit--read-value -default"> |
||||
<span |
||||
*ngIf="!textEmpty" |
||||
[innerHTML]="customText"></span> |
||||
<span |
||||
*ngIf="textEmpty" |
||||
[innerHTML]="placeholderText"></span> |
||||
</span> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,95 @@ |
||||
import {AbstractWidgetComponent} from "core-app/modules/grids/widgets/abstract-widget.component"; |
||||
import {Component, ChangeDetectionStrategy, Injector, OnInit, OnDestroy, SimpleChanges, ChangeDetectorRef} from '@angular/core'; |
||||
import {CustomTextEditFieldService} from "core-app/modules/grids/widgets/custom-text/custom-text-edit-field.service"; |
||||
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||
import {HalResource} from "core-app/modules/hal/resources/hal-resource"; |
||||
import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed'; |
||||
import {filter} from 'rxjs/operators'; |
||||
|
||||
@Component({ |
||||
templateUrl: './custom-text.component.html', |
||||
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
providers: [ |
||||
CustomTextEditFieldService |
||||
] |
||||
}) |
||||
export class WidgetCustomTextComponent extends AbstractWidgetComponent implements OnInit, OnDestroy { |
||||
protected currentRawText:string; |
||||
|
||||
constructor (protected i18n:I18nService, |
||||
protected injector:Injector, |
||||
public handler:CustomTextEditFieldService, |
||||
protected cdr:ChangeDetectorRef) { |
||||
super(i18n, injector); |
||||
} |
||||
|
||||
ngOnInit():void { |
||||
this.memorizeRawText(); |
||||
this.handler.initialize(this.resource); |
||||
|
||||
this |
||||
.handler |
||||
.valueChanged$ |
||||
.pipe( |
||||
untilComponentDestroyed(this), |
||||
filter(value => value !== this.resource.options['text']) |
||||
).subscribe(newText => { |
||||
let changeset = this.setChangesetOptions({ text: { raw: newText } }); |
||||
this.resourceChanged.emit(changeset); |
||||
}); |
||||
} |
||||
|
||||
ngOnDestroy():void { |
||||
// comply to interface
|
||||
} |
||||
|
||||
ngOnChanges(changes:SimpleChanges):void { |
||||
if (changes.resource.currentValue.options.text.raw !== this.currentRawText) { |
||||
this.memorizeRawText(); |
||||
this.handler.reinitialize(this.resource); |
||||
this.cdr.detectChanges(); |
||||
} |
||||
} |
||||
|
||||
public activate() { |
||||
this.handler.activate(); |
||||
} |
||||
|
||||
public get customText() { |
||||
return this.handler.htmlText; |
||||
} |
||||
|
||||
public get placeholderText() { |
||||
return this.i18n.t('js.grid.widgets.work_packages_overview.placeholder'); |
||||
} |
||||
|
||||
public get inplaceEditClasses() { |
||||
let classes = 'inplace-editing--container wp-edit-field--display-field'; |
||||
|
||||
if (this.textEmpty) { |
||||
classes += ' -placeholder'; |
||||
} |
||||
|
||||
return classes; |
||||
} |
||||
|
||||
public get schema() { |
||||
return this.handler.schema; |
||||
} |
||||
|
||||
public get changeset() { |
||||
return this.handler.changeset; |
||||
} |
||||
|
||||
public get active() { |
||||
return this.handler.active; |
||||
} |
||||
|
||||
public get textEmpty() { |
||||
return !this.customText; |
||||
} |
||||
|
||||
private memorizeRawText() { |
||||
this.currentRawText = (this.resource.options.text as HalResource).raw; |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
import {EditChangeset} from "core-app/modules/fields/changeset/edit-changeset"; |
||||
import {GridWidgetResource} from "core-app/modules/hal/resources/grid-widget-resource"; |
||||
|
||||
export class WidgetChangeset extends EditChangeset<GridWidgetResource> { |
||||
|
||||
} |
@ -1,4 +1,4 @@ |
||||
<ng-container wp-isolated-query-space> |
||||
<widget-wp-table [resource]="resource" |
||||
(resourceChanged)="onResourceChanged(resource)"> </widget-wp-table> |
||||
(resourceChanged)="onResourceChanged($event)"> </widget-wp-table> |
||||
</ng-container> |
||||
|
@ -1,13 +1,14 @@ |
||||
import {Component} from '@angular/core'; |
||||
import {AbstractWidgetComponent} from "core-app/modules/grids/widgets/abstract-widget.component"; |
||||
import {GridWidgetResource} from "core-app/modules/hal/resources/grid-widget-resource"; |
||||
import {WidgetChangeset} from "core-app/modules/grids/widgets/widget-changeset"; |
||||
|
||||
@Component({ |
||||
templateUrl: './wp-table-qs.component.html', |
||||
styleUrls: ['./wp-table-qs.component.sass'], |
||||
}) |
||||
export class WidgetWpTableQuerySpaceComponent extends AbstractWidgetComponent { |
||||
public onResourceChanged(resource:GridWidgetResource) { |
||||
this.resourceChanged.emit(resource); |
||||
public onResourceChanged(changeset:WidgetChangeset) { |
||||
this.resourceChanged.emit(changeset); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,47 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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-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. |
||||
#++ |
||||
|
||||
module API |
||||
module V3 |
||||
module Boards |
||||
module Widgets |
||||
class BoardOptionsRepresenter < ::API::V3::Grids::Widgets::DefaultOptionsRepresenter |
||||
property :queryId, |
||||
getter: ->(represented:, **) { |
||||
represented['queryId'] || represented['query_id'] |
||||
} |
||||
|
||||
property :filters, |
||||
getter: ->(represented:, **) { |
||||
represented['filters'] |
||||
} |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,97 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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-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. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
require_relative '../support/pages/dashboard' |
||||
|
||||
describe 'Project description widget on dashboard', type: :feature, js: true do |
||||
let!(:project) do |
||||
FactoryBot.create :project |
||||
end |
||||
|
||||
let(:permissions) do |
||||
%i[view_dashboards |
||||
manage_dashboards] |
||||
end |
||||
|
||||
let(:role) do |
||||
FactoryBot.create(:role, permissions: permissions) |
||||
end |
||||
|
||||
let(:user) do |
||||
FactoryBot.create(:user, member_in_project: project, member_with_permissions: permissions) |
||||
end |
||||
let(:dashboard_page) do |
||||
Pages::Dashboard.new(project) |
||||
end |
||||
|
||||
before do |
||||
login_as user |
||||
|
||||
dashboard_page.visit! |
||||
end |
||||
|
||||
it 'can add the widget and see the description in it' do |
||||
dashboard_page.add_column(3, before_or_after: :before) |
||||
|
||||
sleep(0.1) |
||||
|
||||
dashboard_page.add_widget(2, 3, "Custom text") |
||||
|
||||
sleep(0.1) |
||||
|
||||
# As the user lacks the manage_public_queries and save_queries permission, no other widget is present |
||||
custom_text_widget = Components::Grids::GridArea.new('.grid--area.-widgeted:nth-of-type(1)') |
||||
|
||||
custom_text_widget.expect_to_span(2, 3, 5, 5) |
||||
custom_text_widget.resize_to(6, 5) |
||||
|
||||
custom_text_widget.expect_to_span(2, 3, 7, 6) |
||||
|
||||
within custom_text_widget.area do |
||||
find('.inplace-editing--trigger-container').click |
||||
|
||||
field = WorkPackageEditorField.new(page, 'description', selector: '.wp-inline-edit--active-field') |
||||
|
||||
field.set_value('My own little text') |
||||
field.save! |
||||
|
||||
expect(page) |
||||
.to have_selector('.wp-edit-field--display-field', text: 'My own little text') |
||||
|
||||
find('.inplace-editing--trigger-container').click |
||||
|
||||
field.set_value('My new text') |
||||
field.cancel_by_click |
||||
|
||||
expect(page) |
||||
.to have_selector('.wp-edit-field--display-field', text: 'My own little text') |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,196 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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-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. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
require 'rack/test' |
||||
|
||||
describe 'API v3 Grids resource', type: :request, content_type: :json do |
||||
include Rack::Test::Methods |
||||
include API::V3::Utilities::PathHelper |
||||
|
||||
let(:current_user) do |
||||
FactoryBot.create(:user, |
||||
member_in_project: project, |
||||
member_with_permissions: permissions) |
||||
end |
||||
let(:permissions) { %i[view_dashboards manage_dashboards] } |
||||
let(:project) { FactoryBot.create(:project) } |
||||
let(:grid) do |
||||
FactoryBot.create(:dashboard, |
||||
project: project, |
||||
widgets: widgets) |
||||
end |
||||
let(:widgets) do |
||||
[FactoryBot.create(:grid_widget, |
||||
identifier: 'custom_text', |
||||
start_column: 1, |
||||
end_column: 3, |
||||
start_row: 1, |
||||
end_row: 3, |
||||
options: { |
||||
text: custom_text |
||||
})] |
||||
end |
||||
let(:custom_text) { "Some text a user wrote" } |
||||
|
||||
before do |
||||
login_as(current_user) |
||||
end |
||||
|
||||
subject(:response) { last_response } |
||||
|
||||
describe '#get' do |
||||
let(:path) { api_v3_paths.grid(grid.id) } |
||||
|
||||
let(:stored_grids) do |
||||
grid |
||||
end |
||||
|
||||
before do |
||||
stored_grids |
||||
|
||||
get path |
||||
end |
||||
|
||||
it 'responds with 200 OK' do |
||||
expect(subject.status).to eq(200) |
||||
end |
||||
|
||||
it 'sends a grid block' do |
||||
expect(subject.body) |
||||
.to be_json_eql('Grid'.to_json) |
||||
.at_path('_type') |
||||
end |
||||
|
||||
it 'identifies the url the grid is stored for' do |
||||
expect(subject.body) |
||||
.to be_json_eql(project_dashboards_path(project).to_json) |
||||
.at_path('_links/scope/href') |
||||
end |
||||
|
||||
it 'has a widget that renders custom text' do |
||||
expect(subject.body) |
||||
.to be_json_eql('custom_text'.to_json) |
||||
.at_path('widgets/0/identifier') |
||||
|
||||
expect(subject.body) |
||||
.to be_json_eql(custom_text.to_json) |
||||
.at_path('widgets/0/options/text/raw') |
||||
end |
||||
|
||||
context 'with the grid not existing' do |
||||
let(:path) { api_v3_paths.grid(grid.id + 1) } |
||||
|
||||
it 'responds with 404 NOT FOUND' do |
||||
expect(subject.status).to eql 404 |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe '#patch' do |
||||
let(:path) { api_v3_paths.grid(grid.id) } |
||||
|
||||
let(:stored_grids) do |
||||
grid |
||||
end |
||||
|
||||
let(:widget_params) { [] } |
||||
|
||||
let(:params) do |
||||
{ |
||||
"rowCount": 10, |
||||
"name": 'foo', |
||||
"columnCount": 15, |
||||
"widgets": widget_params |
||||
}.with_indifferent_access |
||||
end |
||||
|
||||
before do |
||||
stored_grids |
||||
|
||||
patch path, params.to_json |
||||
end |
||||
|
||||
context 'with an added custom_text widget' do |
||||
let(:widget_params) do |
||||
[ |
||||
{ |
||||
"startColumn": 1, |
||||
"startRow": 1, |
||||
"endColumn": 3, |
||||
"endRow": 3, |
||||
"identifier": "custom_text", |
||||
"options": { |
||||
"name": "Name for custom text widget", |
||||
"text": { |
||||
"format": "markdown", |
||||
"raw": "A custom text text", |
||||
"html": "<p>A custom text text</p>" |
||||
} |
||||
} |
||||
}.with_indifferent_access |
||||
] |
||||
end |
||||
let(:widgets) { [] } |
||||
|
||||
it 'responds with 200 OK' do |
||||
expect(subject.status).to eq(200) |
||||
end |
||||
|
||||
it 'returns the altered grid block with the added widget' do |
||||
expect(subject.body) |
||||
.to be_json_eql('Grid'.to_json) |
||||
.at_path('_type') |
||||
expect(subject.body) |
||||
.to be_json_eql('foo'.to_json) |
||||
.at_path('name') |
||||
expect(subject.body) |
||||
.to be_json_eql(params['rowCount'].to_json) |
||||
.at_path('rowCount') |
||||
expect(subject.body) |
||||
.to be_json_eql(params['widgets'][0]['identifier'].to_json) |
||||
.at_path('widgets/0/identifier') |
||||
expect(subject.body) |
||||
.to be_json_eql(params['widgets'][0]['options']['text']['raw'].to_json) |
||||
.at_path('widgets/0/options/text/raw') |
||||
expect(subject.body) |
||||
.to be_json_eql(params['widgets'][0]['options']['name'].to_json) |
||||
.at_path('widgets/0/options/name') |
||||
end |
||||
|
||||
it 'perists the changes' do |
||||
expect(grid.reload.row_count) |
||||
.to eql params['rowCount'] |
||||
expect(grid.reload.widgets[0].options['text']) |
||||
.to eql params['widgets'][0]['options']['text']['raw'] |
||||
expect(grid.reload.widgets[0].options['name']) |
||||
.to eql params['widgets'][0]['options']['name'] |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,46 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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-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. |
||||
#++ |
||||
|
||||
module API |
||||
module V3 |
||||
module Grids |
||||
module Widgets |
||||
class CustomTextOptionsRepresenter < DefaultOptionsRepresenter |
||||
include API::Decorators::FormattableProperty |
||||
|
||||
formattable_property :text, |
||||
getter: ->(*) do |
||||
::API::Decorators::Formattable.new(represented[:text], |
||||
object: represented, |
||||
plain: false) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,42 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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-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. |
||||
#++ |
||||
|
||||
module API |
||||
module V3 |
||||
module Grids |
||||
module Widgets |
||||
class DefaultOptionsRepresenter < ::API::Decorators::Single |
||||
property :name, |
||||
getter: ->(represented:, **) { |
||||
represented['name'] |
||||
} |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,49 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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-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. |
||||
#++ |
||||
|
||||
module API |
||||
module V3 |
||||
module Grids |
||||
module Widgets |
||||
class QueryOptionsRepresenter < DefaultOptionsRepresenter |
||||
property :queryId, |
||||
getter: ->(represented:, **) { |
||||
represented['queryId'] |
||||
} |
||||
|
||||
# This is required for initialization where the values |
||||
# are stored like this so the front end can then initialize it. |
||||
property :queryProps, |
||||
getter: ->(represented:, **) { |
||||
represented['queryProps'] |
||||
} |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,58 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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-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. |
||||
#++ |
||||
|
||||
module API |
||||
module V3 |
||||
module Grids |
||||
module Widgets |
||||
class WidgetRepresenter < ::API::Decorators::Single |
||||
property :id |
||||
property :identifier |
||||
property :start_row |
||||
property :end_row |
||||
property :start_column |
||||
property :end_column |
||||
|
||||
property :options, |
||||
getter: ->(represented:, decorator:, **) { |
||||
::Grids::Configuration |
||||
.widget_strategy(represented.grid.class, represented.identifier) |
||||
.options_representer |
||||
.constantize |
||||
.new(represented.options.with_indifferent_access, |
||||
current_user: decorator.current_user) |
||||
} |
||||
|
||||
def _type |
||||
'GridWidget' |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue