Skip to content

Commit

Permalink
QE-513: tempId for newly created objects (#3594)
Browse files Browse the repository at this point in the history
* QuestionGroups return tempId - real Id mapping

* Questions return tempId - real Id mapping

* Fix unit test and code check

* SubQuestions return tempId - real Id mapping

* If question with subquestions was created, their mapping will also be returned

* Fixed issue: OpHandlerAnswer does a proper update when aid is provided

* Answers return tempId - real Id mapping

* If question with answers was created, their mapping will now also be returned

* Non DB attributes of Question should be camelcase

* Avoided nesting in if statement

* Response output now handled with classes

* Codestyle fix

* ignore method length warning for PatcherSurvey __construct()

* fixes for psalm

* fixes for psalm

* fixes for psalm

* Dev: Refactor SurveyPatch/PatcherSurvey

---------

Co-authored-by: Kevin Foster <kevin.foster.uk@gmail.com>
  • Loading branch information
twilligls and kevin-foster-uk committed Nov 15, 2023
1 parent e0fefaf commit 6e289f0
Show file tree
Hide file tree
Showing 16 changed files with 499 additions and 115 deletions.
4 changes: 2 additions & 2 deletions application/libraries/Api/Command/V1/SurveyPatch.php
Expand Up @@ -65,14 +65,14 @@ public function run(Request $request)
PatcherSurvey::class
);
try {
$patcher->applyPatch($patch, ['id' => $id]);
$returnedData = $patcher->applyPatch($patch, ['id' => $id]);
} catch (ObjectPatchException $e) {
return $this->responseFactory->makeErrorBadRequest(
$e->getMessage()
);
}

return $this->responseFactory
->makeSuccess(true);
->makeSuccess($returnedData);
}
}
106 changes: 83 additions & 23 deletions application/libraries/Api/Command/V1/SurveyPatch/OpHandlerAnswer.php
Expand Up @@ -54,42 +54,50 @@ public function canHandle(OpInterface $op): bool
* "id" is the qid, so we are only allowing answers for a single question in
* the patch.
* Attention: Currently all answers not provided in the patch
* will be deleted by the service.
* All existing answers are needed independently from "update" or "create".
* will be deleted by the service. Doesn't matter if
* create or update was chosen
* You could also mix updated and newly created answers in one patch.
* The difference is that new created answers need "tempId"
* and updated answers need the "aid" provided.
* The OpHandler doesn't care if you choose create or update as "op"!!!
*
* Example for "update":
* {
* "patch": [{
* "entity": "answer",
* "op": "create", // "update"
* "id": "726", // qid(!)
* "op": "update",
* "id": "809",
* "props": {
* "0": {
* "code": "XX01",
* "sortOrder": 0,
* "aid": 465,
* "code": "AO01",
* "sortOrder": 2,
* "assessmentValue": 0,
* "scaleId": 0,
* "scaleId": 1,
* "l10ns": {
* "de": {
* "answer": "antwort1",
* "answer": "ANTW1 scale 1",
* "language": "de"
* },
* "en": {
* "answer": "answer1",
* "answer": "ANS1 scale 1",
* "language": "en"
* }
* }
* },
* "1": {
* "code": "YY02",
* "sortOrder": 1,
* "aid": 467,
* "code": "AO02",
* "sortOrder": 3,
* "assessmentValue": 0,
* "scaleId": 0,
* "scaleId": 1,
* "l10ns": {
* "de": {
* "answer": "antwort1.2",
* "answer": "ANTW2 scale 1",
* "language": "de"
* },
* "en": {
* "answer": "answer1.2",
* "answer": "ANS2 scale 1",
* "language": "en"
* }
* }
Expand All @@ -99,30 +107,82 @@ public function canHandle(OpInterface $op): bool
* ]
* }
*
* Example for "create":
* {
* "patch": [{
* "entity": "answer",
* "op": "create",
* "id": "809",
* "props": {
* "0": {
* "tempId": "222",
* "code": "AO11",
* "sortOrder": 4,
* "assessmentValue": 0,
* "scaleId": 1,
* "l10ns": {
* "de": {
* "answer": "ANTW11 scale 1",
* "language": "de"
* },
* "en": {
* "answer": "ANS11 scale 1",
* "language": "en"
* }
* }
* },
* "1": {
* "tempId": "223",
* "code": "AO12",
* "sortOrder": 5,
* "assessmentValue": 0,
* "scaleId": 1,
* "l10ns": {
* "de": {
* "answer": "ANTW12 scale 1",
* "language": "de"
* },
* "en": {
* "answer": "ANS12 scale 1",
* "language": "en"
* }
* }
* }
* }
* }
* ]
* }
* @param OpInterface $op
* @return void
* @return array
* @throws OpHandlerException
* @throws \DI\DependencyException
* @throws \DI\NotFoundException
* @throws \LimeSurvey\Models\Services\Exception\NotFoundException
* @throws \LimeSurvey\Models\Services\Exception\PermissionDeniedException
* @throws \LimeSurvey\Models\Services\Exception\PersistErrorException
*/
public function handle(OpInterface $op): void
public function handle(OpInterface $op): array
{
$question = $this->questionService->getQuestionBySidAndQid(
$this->getSurveyIdFromContext($op),
$op->getEntityId()
);
$preparedData = $this->prepareAnswers(
$op,
$op->getProps(),
$this->transformerAnswer,
$this->transformerAnswerL10n,
['answer', 'answerL10n']
);
$this->answersService->save(
$question,
$this->prepareAnswers(
$op,
$op->getProps(),
$this->transformerAnswer,
$this->transformerAnswerL10n,
['answer', 'answerL10n']
)
$preparedData
);

return $this->getSubQuestionNewIdMapping(
$question,
$preparedData,
true
);
}

Expand Down
Expand Up @@ -76,34 +76,35 @@ public function canHandle(OpInterface $op): bool
* "qid": "0",
* "title": "G01Q06",
* "type": "1",
* "question_theme_name": "arrays\/dualscale",
* "questionThemeName": "arrays\/dualscale",
* "gid": "1",
* "mandatory": false,
* "relevance": "1",
* "encrypted": false,
* "save_as_default": false
* "saveAsDefault": false,
* "tempId": "XXX321"
* },
* "questionL10n": {
* "en": {
* "question": "Array Question",
* "help": "Help text"
* },
* "de-informal": {
* "de": {
* "question": "Array ger",
* "help": "help ger"
* }
* },
* "attributes": {
* "dualscale_headerA": {
* "de-informal": {
* "de": {
* "value": "A ger"
* },
* "en": {
* "value": "A"
* }
* },
* "dualscale_headerB": {
* "de-informal": {
* "de": {
* "value": "B ger"
* },
* "en": {
Expand All @@ -122,6 +123,7 @@ public function canHandle(OpInterface $op): bool
* "sortOrder": 0,
* "assessmentValue": 0,
* "scaleId": 0,
* "tempId": "111",
* "l10ns": {
* "de": {
* "answer": "antwort1",
Expand All @@ -138,6 +140,7 @@ public function canHandle(OpInterface $op): bool
* "sortOrder": 1,
* "assessmentValue": 0,
* "scaleId": 0,
* "tempId": "112",
* "l10ns": {
* "de": {
* "answer": "antwort1.2",
Expand All @@ -155,8 +158,9 @@ public function canHandle(OpInterface $op): bool
* "title": "SQ001",
* "sortOrder": 0,
* "relevance": "1",
* "tempId": "113",
* "l10ns": {
* "de-informal": {
* "de": {
* "question": "subger1",
* "language": "de"
* },
Expand All @@ -170,8 +174,9 @@ public function canHandle(OpInterface $op): bool
* "title": "SQ002",
* "sortOrder": 1,
* "relevance": "1",
* "tempId": "114",
* "l10ns": {
* "de-informal": {
* "de": {
* "question": "subger2",
* "language": "de"
* },
Expand All @@ -188,24 +193,62 @@ public function canHandle(OpInterface $op): bool
* }
*
* @param OpInterface $op
* @return void
* @return array
* @throws OpHandlerException
* @throws \DI\DependencyException
* @throws \DI\NotFoundException
* @throws \LimeSurvey\Models\Services\Exception\NotFoundException
* @throws \LimeSurvey\Models\Services\Exception\PermissionDeniedException
* @throws \LimeSurvey\Models\Services\Exception\PersistErrorException
*/
public function handle(OpInterface $op): void
public function handle(OpInterface $op): array
{
$transformedProps = $this->prepareData($op);
if (
!is_array($transformedProps) ||
!array_key_exists(
'question',
$transformedProps
)
) {
throw new OpHandlerException(
sprintf(
'no question entity provided within props for %s with id "%s"',
$this->entity,
print_r($op->getEntityId(), true)
)
);
}
$tempId = $this->extractTempId($transformedProps['question']);
$diContainer = \LimeSurvey\DI::getContainer();
$questionService = $diContainer->get(
QuestionAggregateService::class
);

$questionService->save(
$question = $questionService->save(
$this->getSurveyIdFromContext($op),
$this->prepareData($op)
$transformedProps
);

return array_merge(
[
'questionsMap' => [
new TempIdMapItem(
$tempId,
$question->qid,
'qid'
)
]
],
$this->getSubQuestionNewIdMapping(
$question,
$transformedProps['subquestions']
),
$this->getSubQuestionNewIdMapping(
$question,
$transformedProps['answeroptions'],
true
)
);
}

Expand Down
Expand Up @@ -70,19 +70,19 @@ public function canHandle(OpInterface $op): bool
* @param OpInterface $op
* @throws OpHandlerException
*/
public function handle(OpInterface $op): void
public function handle(OpInterface $op): array
{
switch (true) {
case $this->isUpdateOperation:
$this->update($op);
break;
case $this->isCreateOperation:
$this->create($op);
break;
return $this->create($op);
case $this->isDeleteOperation:
$this->delete($op);
break;
}
return [];
}

/**
Expand Down Expand Up @@ -210,6 +210,7 @@ private function update(OpInterface $op)
* "op": "create",
* "props":{
* "questionGroup": {
* "tempId": 777,
* "randomizationGroup": "",
* "gRelevance": ""
* },
Expand All @@ -235,19 +236,27 @@ private function update(OpInterface $op)
*
* @param OpInterface $op
* @param QuestionGroupService $groupService
* @return void
* @return array
* @throws OpHandlerException
* @throws \LimeSurvey\Models\Services\Exception\NotFoundException
* @throws \LimeSurvey\Models\Services\Exception\PersistErrorException
*/
private function create(OpInterface $op)
private function create(OpInterface $op): array
{
$surveyId = $this->getSurveyIdFromContext($op);
$transformedProps = $this->getTransformedProps($op);
$this->questionGroupService->createGroup(
$tempId = $this->extractTempId($transformedProps['questionGroup']);
$questionGroup = $this->questionGroupService->createGroup(
$surveyId,
$transformedProps
);
$questionGroup->refresh();
return [
'questionGroupsMap' => [
'tempId' => $tempId,
'gid' => $questionGroup->gid
]
];
}

/**
Expand Down

0 comments on commit 6e289f0

Please sign in to comment.