//todo: if issue with data, don't just hang visualiser private async Task <IEnumerable <IQuery <object?> > > BuildVisualisationCommands( string contentItemId, IContentItemVersion contentItemVersion) { ContentItem?contentItem = await contentItemVersion.GetContentItem(_contentManager, contentItemId); if (contentItem == null) { return(Enumerable.Empty <IQuery <INodeAndOutRelationshipsAndTheirInRelationships> >()); } //todo: best to not use dynamic dynamic?graphSyncPartContent = contentItem.Content[nameof(GraphSyncPart)]; _syncNameProvider.ContentType = contentItem.ContentType; string?sourceNodeId = _syncNameProvider.GetNodeIdPropertyValue(graphSyncPartContent, contentItemVersion); IEnumerable <string> sourceNodeLabels = await _syncNameProvider.NodeLabels(); string sourceNodeIdPropertyName = _syncNameProvider.IdPropertyName(); var rootContext = await _describeContentItemHelper.BuildRelationships( contentItem, sourceNodeIdPropertyName, sourceNodeId, sourceNodeLabels, _syncNameProvider, _contentManager, contentItemVersion, null, _serviceProvider); //todo: return relationships - can we do it without creating cypher outside of a query? if (rootContext == null) { return(Enumerable.Empty <IQuery <object?> >()); } //todo: should create relationships in here return(await _describeContentItemHelper.GetRelationshipCommands(rootContext)); }
public async Task <IAllowSync> DeleteAllowed(ContentItem contentItem, IContentItemVersion contentItemVersion, SyncOperation syncOperation, IEnumerable <KeyValuePair <string, object> >?deleteIncomingRelationshipsProperties = null, IGraphDeleteContext?parentContext = null) { _syncNameProvider.ContentType = contentItem.ContentType; if (contentItem.Content.GraphSyncPart == null || _syncNameProvider.GraphSyncPartSettings.PreexistingNode) { return(AllowSync.NotRequired); } //todo: helper for this var allDeleteIncomingRelationshipsProperties = new HashSet <KeyValuePair <string, object> >(); if (deleteIncomingRelationshipsProperties != null) { allDeleteIncomingRelationshipsProperties.UnionWith(deleteIncomingRelationshipsProperties); } //todo: unions unnecessarily when called embeddedly : new deleteembeddedallowed method? if (syncOperation == SyncOperation.Unpublish) { allDeleteIncomingRelationshipsProperties.UnionWith( ContentPickerFieldGraphSyncer.ContentPickerRelationshipProperties); } _graphDeleteItemSyncContext = new GraphDeleteContext( contentItem, _deleteNodeCommand, this, syncOperation, _syncNameProvider, _contentManager, contentItemVersion, allDeleteIncomingRelationshipsProperties, parentContext, _serviceProvider); return(await DeleteAllowed()); }
private async Task AttemptDeleteRepair( ContentTypeDefinition contentTypeDefinition, IContentItemVersion contentItemVersion, ValidateAndRepairResult result, ValidationFailure failure) { var deleteGraphSyncer = _serviceProvider.GetRequiredService <IDeleteGraphSyncer>(); try { await deleteGraphSyncer.DeleteIfAllowed(failure.ContentItem, contentItemVersion, SyncOperation.Delete); } catch (Exception ex) { _logger.LogWarning(ex, "Repair of deleted {ContentItem} failed.", failure.ContentItem); } (bool validated, string?validationFailureReason) = await ValidateDeletedContentItem(failure.ContentItem, contentTypeDefinition, contentItemVersion); if (validated) { _logger.LogInformation("Repair was successful on deleted {ContentType} {ContentItemId} in {CurrentGraph}.", failure.ContentItem.ContentType, failure.ContentItem.ContentItemId, GraphDescription(_currentGraph !)); result.Repaired.Add(new ValidatedContentItem(failure.ContentItem, ValidateType.Delete)); } else { string message = $"Repair was unsuccessful.{Environment.NewLine}{{ValidationFailureReason}}."; _logger.LogWarning(message, validationFailureReason); result.RepairFailures.Add(new RepairFailure(failure.ContentItem, validationFailureReason !, ValidateType.Delete)); } }
#pragma warning restore S4144 private async Task PublishContentEvent( IOrchestrationContext context, ContentEventType eventType) { if (!_eventGridConfiguration.CurrentValue.PublishEvents) { _logger.LogInformation("Event grid publishing is disabled. No events will be published."); return; } try { IContentItemVersion contentItemVersion = eventType switch { ContentEventType.Published => _publishedContentItemVersion, ContentEventType.Draft => _previewContentItemVersion, _ => _neutralEventContentItemVersion }; string userId = _syncNameProvider.GetEventIdPropertyValue( context.ContentItem.Content.GraphSyncPart, contentItemVersion); ContentEvent contentEvent = new ContentEvent(context.ContentItem, userId, eventType); await _eventGridContentClient.Publish(contentEvent); } catch (Exception publishException) { _logger.LogError(publishException, "The event grid event could not be published."); await context.Notifier.Add("Warning: the event grid event could not be published. Composite apps might not show your changes.", "Exception", publishException, type : NotifyType.Warning); } }
public DescribeRelationshipsContext(string sourceNodeIdPropertyName, string sourceNodeId, IEnumerable <string> sourceNodeLabels, ContentItem contentItem, int maxDepthFromHere, ISyncNameProvider graphSyncHelper, IContentManager contentManager, IContentItemVersion contentItemVersion, IDescribeRelationshipsContext?parentContext, IServiceProvider serviceProvider) : base( contentItem, graphSyncHelper, contentManager, contentItemVersion, parentContext, serviceProvider.GetRequiredService <ILogger <GraphDeleteContext> >()) { AvailableRelationships = new List <ContentItemRelationship>(); ServiceProvider = serviceProvider; SourceNodeId = sourceNodeId; SourceNodeLabels = sourceNodeLabels; MaxDepthFromHere = maxDepthFromHere; SourceNodeIdPropertyName = sourceNodeIdPropertyName; CurrentDepth = (parentContext?.CurrentDepth + 1) ?? 0; }
private async Task <(bool validated, string failureReason)> ValidateDeletedContentItem( ContentItem contentItem, ContentTypeDefinition contentTypeDefinition, IContentItemVersion contentItemVersion) { _logger.LogDebug("Validating deleted {ContentType} {ContentItemId} '{ContentDisplayText}'.", contentItem.ContentType, contentItem.ContentItemId, contentItem.DisplayText); _syncNameProvider.ContentType = contentItem.ContentType; object nodeId = _syncNameProvider.GetNodeIdPropertyValue(contentItem.Content.GraphSyncPart, contentItemVersion); // this is basically querying to see if the node's there or not - a simpler query might be better ISubgraph?nodeWithRelationships = (await _currentGraph !.Run( new SubgraphQuery( await _syncNameProvider.NodeLabels(), _syncNameProvider.IdPropertyName(), nodeId, SubgraphQuery.RelationshipFilterNone, 0))) .FirstOrDefault(); return(nodeWithRelationships?.SourceNode != null ? (false, $"{contentTypeDefinition.DisplayName} {contentItem.ContentItemId} is still present in the graph.") : (true, "")); }
protected async Task <(IAllowSync, IDeleteGraphSyncer?)> GetDeleteGraphSyncerIfDeleteAllowed( ContentItem contentItem, IContentItemVersion contentItemVersion, SyncOperation syncOperation) { try { IDeleteGraphSyncer deleteGraphSyncer = _serviceProvider.GetRequiredService <IDeleteGraphSyncer>(); IAllowSync allowSync = await deleteGraphSyncer.DeleteAllowed( contentItem, contentItemVersion, syncOperation); return(allowSync, deleteGraphSyncer); } catch (Exception exception) { string contentType = GetContentTypeDisplayName(contentItem); //todo: will get logged twice, but want to keep the param version _logger.LogError(exception, "Unable to check if the '{ContentItem}' {ContentType} can be {DeleteOperation} from the {GraphReplicaSetName} graph.", contentItem.DisplayText, contentType, syncOperation.ToString("PrP", null).ToLower(), contentItemVersion.GraphReplicaSetName); await _notifier.Add(GetSyncOperationCancelledUserMessage(syncOperation, contentItem.DisplayText, contentType), $"Unable to check if the '{contentItem.DisplayText}' {contentType} can be {syncOperation.ToString("PrP", null).ToLower()} from the {contentItemVersion.GraphReplicaSetName} graph.", exception : exception); throw; } }
private object GetNodeId( ContentItem pickedContentItem, ISyncNameProvider syncNameProvider, IContentItemVersion contentItemVersion) { //todo: add GetNodeId support to TaxonomyFieldGraphSyncer return(syncNameProvider.GetNodeIdPropertyValue( pickedContentItem.Content[nameof(GraphSyncPart)], contentItemVersion)); }
private object?GetNodeId( string termContentItemId, IDictionary <string, ContentItem> taxonomyTerms, ISyncNameProvider termSyncNameProvider, IContentItemVersion contentItemVersion) { ContentItem termContentItem = taxonomyTerms[termContentItemId]; return(termSyncNameProvider.GetNodeIdPropertyValue( (JObject)termContentItem.Content[nameof(GraphSyncPart)] !, contentItemVersion)); }
//todo: contentmanager //todo: taxonomies use reltype*maxdepth ? //todo: for child contexts, do we need anything more than parentcontext, contentitem & relationships? public async Task <IDescribeRelationshipsContext?> BuildRelationships( ContentItem contentItem, string sourceNodeIdPropertyName, string sourceNodeId, IEnumerable <string> sourceNodeLabels, ISyncNameProvider syncNameProvider, IContentManager contentManager, IContentItemVersion contentItemVersion, IDescribeRelationshipsContext?parentContext, IServiceProvider serviceProvider) { var graphSyncPartSettings = syncNameProvider.GetGraphSyncPartSettings(contentItem.ContentType); int maxDepthFromHere; if (parentContext == null) { maxDepthFromHere = Math.Min(graphSyncPartSettings.VisualiserNodeDepth ?? int.MaxValue, //todo: store in root in case changes mid flow? _graphSyncSettings.CurrentValue.MaxVisualiserNodeDepth); } else { if (_encounteredContentTypes.Any(x => x == contentItem.ContentType)) { return(null); } maxDepthFromHere = Math.Min(parentContext.MaxDepthFromHere - 1, graphSyncPartSettings.VisualiserNodeDepth ?? int.MaxValue); } if (maxDepthFromHere <= 0) { return(null); } var context = new DescribeRelationshipsContext( sourceNodeIdPropertyName, sourceNodeId, sourceNodeLabels, contentItem, maxDepthFromHere, syncNameProvider, contentManager, contentItemVersion, parentContext, serviceProvider); foreach (IContentItemGraphSyncer itemSyncer in _contentItemGraphSyncers) { //todo: allow syncers to chain or not? probably not if (itemSyncer.CanSync(context.ContentItem)) { await itemSyncer.AddRelationship(context); } } _encounteredContentTypes.Add(contentItem.ContentType); return(context); }
private async Task <ContentItem?> GetTaxonomyContentItem( JObject contentItemField, IContentItemVersion contentItemVersion, IContentManager contentManager) { string taxonomyContentItemId = contentItemField[TaxonomyContentItemId]?.ToObject <string>() !; //todo: null? //todo: need to really think this through/test it return(await contentItemVersion.GetContentItem(contentManager, taxonomyContentItemId)); }
protected GraphSyncContext( ContentItem contentItem, ISyncNameProvider syncNameProvider, IContentManager contentManager, IContentItemVersion contentItemVersion, IGraphSyncContext?parentContext, ILogger logger) : base(contentItem, syncNameProvider, contentManager, contentItemVersion, logger) { ParentContext = parentContext; parentContext?.AddChildContext(this); _childContexts = new List <IGraphSyncContext>(); }
protected GraphOperationContext( ContentItem contentItem, ISyncNameProvider syncNameProvider, IContentManager contentManager, IContentItemVersion contentItemVersion, ILogger logger) { _logger = logger; ContentItem = contentItem; SyncNameProvider = syncNameProvider; ContentManager = contentManager; ContentItemVersion = contentItemVersion; // will be set before any syncers receive a context ContentTypePartDefinition = default !;
public CloneContext( ContentItem contentItem, ICloneGraphSync cloneGraphSync, ISyncNameProvider syncNameProvider, IContentManager contentManager, IContentItemVersion contentItemVersion, IServiceProvider serviceProvider, ICloneContext?parentContext = null) : base( contentItem, syncNameProvider, contentManager, contentItemVersion, parentContext, serviceProvider.GetRequiredService <ILogger <CloneContext> >()) { CloneGraphSync = cloneGraphSync; }
private async Task <bool> DeleteFromGraphReplicaSetIfAllowed( ContentItem contentItem, IContentItemVersion contentItemVersion, SyncOperation syncOperation) { (IAllowSync allowSync, IDeleteGraphSyncer? publishedDeleteGraphSyncer) = await GetDeleteGraphSyncerIfDeleteAllowed( contentItem, contentItemVersion, syncOperation); switch (allowSync.Result) { case AllowSyncResult.Blocked: await _notifier.AddBlocked( syncOperation, contentItem, new[] { (contentItemVersion.GraphReplicaSetName, allowSync) });
public ValidateAndRepairContext( ContentItem contentItem, IContentManager contentManager, IContentItemVersion contentItemVersion, ISubgraph nodeWithRelationships, ISyncNameProvider syncNameProvider, IGraphValidationHelper graphValidationHelper, IValidateAndRepairGraph validateAndRepairGraph, ILogger logger) : base(contentItem, syncNameProvider, contentManager, contentItemVersion, logger) { ContentItemVersion = contentItemVersion; NodeWithRelationships = nodeWithRelationships; GraphValidationHelper = graphValidationHelper; ValidateAndRepairGraph = validateAndRepairGraph; ExpectedRelationshipCounts = new Dictionary <string, int>(); }
public async Task <IAllowSync> DeleteIfAllowed( ContentItem contentItem, IContentItemVersion contentItemVersion, SyncOperation syncOperation, IEnumerable <KeyValuePair <string, object> >?deleteIncomingRelationshipsProperties = null) { IAllowSync allowSync = await DeleteAllowed( contentItem, contentItemVersion, syncOperation, deleteIncomingRelationshipsProperties); if (allowSync.Result == AllowSyncResult.Allowed) { await Delete(); } return(allowSync); }
public ValidateAndRepairItemSyncContext( ContentItem contentItem, IContentManager contentManager, IContentItemVersion contentItemVersion, ISubgraph nodeWithRelationships, ISyncNameProvider syncNameProvider, IGraphValidationHelper graphValidationHelper, IValidateAndRepairGraph validateAndRepairGraph, ContentTypeDefinition contentTypeDefinition, object nodeId, IServiceProvider serviceProvider) : base(contentItem, contentManager, contentItemVersion, nodeWithRelationships, syncNameProvider, graphValidationHelper, validateAndRepairGraph, serviceProvider.GetRequiredService <ILogger <ValidateAndRepairItemSyncContext> >()) { ContentTypeDefinition = contentTypeDefinition; NodeId = nodeId; }
public async Task <Subgraph> GetVisualisationSubgraph( string contentItemId, string graphName, IContentItemVersion contentItemVersion) { var relationshipCommands = await BuildVisualisationCommands(contentItemId, contentItemVersion !); // get all results atomically var result = await _neoGraphCluster.Run(graphName, relationshipCommands.ToArray()); var inAndOutResults = result.OfType <INodeAndOutRelationshipsAndTheirInRelationships?>(); //todo: should really always return the source node (until then, the subgraph will pull it if the main results don't) Subgraph subgraph; if (inAndOutResults.Any()) { // get all outgoing relationships from the query and add in any source nodes subgraph = new Subgraph( inAndOutResults .SelectMany(x => x !.OutgoingRelationships.Select(x => x.outgoingRelationship.DestinationNode)) .Union(inAndOutResults.GroupBy(x => x !.SourceNode).Select(z => z.FirstOrDefault() !.SourceNode)), inAndOutResults ! .SelectMany(y => y !.OutgoingRelationships.Select(z => z.outgoingRelationship.Relationship)) .ToHashSet(), inAndOutResults.FirstOrDefault()?.SourceNode); } else { subgraph = new Subgraph(); } ISubgraph?inResults = result.OfType <ISubgraph>().FirstOrDefault(); if (inResults != null) { subgraph.Add(inResults); } return(subgraph); }
//todo: nodeId should be object public async Task <string?> GetContentItemId(string nodeId, string graphReplicaSetName) { IContentItemVersion contentItemVersion = _contentItemVersionFactory.Get(graphReplicaSetName); // what we should be doing, but we'd have to pass along the node labels to get the content type //ISyncNameProvider syncNameProvider = _serviceProvider.GetSyncNameProvider(pickedContentType); //string graphSyncNodeId = syncNameProvider.IdPropertyValueFromNodeValue(nodeId, contentItemVersion); string graphSyncNodeId = _syncNameProvider.ConvertIdPropertyValue(nodeId, _superpositionContentItemVersion, contentItemVersion, _escoContentItemVersion); var contentItems = await _session .Query <ContentItem, GraphSyncPartIndex>(x => x.NodeId == graphSyncNodeId) .ListAsync(); return(contentItems?.FirstOrDefault()?.ContentItemId); }
private async Task <ContentItem[]> GetContentItemsFromIds( JArray contentItemIds, IContentManager contentManager, IContentItemVersion contentItemVersion) { // GetAsync should be returning ContentItem? as it can be null ContentItem?[] contentItems = await Task.WhenAll(contentItemIds .Select(idJToken => idJToken.ToObject <string?>()) .Select(async id => await contentItemVersion.GetContentItem(contentManager, id !))); #pragma warning disable S1905 return(contentItems .Where(ci => ci != null) .Cast <ContentItem>() .ToArray()); #pragma warning restore S1905 }
public GraphDeleteContext(ContentItem contentItem, IDeleteNodeCommand deleteNodeCommand, IDeleteGraphSyncer deleteGraphSyncer, SyncOperation syncOperation, ISyncNameProvider syncNameProvider, IContentManager contentManager, IContentItemVersion contentItemVersion, IEnumerable <KeyValuePair <string, object> >?deleteIncomingRelationshipsProperties, IGraphDeleteContext?parentGraphDeleteContext, IServiceProvider serviceProvider) : base( contentItem, syncNameProvider, contentManager, contentItemVersion, parentGraphDeleteContext, serviceProvider.GetRequiredService <ILogger <GraphDeleteContext> >()) { DeleteGraphSyncer = deleteGraphSyncer; DeleteNodeCommand = deleteNodeCommand; SyncOperation = syncOperation; DeleteIncomingRelationshipsProperties = deleteIncomingRelationshipsProperties; }
private async Task AttemptRepair( IEnumerable <ValidationFailure> syncValidationFailures, ContentTypeDefinition contentTypeDefinition, IContentItemVersion contentItemVersion, ValidateAndRepairResult result) { _logger.LogWarning( "Content items of type {ContentTypeDefinitionName} failed validation ({ValidationFailures}). Attempting to repair them.", contentTypeDefinition.Name, string.Join(", ", syncValidationFailures.Select(f => f.ContentItem.ToString()))); foreach (var failure in syncValidationFailures) { if (failure.Type == ValidateType.Merge) { await AttemptMergeRepair(contentTypeDefinition, contentItemVersion, result, failure); } else { await AttemptDeleteRepair(contentTypeDefinition, contentItemVersion, result, failure); } } }
private async Task <(bool validated, string failureReason)> ValidateDeletedContentItem( ContentItem contentItem, ContentTypeDefinition contentTypeDefinition, IContentItemVersion contentItemVersion) { _logger.LogDebug("Validating deleted {ContentType} {ContentItemId} '{ContentDisplayText}'.", contentItem.ContentType, contentItem.ContentItemId, contentItem.DisplayText); _syncNameProvider.ContentType = contentItem.ContentType; object nodeId = _syncNameProvider.GetNodeIdPropertyValue(contentItem.Content.GraphSyncPart, contentItemVersion); //todo: one query to fetch outgoing and incoming List <INodeWithOutgoingRelationships?> results = await _currentGraph !.Run( new NodeWithOutgoingRelationshipsQuery( await _syncNameProvider.NodeLabels(), _syncNameProvider.IdPropertyName(), nodeId)); return(results.Any() ? (false, $"{contentTypeDefinition.DisplayName} {contentItem.ContentItemId} is still present in the graph.") : (true, "")); }
//todo: if issue with data, don't just hang visualiser private async Task <IEnumerable <IQuery <object?> > > BuildVisualisationCommands( string contentItemId, IContentItemVersion contentItemVersion) { ContentItem?contentItem = await contentItemVersion.GetContentItem(_contentManager, contentItemId); if (contentItem == null) { return(Enumerable.Empty <IQuery <INodeAndOutRelationshipsAndTheirInRelationships> >()); } //todo: best to not use dynamic dynamic?graphSyncPartContent = contentItem.Content[nameof(GraphSyncPart)]; _syncNameProvider.ContentType = contentItem.ContentType; string?sourceNodeId = _syncNameProvider.GetNodeIdPropertyValue(graphSyncPartContent, contentItemVersion); IEnumerable <string> sourceNodeLabels = await _syncNameProvider.NodeLabels(); string sourceNodeIdPropertyName = _syncNameProvider.IdPropertyName(); var rootContext = await _describeContentItemHelper.BuildRelationships( contentItem, sourceNodeIdPropertyName, sourceNodeId, sourceNodeLabels, _syncNameProvider, _contentManager, contentItemVersion, null, _serviceProvider); //todo: return relationships - can we do it without creating cypher outside of a query? //todo: current depth is always 0, so deep nodes like PersonalityQuestionSet returns masses of data //todo: depth cut-off is done after build relationships, so does more work than is necessary //var relationships = new List<ContentItemRelationship>(); if (rootContext == null) { return(Enumerable.Empty <IQuery <object?> >()); } //todo: should create relationships in here return(await _describeContentItemHelper.GetRelationshipCommands(rootContext)); }
private async Task AttemptMergeRepair( ContentTypeDefinition contentTypeDefinition, IContentItemVersion contentItemVersion, ValidateAndRepairResult result, ValidationFailure failure) { var mergeGraphSyncer = _serviceProvider.GetRequiredService <IMergeGraphSyncer>(); IGraphReplicaSet graphReplicaSet = _currentGraph !.GetReplicaSetLimitedToThisGraph(); try { await mergeGraphSyncer.SyncToGraphReplicaSetIfAllowed(graphReplicaSet, failure.ContentItem, _contentManager); } catch (Exception ex) { _logger.LogWarning(ex, "Repair of {ContentItem} in {GraphReplicaSet} failed.", failure.ContentItem, graphReplicaSet); } (bool validated, string?validationFailureReason) = await ValidateContentItem(failure.ContentItem, contentTypeDefinition, contentItemVersion); if (validated) { _logger.LogInformation("Repair was successful on {ContentType} {ContentItemId} in {CurrentGraph}.", failure.ContentItem.ContentType, failure.ContentItem.ContentItemId, GraphDescription(_currentGraph !)); result.Repaired.Add(new ValidatedContentItem(failure.ContentItem)); } else { string message = $"Repair was unsuccessful.{Environment.NewLine}{{ValidationFailureReason}}."; _logger.LogWarning(message, validationFailureReason); result.RepairFailures.Add(new RepairFailure(failure.ContentItem, validationFailureReason !)); } }
private async Task <List <ValidationFailure> > ValidateContentItemsOfContentType( IContentItemVersion contentItemVersion, ContentTypeDefinition contentTypeDefinition, DateTime lastSynced, ValidateAndRepairResult result, ValidationScope scope) { List <ValidationFailure> syncFailures = new List <ValidationFailure>(); (bool?latest, bool?published) = contentItemVersion.ContentItemIndexFilterTerms; //todo: do we want to batch up content items of type? IEnumerable <ContentItem> contentTypeContentItems = await _contentItemsService .Get(contentTypeDefinition.Name, lastSynced, latest : latest, published : published); IEnumerable <ContentItem> deletedContentTypeContentItems = await _contentItemsService .GetDeleted(contentTypeDefinition.Name, lastSynced); if (!contentTypeContentItems.Any() && !deletedContentTypeContentItems.Any()) { _logger.LogDebug("No {ContentType} content items found that require validation.", contentTypeDefinition.Name); return(syncFailures); } foreach (ContentItem contentItem in contentTypeContentItems) { (bool validated, string?validationFailureReason) = await ValidateContentItem(contentItem, contentTypeDefinition, contentItemVersion); if (validated) { _logger.LogInformation("Sync validation passed for {ContentType} {ContentItemId} in {CurrentGraph}.", contentItem.ContentType, contentItem.ContentItemId, GraphDescription(_currentGraph !)); result.Validated.Add(new ValidatedContentItem(contentItem)); } else { string message = $"Sync validation failed in {{CurrentGraph}}.{Environment.NewLine}{{ValidationFailureReason}}."; _logger.LogWarning(message, GraphDescription(_currentGraph !), validationFailureReason); ValidationFailure validationFailure = new ValidationFailure(contentItem, validationFailureReason !); syncFailures.Add(validationFailure); result.ValidationFailures.Add(validationFailure); } } if (scope == ValidationScope.ModifiedSinceLastValidation) { foreach (ContentItem contentItem in deletedContentTypeContentItems) { (bool validated, string?validationFailureReason) = await ValidateDeletedContentItem(contentItem, contentTypeDefinition, contentItemVersion); if (validated) { _logger.LogInformation( "Sync validation passed for deleted {ContentType} {ContentItemId} in {CurrentGraph}.", contentItem.ContentType, contentItem.ContentItemId, GraphDescription(_currentGraph !)); result.Validated.Add(new ValidatedContentItem(contentItem, ValidateType.Delete)); } else { string message = $"Sync validation failed in {{CurrentGraph}}.{Environment.NewLine}{{validationFailureReason}}."; _logger.LogWarning(message, GraphDescription(_currentGraph !), validationFailureReason); ValidationFailure validationFailure = new ValidationFailure(contentItem, validationFailureReason !, ValidateType.Delete); syncFailures.Add(validationFailure); result.ValidationFailures.Add(validationFailure); } } } return(syncFailures); }
private async Task <IValidateAndRepairResults> ValidateGraphImpl( ValidationScope validationScope, params string[] graphReplicaSetNames) { IEnumerable <ContentTypeDefinition> syncableContentTypeDefinitions = _contentDefinitionManager .ListTypeDefinitions() .Where(x => x.Parts.Any(p => p.Name == nameof(GraphSyncPart))); DateTime timestamp = DateTime.UtcNow; IEnumerable <string> graphReplicaSetNamesToValidate = graphReplicaSetNames.Any() ? graphReplicaSetNames : _graphClusterLowLevel.GraphReplicaSetNames; DateTime validateFrom = await GetValidateFromTime(validationScope); var results = new ValidateAndRepairResults(validateFrom); //todo: we could optimise to only get content items from the oc database once for each replica set, // rather than for each instance foreach (string graphReplicaSetName in graphReplicaSetNamesToValidate) { IGraphReplicaSetLowLevel graphReplicaSetLowLevel = _graphClusterLowLevel.GetGraphReplicaSetLowLevel(graphReplicaSetName); IContentItemVersion contentItemVersion = _contentItemVersionFactory.Get(graphReplicaSetName); foreach (IGraph graph in graphReplicaSetLowLevel.GraphInstances) { ValidateAndRepairResult result = results.AddNewValidateAndRepairResult( graphReplicaSetName, graph.Instance, graph.Endpoint.Name, graph.GraphName, graph.DefaultGraph); // make current graph available for when parts/fields call back into ValidateContentItem // seems a little messy, and will make concurrent validation a pita, // but stops part/field syncers needing low level graph access _currentGraph = graph; foreach (ContentTypeDefinition contentTypeDefinition in syncableContentTypeDefinitions) { List <ValidationFailure> syncFailures = await ValidateContentItemsOfContentType( contentItemVersion, contentTypeDefinition, validateFrom, result, validationScope); if (syncFailures.Any()) { await AttemptRepair(syncFailures, contentTypeDefinition, contentItemVersion, result); } } } } if (results.AnyRepairFailures) { return(results); } _logger.LogInformation("Woohoo: graph passed validation or was successfully repaired at {Time}.", timestamp.ToString("O")); _session.Save(new AuditSyncLog(timestamp)); await _session.CommitAsync(); return(results); }
public async Task <(bool validated, string failureReason)> ValidateContentItem( ContentItem contentItem, ContentTypeDefinition contentTypeDefinition, IContentItemVersion contentItemVersion) { _logger.LogDebug("Validating {ContentType} {ContentItemId} '{ContentDisplayText}'.", contentItem.ContentType, contentItem.ContentItemId, contentItem.DisplayText); _syncNameProvider.ContentType = contentItem.ContentType; object nodeId = _syncNameProvider.GetNodeIdPropertyValue(contentItem.Content.GraphSyncPart, contentItemVersion); //todo: one query to fetch outgoing and incoming List <INodeWithOutgoingRelationships?> results = await _currentGraph !.Run( new NodeWithOutgoingRelationshipsQuery( await _syncNameProvider.NodeLabels(), _syncNameProvider.IdPropertyName(), nodeId)); //todo: does this belong in the query? INodeWithOutgoingRelationships?nodeWithOutgoingRelationships = results.FirstOrDefault(); if (nodeWithOutgoingRelationships == null) { return(false, FailureContext("Node not found querying outgoing relationships.", contentItem)); } List <INodeWithIncomingRelationships?> incomingResults = await _currentGraph !.Run( new NodeWithIncomingRelationshipsQuery( await _syncNameProvider.NodeLabels(), _syncNameProvider.IdPropertyName(), nodeId)); INodeWithIncomingRelationships?nodeWithIncomingRelationships = incomingResults.FirstOrDefault(); if (nodeWithIncomingRelationships == null) { return(false, FailureContext("Node not found querying incoming relationships.", contentItem)); } ValidateAndRepairItemSyncContext context = new ValidateAndRepairItemSyncContext( contentItem, _contentManager, contentItemVersion, nodeWithOutgoingRelationships, nodeWithIncomingRelationships, _syncNameProvider, _graphValidationHelper, this, contentTypeDefinition, nodeId, _serviceProvider); foreach (IContentItemGraphSyncer itemSyncer in _itemSyncers) { //todo: allow syncers to chain or not? if (itemSyncer.CanSync(contentItem)) { (bool validated, string failureReason) = await itemSyncer.ValidateSyncComponent(context); if (!validated) { return(validated, failureReason); } } } // check there aren't any more relationships of each type than there should be // we need to do this after all parts have added their own expected relationship counts // to handle different parts or multiple named instances of a part creating relationships of the same type foreach ((string relationshipType, int relationshipsInDbCount) in context.ExpectedRelationshipCounts) { int relationshipsInGraphCount = nodeWithOutgoingRelationships.OutgoingRelationships.Count(r => r.Relationship.Type == relationshipType); if (relationshipsInDbCount != relationshipsInGraphCount) { return(false, FailureContext( $"Expecting {relationshipsInDbCount} relationships of type {relationshipType} in graph, but found {relationshipsInGraphCount}.", contentItem)); } } return(true, ""); }