Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiedit support for entity attributes #401

Merged
merged 4 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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