Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[backend] Avoid upserting a filled attribute with something null (#4804) #5042

Merged
merged 1 commit into from
Dec 1, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 13 additions & 6 deletions opencti-platform/opencti-graphql/src/database/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -2622,7 +2622,7 @@ const upsertElement = async (context, user, element, type, updatePatch, opts = {
}
// If confidence is passed at creation, just compare confidence
const isPatchUpdateOption = updatePatch.update === true;
const applyUpdate = isNotEmptyField(updatePatch.confidence) ? (updatePatch.confidence >= element.confidence) : isPatchUpdateOption;
const isConfidenceMatch = isNotEmptyField(updatePatch.confidence) ? (updatePatch.confidence >= element.confidence) : isPatchUpdateOption;
// -- Upsert attributes
const attributes = Array.from(schemaAttributesDefinition.getAttributes(type).values());
for (let attrIndex = 0; attrIndex < attributes.length; attrIndex += 1) {
Expand All @@ -2632,11 +2632,15 @@ const upsertElement = async (context, user, element, type, updatePatch, opts = {
if (isInputAvailable) { // The attribute is explicitly available in the patch
const inputData = updatePatch[attributeKey];
const isFullSync = context.synchronizedUpsert; // In case of full synchronization, just update the data
const canBeUpsert = applyUpdate && attribute.upsert; // If field can be upsert
const isInputWithData = isNotEmptyField(inputData);
const isCurrentlyEmpty = isEmptyField(element[attributeKey]) && isInputWithData; // If the element current data is empty, we always expect to put the value
const isWorkflowReset = attribute.name === X_WORKFLOW_ID && !isInputWithData; // Reset of workflow is not allowed
if ((isFullSync || canBeUpsert || isCurrentlyEmpty) && !isWorkflowReset) {
// Field can be upsert if:
// 1. Confidence is correct
// 2. Attribute is declared upsert=true in the schema
// 3. Data from the inputs is not empty to prevent any data cleaning
const canBeUpsert = isConfidenceMatch && attribute.upsert && isInputWithData;
// Upsert will be done if upsert is well-defined but also in full synchro mode or if the current value is empty
if (canBeUpsert || isFullSync || isCurrentlyEmpty) {
inputs.push(...buildAttributeUpdate(isFullSync, attribute, element[attributeKey], inputData));
}
}
Expand All @@ -2649,22 +2653,25 @@ const upsertElement = async (context, user, element, type, updatePatch, opts = {
const isInputAvailable = inputField in updatePatch;
if (isInputAvailable) {
const patchInputData = updatePatch[inputField];
const isInputWithData = isNotEmptyField(patchInputData);
const isUpsertSynchro = (context.synchronizedUpsert || inputField === INPUT_GRANTED_REFS); // Granted Refs are always fully sync
if (relDef.multiple) {
const currentData = element[relDef.databaseName] ?? [];
const targetData = (patchInputData ?? []).map((n) => n.internal_id);
// If expected data is different from current data
if (R.symmetricDifference(currentData, targetData).length > 0) {
const diffTargets = (patchInputData ?? []).filter((target) => !currentData.includes(target.internal_id));
// In full synchro, just replace everything
if (isUpsertSynchro) {
inputs.push({ key: inputField, value: patchInputData ?? [], operation: UPDATE_OPERATION_REPLACE });
} else if (diffTargets.length > 0) {
} else if (isInputWithData && diffTargets.length > 0) {
// If data is provided and different from existing data, apply an add operation
inputs.push({ key: inputField, value: diffTargets, operation: UPDATE_OPERATION_ADD });
}
}
} else {
const currentData = element[relDef.databaseName];
const updatable = isUpsertSynchro || isEmptyField(currentData);
const updatable = isUpsertSynchro || (isInputWithData && isEmptyField(currentData));
// If expected data is different from current data
// And data can be updated (complete a null value or forced through synchro upsert option
if (!R.equals(currentData, patchInputData) && updatable) {
Expand Down