/// <summary>
        /// Process a task and update the status
        /// </summary>
        /// <param name="task">The task item to process</param>
        /// <param name="token">The cancellation token, if any</param>
        /// <returns></returns>
        public virtual async Task <TransformationProcessTaskStatus> ProcessTaskAsync(PageTransformationTask task,
                                                                                     CancellationToken token = default)
        {
            if (task == null)
            {
                throw new ArgumentNullException(nameof(task));
            }

            var logger            = ServiceProvider.GetRequiredService <ILogger <LongRunningTransformationProcessBase> >();
            var pageTransformator = ServiceProvider.GetRequiredService <IPageTransformator>();

            // Retrieve the status for the task
            var taskStatus = await GetTaskStatusAsync(task.Id, token).ConfigureAwait(false);

            // Check if task has been already processed
            if (taskStatus.State != TransformationTaskExecutionState.Pending)
            {
                // Skip
                return(taskStatus);
            }

            // Retrieve current process status
            var status = await GetStatusAsync(token).ConfigureAwait(false);

            // Process is not running, skip the task
            if (status.State != TransformationExecutionState.Running)
            {
                // Mark task as aborted
                taskStatus = TransformationProcessTaskStatus.CreateNormal(Id, task.Id, taskStatus.CreationDate, taskStatus.StartDate, DateTimeOffset.Now, TransformationTaskExecutionState.Aborted);
                await ChangeTaskStatusAsync(taskStatus, token).ConfigureAwait(false);

                return(taskStatus);
            }

            try
            {
                // Mark task as running
                taskStatus = TransformationProcessTaskStatus.CreateNormal(Id, task.Id, taskStatus.CreationDate, DateTimeOffset.Now, null, TransformationTaskExecutionState.Running);
                await ChangeTaskStatusAsync(taskStatus, token).ConfigureAwait(false);

                // Run the actual transformation task
                await pageTransformator.TransformAsync(task, token).ConfigureAwait(false);

                // Mark task as completed
                taskStatus = TransformationProcessTaskStatus.CreateNormal(Id, task.Id, taskStatus.CreationDate, taskStatus.StartDate, DateTimeOffset.Now, TransformationTaskExecutionState.Completed);
                await ChangeTaskStatusAsync(taskStatus, token).ConfigureAwait(false);

                return(taskStatus);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Error while transforming task {id}", task.Id);

                // Mark task as faulted
                taskStatus = TransformationProcessTaskStatus.CreateFaulted(Id, task.Id, taskStatus.CreationDate, taskStatus.StartDate, DateTimeOffset.Now, ex);
                await ChangeTaskStatusAsync(taskStatus, token).ConfigureAwait(false);

                return(taskStatus);
            }
        }
 /// <summary>
 /// Adds the task to a queue in order to process it asynchronously
 /// </summary>
 /// <param name="task">The task item to enqueue</param>
 /// <param name="token">The cancellation token, if any</param>
 protected abstract Task EnqueueTaskAsync(PageTransformationTask task, CancellationToken token);
        /// <summary>
        /// Transforms a page from the configured data source to a modern SharePoint Online page
        /// </summary>
        /// <param name="task">The context of the transformation process</param>
        /// <param name="token">The cancellation token, if any</param>
        /// <returns>The URL of the transformed page</returns>
        public virtual async Task <Uri> TransformAsync(PageTransformationTask task, CancellationToken token = default)
        {
            if (task == null)
            {
                throw new ArgumentNullException(nameof(task));
            }

            logger.LogInformation(
                TransformationResources.Info_RunningTransformationTask.CorrelateString(task.Id),
                task.Id, task.SourceItemId.Id);

            // Get the source item by id
            var sourceItem = await task.SourceProvider.GetItemAsync(task.SourceItemId, token).ConfigureAwait(false);

            // Resolve the target page uri
            var targetPageUri = await targetPageUriResolver.ResolveAsync(sourceItem, task.TargetContext, token).ConfigureAwait(false);

            // Call pre transformations handlers
            var preContext = new PagePreTransformationContext(task, sourceItem, targetPageUri);

            foreach (var pagePreTransformation in pagePreTransformations)
            {
                await pagePreTransformation.PreTransformAsync(preContext, token).ConfigureAwait(false);

                token.ThrowIfCancellationRequested();
            }

            // Save start date and time for telemetry
            DateTime transformationStartDateTime = DateTime.Now;

            // Invoke the configured main mapping provider
            var context = new PageTransformationContext(task, sourceItem, targetPageUri);
            var input   = new MappingProviderInput(context);
            MappingProviderOutput output = await mappingProvider.MapAsync(input, token).ConfigureAwait(false);

            token.ThrowIfCancellationRequested();

            // Here we generate the actual SPO modern page in SharePoint Online
            var generatedPage = await pageGenerator.GenerateAsync(context, output, targetPageUri, token).ConfigureAwait(false);

            var generatedPageUri = generatedPage.GeneratedPageUrl;

            token.ThrowIfCancellationRequested();

            // Save duration for telemetry
            TimeSpan duration = DateTime.Now.Subtract(transformationStartDateTime);

            // Save telemetry
            var telemetryProperties = output.TelemetryProperties.Merge(generatedPage.TelemetryProperties);

            // Add global telemetry properties
            telemetryProperties.Add(TelemetryService.CorrelationId, task.Id.ToString());
            telemetryProperties.Add(TelemetryService.AADTenantId, context.Task.TargetContext.GlobalOptions.AADTenantId.ToString());

            this.telemetry.LogTransformationCompleted(duration, telemetryProperties);

            // Call post transformations handlers
            var postContext = new PagePostTransformationContext(task, sourceItem, generatedPageUri);

            foreach (var pagePostTransformation in pagePostTransformations)
            {
                await pagePostTransformation.PostTransformAsync(postContext, token).ConfigureAwait(false);

                token.ThrowIfCancellationRequested();
            }

            return(generatedPageUri);
        }
 /// <summary>
 /// Creates an instance of PagePostTransformationContext
 /// </summary>
 /// <param name="task">The page transformation task</param>
 /// <param name="sourceItem">The source item of the transformation</param>
 /// <param name="targetPageUri">The target URI of the transformed page</param>
 public PagePostTransformationContext(PageTransformationTask task, ISourceItem sourceItem, Uri targetPageUri) : base(task, sourceItem, targetPageUri)
 {
 }
 /// <summary>
 /// Creates an instance of PageTransformationContext
 /// </summary>
 /// <param name="task">The page transformation task</param>
 /// <param name="sourceItem">The source item of the transformation</param>
 /// <param name="targetPageUri">The target URI of the transformed page</param>
 public PageTransformationContext(PageTransformationTask task, ISourceItem sourceItem, Uri targetPageUri)
 {
     Task          = task ?? throw new ArgumentNullException(nameof(task));
     SourceItem    = sourceItem ?? throw new ArgumentNullException(nameof(sourceItem));
     TargetPageUri = targetPageUri ?? throw new ArgumentNullException(nameof(targetPageUri));
 }