Skip to content

Commit

Permalink
Merge pull request #242 from markusguenther/feature/localized-exceptions
Browse files Browse the repository at this point in the history
FEATURE: Use custom transformer for flow exceptions
  • Loading branch information
Sebobo authored Nov 4, 2024
2 parents f236534 + 6646240 commit d7e8843
Show file tree
Hide file tree
Showing 11 changed files with 603 additions and 29 deletions.
48 changes: 44 additions & 4 deletions Classes/GraphQL/Resolver/Type/MutationResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Flowpack\Media\Ui\GraphQL\Context\AssetSourceContext;
use Flowpack\Media\Ui\Service\AssetCollectionService;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\I18n\Translator;
use Neos\Flow\Persistence\Exception\IllegalObjectTypeException;
use Neos\Flow\Persistence\Exception\InvalidQueryException;
use Neos\Flow\Persistence\PersistenceManagerInterface;
Expand Down Expand Up @@ -113,6 +114,23 @@ class MutationResolver implements ResolverInterface
*/
protected $assetCollectionService;

/**
* @Flow\Inject
* @var Translator
*/
protected $translator;

protected function localizedMessage(string $id, string $fallback = '', array $arguments = []): string
{
return $this->translator->translateById($id, [], null, null, 'Main', 'Flowpack.Media.Ui') ?? $fallback;
}

protected function localizedMessageFromException(\Exception $exception): string
{
$labelIdentifier = 'errors.' . $exception->getCode() . '.message';
return $this->localizedMessage($labelIdentifier, $exception->getMessage());
}

/**
* @throws Exception
*/
Expand All @@ -125,23 +143,45 @@ public function deleteAsset($_, array $variables, AssetSourceContext $assetSourc

$assetProxy = $assetSourceContext->getAssetProxy($id, $assetSourceId);
if (!$assetProxy) {
return new MutationResult(false, ['Asset could not be resolved']);
return new MutationResult(
false,
[$this->localizedMessage(
'actions.deleteAssets.noProxy',
'Asset could not be resolved')
]
);
}
$asset = $assetSourceContext->getAssetForProxy($assetProxy);

if (!$asset) {
return new MutationResult(false, ['Cannot delete asset that was never imported']);
return new MutationResult(
false,
[
$this->localizedMessage(
'actions.deleteAssets.noImportExists',
'Cannot delete asset that was never imported'
)
]
);
}

try {
$this->assetRepository->remove($asset);
} catch (AssetServiceException $e) {
return new MutationResult(false, [$e->getMessage()]);
return new MutationResult(false, [$this->localizedMessageFromException($e)]);
} catch (\Exception $e) {
throw new Exception('Failed to delete asset', 1591537315);
}

return new MutationResult(true, ['Asset deleted']);
return new MutationResult(
true,
[
$this->localizedMessage(
'actions.deleteAssets.success',
'Asset deleted'
)
]
);
}

/**
Expand Down
49 changes: 49 additions & 0 deletions Classes/Transform/FlowErrorTransform.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Flowpack\Media\Ui\Transform;

use GraphQL\Error\Error;
use GraphQL\Executor\ExecutionResult;
use GraphQLTools\Transforms\Transform;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Log\ThrowableStorageInterface;

/**
* This transform is used to convert exceptions to errors in the GraphQL response.
* To be able to localize error messages we extend the FlowErrorTransform from the t3n.GraphQL package.
*/
class FlowErrorTransform extends \t3n\GraphQL\Transform\FlowErrorTransform
{
public function transformResult(ExecutionResult $result): ExecutionResult
{
$result->errors = array_map(function (Error $error) {
$previousError = $error->getPrevious();
if (!$previousError instanceof Error) {
$message = $this->throwableStorage->logThrowable($previousError);

if (!$this->includeExceptionMessageInOutput) {
$message = preg_replace('/.* - See also: (.+)\.txt$/s', 'Internal error ($1)', $message);
}

$errorExtendedInformation = $error->getExtensions();
$errorExtendedInformation['errorCode'] = $previousError->getCode();

return new Error(
$message,
$error->getNodes(),
$error->getSource(),
$error->getPositions(),
$error->getPath(),
$previousError,
$errorExtendedInformation
);
}

return $error;
}, $result->errors);

return $result;
}
}
1 change: 1 addition & 0 deletions Configuration/Settings.GraphQL.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ t3n:
'media-assets':
logRequests: false
context: 'Flowpack\Media\Ui\GraphQL\Context\AssetSourceContext'
errorTransform: 'Flowpack\Media\Ui\Transform\FlowErrorTransform'
schemas:
root:
typeDefs: 'resource://Flowpack.Media.Ui/Private/GraphQL/schema.root.graphql'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@ const CreateAssetCollectionDialog = () => {
.then(() => {
Notify.ok(translate('assetCollectionActions.create.success', 'Asset collection was created'));
})
.catch((error) => {
Notify.error(
translate('assetCollectionActions.create.error', 'Failed to create asset collection'),
error.message
);
.catch(() => {
return;
});
}, [setDialogVisible, createAssetCollection, title, selectedAssetCollection?.id, Notify, translate]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,8 @@ const DeleteButton: React.FC = () => {
);
setSelectedAssetCollectionAndTag({ tagId: null, assetCollectionId: null });
})
.catch((error) => {
Notify.error(
translate('assetCollectionActions.delete.error', 'Failed to delete asset collection'),
error.message
);
.catch(() => {
return;
});
}
}, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ const CreateTagDialog: React.FC = () => {
.then(() => {
Notify.ok(translate('tagActions.create.success', 'Tag was created'));
})
.catch((error) => {
Notify.error(translate('tagActions.create.error', 'Failed to create tag'), error.message);
.catch(() => {
return;
});
}, [Notify, setDialogState, createTag, dialogState, translate, selectedAssetCollection]);
const setLabel = useCallback((label) => setDialogState((state) => ({ ...state, label })), [setDialogState]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ const NewAssetDialog: React.FC = () => {
void refetch();
}
})
.catch((error) => {
Notify.error(translate('fileUpload.error', 'Upload failed'), error);
.catch(() => {
return;
});
}, [uploadFiles, dialogState.files.selected, setFiles, Notify, translate, refetch]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const AssetActions: React.FC<ItemActionsProps> = ({ asset }: ItemActionsProps) =
.then(() => {
Notify.ok(translate('assetActions.import.success', 'Asset was successfully imported'));
})
.catch((error) => {
Notify.error(translate('assetActions.import.error', 'Failed to import asset'), error.message);
.catch(() => {
return;
});
}, [importAsset, asset, Notify, translate]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import { onError } from '@apollo/client/link/error';

const createErrorHandler = (notify: NeosNotification) => {
const translate = (id, value = null, args = {}, packageKey = 'Flowpack.Media.Ui', source = 'Main') => {
return window.NeosCMS.I18n.translate(id, value, packageKey, source, args);
};

return onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map((data) => {
const isInternalError = data.extensions.code === 'INTERNAL_SERVER_ERROR';

console.error(
data.extensions.code === isInternalError ? '[Internal server error]' : '[GraphQL error]',
data.path,
data
);
const isInternalError = data.extensions.category === 'internal';
const defaultErrorTitle = isInternalError
? translate('errors.internal.title', 'Internal server error')
: translate('errors.graphql.title', 'Communication error');
let errorMessageLabel = '';
let errorTitleLabel = '';
if (data.extensions.errorCode) {
errorTitleLabel = `errors.${data.extensions.errorCode}.title`;
errorMessageLabel = `errors.${data.extensions.errorCode}.message`;
}

notify.error(
data.extensions.code === isInternalError ? 'Internal server error' : 'Communication error',
data.message
translate(errorTitleLabel, defaultErrorTitle),
errorMessageLabel.length ? translate(errorMessageLabel) : data.message
);
});
}
Expand Down
Loading

0 comments on commit d7e8843

Please sign in to comment.