blob: 17d2fef4bc4db0c857737183d73924f4905ede88 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* global _ */
/**
* A directive that displays errors that occurred during parsing of a connection
* import file, or errors that were returned from the API during the connection
* batch creation attempt.
*/
angular.module('import').directive('connectionImportErrors', [
function connectionImportErrors() {
const directive = {
restrict: 'E',
replace: true,
templateUrl: 'app/import/templates/connectionErrors.html',
scope: {
/**
* The result of parsing the import file. Any errors in this file
* will be displayed to the user.
*
* @type ParseResult
*/
parseResult : '=',
/**
* The error associated with an attempt to batch create the
* connections represented by the ParseResult, if the ParseResult
* had no errors. If the provided ParseResult has errors, no request
* should have been made, and any provided patch error will be
* ignored.
*
* @type Error
*/
patchFailure : '=',
}
};
directive.controller = ['$scope', '$injector',
function connectionImportErrorsController($scope, $injector) {
// Required types
const DirectoryPatch = $injector.get('DirectoryPatch');
const DisplayErrorList = $injector.get('DisplayErrorList');
const ImportConnectionError = $injector.get('ImportConnectionError');
const ParseError = $injector.get('ParseError');
const SortOrder = $injector.get('SortOrder');
// Required services
const $q = $injector.get('$q');
const $translate = $injector.get('$translate');
// There are errors to display if the parse result generated errors, or
// if the patch request failed
$scope.hasErrors = () =>
!!_.get($scope, 'parseResult.hasErrors') || !!$scope.patchFailure;
/**
* All connections with their associated errors for display. These may
* be either parsing failures, or errors returned from the API. Both
* error types will be adapted to a common display format, though the
* error types will never be mixed, because no REST request should ever
* be made if there are client-side parse errors.
*
* @type {ImportConnectionError[]}
*/
$scope.connectionErrors = [];
/**
* SortOrder instance which maintains the sort order of the visible
* connection errors.
*
* @type SortOrder
*/
$scope.errorOrder = new SortOrder([
'rowNumber',
'name',
'group',
'protocol',
'errors',
]);
/**
* Array of all connection error properties that are filterable.
*
* @type String[]
*/
$scope.filteredErrorProperties = [
'rowNumber',
'name',
'group',
'protocol',
'errors',
];
/**
* Generate a ImportConnectionError representing any errors associated
* with the row at the given index within the given parse result.
*
* @param {ParseResult} parseResult
* The result of parsing the connection import file.
*
* @param {Integer} index
* The current row within the patches array, 0-indexed.
*
* @param {Integer} row
* The current row within the original connection, 0-indexed.
* If any REMOVE patches are present, this may be greater than
* the index.
*
* @returns {ImportConnectionError}
* The connection error object associated with the given row in the
* given parse result.
*/
const generateConnectionError = (parseResult, index, row) => {
// Get the patch associated with the current row
const patch = parseResult.patches[index];
// The value of a patch is just the Connection object
const connection = patch.value;
return new ImportConnectionError({
// Add 1 to the provided row to get the position in the file
rowNumber: row + 1,
// Basic connection information - name, group, and protocol.
name: connection.name,
group: parseResult.groupPaths[index],
protocol: connection.protocol,
// The human-readable error messages
errors: new DisplayErrorList(
[ ...(parseResult.errors[index] || []) ])
});
};
// If a new connection patch failure is seen, update the display list
$scope.$watch('patchFailure', function patchFailureChanged(patchFailure) {
const { parseResult } = $scope;
// Do not attempt to process anything before the data has loaded
if (!patchFailure || !parseResult)
return;
// All promises from all translation requests. The scope will not be
// updated until all translations are ready.
const translationPromises = [];
// Any error returned from the API specifically associated with the
// preceding REMOVE patch
let removeError = null;
// Fetch the API error, if any, of the patch at the given index
const getAPIError = index =>
_.get(patchFailure, ['patches', index, 'error']);
// The row number for display. Unlike the index, this number will
// skip any REMOVE patches. In other words, this is the index of
// connections within the original import file.
let row = 0;
// Set up the list of connection errors based on the existing parse
// result, with error messages fetched from the patch failure
const connectionErrors = parseResult.patches.reduce(
(errors, patch, index) => {
// Do not process display REMOVE patches - they are always
// followed by ADD patches containing the actual content
// (and errors, if any)
if (patch.op === DirectoryPatch.Operation.REMOVE) {
// Save the API error, if any, so it can be displayed
// alongside the connection information associated with the
// following ADD patch
removeError = getAPIError(index);
// Do not add an entry for this remove patch - it should
// always be followed by a corresponding CREATE patch
// containing the relevant connection information
return errors;
}
// Generate a connection error for display
const connectionError = generateConnectionError(
parseResult, index, row++);
// Add the error associated with the previous REMOVE patch, if
// any, to the error associated with the current patch, if any
const apiErrors = [ removeError, getAPIError(index) ];
// Clear the previous REMOVE patch error after consuming it
removeError = null;
// Go through each potential API error
apiErrors.forEach(error =>
// If the API error exists, fetch the translation and
// update it when it's ready
error && translationPromises.push($translate(
error.key, error.variables)
.then(translatedError =>
connectionError.errors.getArray().push(translatedError)
)));
errors.push(connectionError);
return errors;
}, []);
// Once all the translations have been completed, update the
// connectionErrors all in one go, to ensure no excessive reloading
$q.all(translationPromises).then(() => {
$scope.connectionErrors = connectionErrors;
});
});
// If a new parse result with errors is seen, update the display list
$scope.$watch('parseResult', function parseResultChanged(parseResult) {
// Do not process if there are no errors in the provided result
if (!parseResult || !parseResult.hasErrors)
return;
// All promises from all translation requests. The scope will not be
// updated until all translations are ready.
const translationPromises = [];
// The parse result should only be updated on a fresh file import;
// therefore it should be safe to skip checking the patch errors
// entirely - if set, they will be from the previous file and no
// longer relevant.
// The row number for display. Unlike the index, this number will
// skip any REMOVE patches. In other words, this is the index of
// connections within the original import file.
let row = 0;
// Set up the list of connection errors based on the updated parse
// result
const connectionErrors = parseResult.patches.reduce(
(errors, patch, index) => {
// Do not process display REMOVE patches - they are always
// followed by ADD patches containing the actual content
// (and errors, if any)
if (patch.op === DirectoryPatch.Operation.REMOVE)
return errors;
// Generate a connection error for display
const connectionError = generateConnectionError(
parseResult, index, row++);
// Go through the errors and check if any are translateable
connectionError.errors.getArray().forEach(
(error, errorIndex) => {
// If this error is a ParseError, it can be translated.
// NOTE: Generally one would translate error messages in the
// template, but in this case, the connection errors need to
// be raw strings in order to enable sorting and filtering.
if (error instanceof ParseError)
// Fetch the translation and update it when it's ready
translationPromises.push($translate(
error.key, error.variables)
.then(translatedError => {
connectionError.errors.getArray()[errorIndex] = translatedError;
}));
// If the error is not a known translatable type, add the
// message directly to the error array
else
connectionError.errors.getArray()[errorIndex] = (
error.message ? error.message : error);
});
errors.push(connectionError);
return errors;
}, []);
// Once all the translations have been completed, update the
// connectionErrors all in one go, to ensure no excessive reloading
$q.all(translationPromises).then(() => {
$scope.connectionErrors = connectionErrors;
});
});
}];
return directive;
}]);