Skip to content

Commit

Permalink
Add multiedit support for entity attributes (#401)
Browse files Browse the repository at this point in the history
* add multiedit support for entity attributes

* update modal function

---------

Signed-off-by: Vinzenz Rosenkranz <[email protected]>
  • Loading branch information
v1r0x authored Oct 9, 2023
1 parent 22d8cff commit 24a806d
Show file tree
Hide file tree
Showing 17 changed files with 687 additions and 219 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog
All notable changes to this project will be documented in this file.

## 0.10 - Jelling
### Added
- Support Multiediting attributes across multiple entities (through ...-menu in entity tree)
### Changed
- Entity tree sorting is now accessible through ...-menu

## 0.9.14
### Added
- Changelog Viewer
Expand Down
96 changes: 95 additions & 1 deletion app/AttributeValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
namespace App;

use App\Exceptions\InvalidDataException;
use App\Plugins\Map\App\Geodata;
use Illuminate\Database\Eloquent\Model;
use MStaack\LaravelPostgis\Eloquent\PostgisTrait;
use App\Traits\CommentTrait;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;
use stdClass;

class AttributeValue extends Model implements Searchable
{
Expand Down Expand Up @@ -102,10 +104,102 @@ public function patch($values) {
$this->save();
}

public static function getFormattedKeyValue($datatype, $rawValue) : stdClass {
$keyValue = new stdClass();

switch($datatype) {
// for primitive types: just save them to the db
case 'stringf':
case 'string':
case 'iconclass':
case 'rism':
$key = 'str_val';
$val = $rawValue;
break;
case 'double':
$key = 'dbl_val';
$val = $rawValue;
break;
case 'boolean':
case 'percentage':
case 'integer':
$key = 'int_val';
$val = $rawValue;
break;
case 'date':
$key = 'dt_val';
$val = $rawValue;
break;
case 'string-sc':
$key = 'thesaurus_val';
$val = $rawValue['concept_url'];
break;
case 'string-mc':
$key = 'json_val';
$thesaurus_urls = [];
foreach($rawValue as $val) {
$thesaurus_urls[] = [
"concept_url" => $val['concept_url'],
"id" => $val['id']
];
}
$val = json_encode($thesaurus_urls);
break;
case 'epoch':
case 'timeperiod':
case 'dimension':
case 'list':
case 'table':
$key = 'json_val';
// check for invalid time spans
if($datatype == 'epoch' || $datatype == 'timeperiod') {
$sl = isset($rawValue['startLabel']) ? strtoupper($rawValue['startLabel']) : null;
$el = isset($rawValue['endLabel']) ? strtoupper($rawValue['endLabel']) : null;
$s = $rawValue['start'];
$e = $rawValue['end'];
if(
(isset($s) && !isset($sl))
||
(isset($e) && !isset($el))
) {
throw new InvalidDataException(__('You have to specify if your date is BC or AD.'));
}
if(
($sl == 'AD' && $el == 'BC')
||
($sl == 'BC' && $el == 'BC' && $s < $e)
||
($sl == 'AD' && $el == 'AD' && $s > $e)
) {
throw new InvalidDataException(__('Start date of a time period must not be after it\'s end date'));
}
}
$val = json_encode($rawValue);
break;
case 'entity':
$key = 'entity_val';
$val = $rawValue;
break;
case 'entity-mc':
$key = 'json_val';
$val = json_encode($rawValue);
break;
case 'geography':
$key = 'geography_val';
$val = Geodata::parseWkt($rawValue);
break;
}

$keyValue->key = $key;
$keyValue->val = $val;

return $keyValue;
}

// Does not handle InvalidDataException and AmbiguousValueException in stringToValue method here!
// Throws InvalidDataException
// Throws AmbiguousValueException
public function setValue($strValue, $type = null, $save = false) {
public function setValueFromRaw($strValue, $type = null, $save = false) {
if(!isset($type)) {
$type = Attribute::first($this->attribute_id)->datatype;
}
Expand Down
136 changes: 59 additions & 77 deletions app/Http/Controllers/EntityController.php
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ public function importData(Request $request) {
$attrVal->certainty = 100;
$attrVal->user_id = $user->id;
try {
$setValue = $attrVal->setValue($row[$val], $type);
$setValue = $attrVal->setValueFromRaw($row[$val], $type);
if($setValue === null) {
continue;
}
Expand Down Expand Up @@ -565,83 +565,14 @@ public function patchAttributes($id, Request $request) {
if($op == 'remove') continue;

$attr = Attribute::find($aid);
switch($attr->datatype) {
// for primitive types: just save them to the db
case 'stringf':
case 'string':
case 'iconclass':
case 'rism':
$attrval->str_val = $value;
break;
case 'double':
$attrval->dbl_val = $value;
break;
case 'boolean':
case 'percentage':
case 'integer':
$attrval->int_val = $value;
break;
case 'date':
$attrval->dt_val = $value;
break;
case 'string-sc':
$thesaurus_url = $value['concept_url'];
$attrval->thesaurus_val = $thesaurus_url;
break;
case 'string-mc':
$thesaurus_urls = [];
foreach($value as $val) {
$thesaurus_urls[] = [
"concept_url" => $val['concept_url'],
"id" => $val['id']
];
}
$attrval->json_val = json_encode($thesaurus_urls);
break;
case 'epoch':
case 'timeperiod':
case 'dimension':
case 'list':
case 'table':
// check for invalid time spans
if($attr->datatype == 'epoch' || $attr->datatype == 'timeperiod') {
$sl = isset($value['startLabel']) ? strtoupper($value['startLabel']) : null;
$el = isset($value['endLabel']) ? strtoupper($value['endLabel']) : null;
$s = $value['start'];
$e = $value['end'];
if(
(isset($s) && !isset($sl))
||
(isset($e) && !isset($el))
) {
return response()->json([
'error' => __('You have to specify if your date is BC or AD.')
], 422);
}
if(
($sl == 'AD' && $el == 'BC')
||
($sl == 'BC' && $el == 'BC' && $s < $e)
||
($sl == 'AD' && $el == 'AD' && $s > $e)
) {
return response()->json([
'error' => __('Start date of a time period must not be after it\'s end date')
], 422);
}
}
$attrval->json_val = json_encode($value);
break;
case 'entity':
$attrval->entity_val = $value;
break;
case 'entity-mc':
$attrval->json_val = json_encode($value);
break;
case 'geography':
$attrval->geography_val = Geodata::parseWkt($value);
break;
try {
$formKeyValue = AttributeValue::getFormattedKeyValue($attr->datatype, $value);
} catch(InvalidDataException $ide) {
return response()->json([
'error' => $ide->getMessage(),
], 422);
}
$attrval->{$formKeyValue->col} = $formKeyValue->val;
$attrval->user_id = $user->id;
$attrval->save();
}
Expand Down Expand Up @@ -706,6 +637,57 @@ public function patchAttribute($id, $aid, Request $request) {
return response()->json($attrValue, 201);
}

public function multieditAttributes(Request $request) {
$user = auth()->user();
if(!$user->can('entity_data_write')) {
return response()->json([
'error' => __('You do not have the permission to modify an entity\'s data')
], 403);
}

$this->validate($request, [
'entity_ids' => 'required|array',
'entries' => 'required|array',
]);

$entities = $request->get('entity_ids');
$attrValues = $request->get('entries');

DB::beginTransaction();

foreach($attrValues as $av) {
try {
$attr = Attribute::findOrFail($av['attribute_id']);
} catch(ModelNotFoundException $e) {
DB::rollBack();
return response()->json([
'error' => __('This attribute does not exist'),
], 400);
}
try {
$formKeyValue = AttributeValue::getFormattedKeyValue($attr->datatype, $av['value']);
} catch(InvalidDataException $ide) {
DB::rollBack();
return response()->json([
'error' => $ide->getMessage(),
], 422);
}
foreach($entities as $eid) {
AttributeValue::updateOrCreate(
['entity_id' => $eid, 'attribute_id' => $av['attribute_id']],
[
$formKeyValue->key => $formKeyValue->val,
'user_id' => $user->id,
]
);
}
}

DB::commit();

return response()->json(null, 204);
}

public function patchName($id, Request $request) {
$user = auth()->user();
if(!$user->can('entity_write')) {
Expand Down
14 changes: 14 additions & 0 deletions resources/js/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,20 @@ export async function patchAttributes(entityId, data) {
);
};

export async function multieditAttributes(entityIds, entries) {
const data = {
entity_ids: entityIds,
entries: entries,
};
return $httpQueue.add(
() => http.patch(`/entity/multiedit`, data).then(response => {
return response.data;
}).catch(error => {
throw error;
})
);
};

export async function moveEntity(entityId, parentId = null, rank = null) {
const data = {
parent_id: parentId,
Expand Down
2 changes: 2 additions & 0 deletions resources/js/bootstrap/font.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ import {
faLightbulb,
faLink,
faList,
faListCheck,
faListOl,
faListUl,
faLongArrowAltDown,
Expand Down Expand Up @@ -300,6 +301,7 @@ library.add(
faLightbulb,
faLink,
faList,
faListCheck,
faListOl,
faListUl,
faLongArrowAltDown,
Expand Down
Loading

0 comments on commit 24a806d

Please sign in to comment.