Skip to content

fix: refactor proposal cloning logic to ensure correct user permissio… - #1287

Merged
jekabs-karklins merged 13 commits into
developfrom
SWAP-5131-uo-could-not-clone-the-proposal-error
Jan 13, 2026
Merged

fix: refactor proposal cloning logic to ensure correct user permissio…#1287
jekabs-karklins merged 13 commits into
developfrom
SWAP-5131-uo-could-not-clone-the-proposal-error

Conversation

@jekabs-karklins

@jekabs-karklins jekabs-karklins commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

Description

This PR addresses the bug where it was not possible to clone proposal if user was already on the proposal. The issue was that the check if user was write permissions on the clonned proposal was done before users were added. This resulted in duplicate key value violates unique constraint exception.

After discussing the issue with UO, i have adjusted the logic to add user to be a co-proposer on the cloned proposal only if he has a user role and is not already part of it. This keeps it more straight forward.

@mutambaraf let me know if this logic is also ok with you.

Context: {"error":"{\"userMessage\":\"Could not clone the proposal error: insert into \\\"proposal_user\\\" (\\\"proposal_pk\\\", \\\"user_id\\\") values ($1, $2) - duplicate key value violates unique constraint \\\"proposal_user_pkey\\\"\",\"error\":{\"response\":{\"errors\":[{\"message\":\"Could not clone the proposal error: insert into \\\"proposal_user\\\" (\\\"proposal_pk\\\", \\\"user_id\\\") values ($1, $2) - duplicate key value violates unique constraint \\\"proposal_user_pkey\\\"\",\"locations\":[{\"line\":2,\"column\":3}],\"path\":[\"cloneProposals\",0],\"extensions\":{\"code\":\"INTERNAL_SERVER_ERROR\",\"context\":{\"proposalToClonePk\":469},\"exception\":{\"length\":229,\"name\":\"error\",\"severity\":\"ERROR\",\"code\":\"23505\",\"detail\":\"Key (proposal_pk, user_id)=(761, 243) already exists.\",\"schema\":\"public\",\"table\":\"proposal_user\",\"constraint\":\"proposal_user_pkey\",\"file\":\"nbtinsert.c\",\"line\":\"666\",\"routine\":\"_bt_check_unique\"},\"stacktrace\":[\"GraphQLError: Could not clone the proposal error: insert into \\\"proposal_user\\\" (\\\"proposal_pk\\\", \\\"user_id\\\") values ($1, $2) - duplicate key value violates unique constraint \\\"proposal_user_pkey\\\"\",\"    at new Rejection (/home/node/app/build/src/models/Rejection.js:8:9)\",\"    at rejection (/home/node/app/build/src/models/Rejection.js:35:12)\",\"    at ProposalMutations.<anonymous> (/home/node/app/build/src/mutations/ProposalMutations.js:607:50)\",\"    at Generator.throw (<anonymous>)\",\"    at rejected (/home/node/app/build/src/mutations/ProposalMutations.js:18:65)\",\"    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)\"]}}],\"data\":null,\"status\":200,\"headers\":{\"map\":{\"access-control-allow-origin\":\"*\",\"cache-control\":\"no-store\",\"content-length\":\"1322\",\"content-security-policy\":\"frame-ancestors 'self' www.kommersannons.se\",\"content-type\":\"application/json; charset=utf-8\",\"date\":\"Fri, 21 Nov 2025 12:24:53 GMT\",\"etag\":\"W/\\\"52a-aFa8tkPdgWa7sVbtIBNx/4pPDCw\\\"\",\"strict-transport-security\":\"max-age=15724800; includeSubDomains\",\"x-content-type-options\":\"nosniff\",\"x-frame-options\":\"SAMEORIGIN\",\"x-powered-by\":\"Express\"}}},\"request\":{\"query\":\"mutation cloneProposals($proposalsToClonePk: [Int!]!, $callId: Int!) {\\n  cloneProposals(\\n    cloneProposalsInput: {proposalsToClonePk: $proposalsToClonePk, callId: $callId}\\n  ) {\\n    ...proposal\\n    proposer {\\n      ...basicUserDetails\\n    }\\n    users {\\n      ...basicUserDetails\\n    }\\n    questionary {\\n      ...questionary\\n      isCompleted\\n    }\\n    technicalReviews {\\n      ...coreTechnicalReview\\n    }\\n    reviews {\\n      id\\n      grade\\n      comment\\n      status\\n      userID\\n      fapID\\n      questionaryID\\n      reviewer {\\n        firstname\\n        lastname\\n        id\\n      }\\n    }\\n    instruments {\\n      id\\n      name\\n      shortCode\\n    }\\n    call {\\n      id\\n      shortCode\\n      isActive\\n      isActiveInternal\\n      referenceNumberFormat\\n      startCall\\n      endCall\\n      endCallInternal\\n    }\\n  }\\n}\\n\\nfragment proposal on Proposal {\\n  primaryKey\\n  title\\n  abstract\\n  statusId\\n  status {\\n    ...status\\n  }\\n  publicStatus\\n  proposalId\\n  finalStatus\\n  commentForUser\\n  commentForManagement\\n  created\\n  updated\\n  callId\\n  questionaryId\\n  notified\\n  submitted\\n  managementDecisionSubmitted\\n  fapMeetingDecisions {\\n    ...fapMeetingDecision\\n  }\\n  fileId\\n}\\n\\nfragment status on Status {\\n  id\\n  shortCode\\n  name\\n  description\\n  isDefault\\n  entityType\\n}\\n\\nfragment fapMeetingDecision on FapMeetingDecision {\\n  proposalPk\\n  recommendation\\n  commentForUser\\n  commentForManagement\\n  rankOrder\\n  submitted\\n  submittedBy\\n  instrumentId\\n  fapId\\n}\\n\\nfragment basicUserDetails on BasicUserDetails {\\n  id\\n  firstname\\n  lastname\\n  preferredname\\n  institution\\n  institutionId\\n  position\\n  created\\n  placeholder\\n  email\\n  country\\n  oidc_sub\\n}\\n\\nfragment questionary on Questionary {\\n  questionaryId\\n  templateId\\n  created\\n  steps {\\n    ...questionaryStep\\n  }\\n}\\n\\nfragment questionaryStep on QuestionaryStep {\\n  topic {\\n    ...topic\\n  }\\n  isCompleted\\n  fields {\\n    ...answer\\n  }\\n}\\n\\nfragment topic on Topic {\\n  title\\n  id\\n  templateId\\n  sortOrder\\n  isEnabled\\n}\\n\\nfragment answer on Answer {\\n  answerId\\n  question {\\n    ...question\\n  }\\n  sortOrder\\n  topicId\\n  config {\\n    ...fieldConfig\\n  }\\n  dependencies {\\n    questionId\\n    dependencyId\\n    dependencyNaturalKey\\n    condition {\\n      ...fieldCondition\\n    }\\n  }\\n  dependenciesOperator\\n  value\\n}\\n\\nfragment question on Question {\\n  id\\n  question\\n  naturalKey\\n  dataType\\n  categoryId\\n  config {\\n    ...fieldConfig\\n  }\\n}\\n\\nfragment fieldConfig on FieldConfig {\\n  ... on BooleanConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on DateConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n    minDate\\n    maxDate\\n    defaultDate\\n    includeTime\\n  }\\n  ... on EmbellishmentConfig {\\n    html\\n    plain\\n    omitFromPdf\\n    readPermissions\\n  }\\n  ... on FileUploadConfig {\\n    file_type\\n    max_files\\n    pdf_page_limit\\n    omitFromPdf\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on IntervalConfig {\\n    units {\\n      ...unit\\n    }\\n    numberValueConstraint\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on NumberInputConfig {\\n    units {\\n      ...unit\\n    }\\n    numberValueConstraint\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on ProposalBasisConfig {\\n    tooltip\\n    readPermissions\\n  }\\n  ... on ProposalEsiBasisConfig {\\n    tooltip\\n    readPermissions\\n  }\\n  ... on SampleEsiBasisConfig {\\n    tooltip\\n    readPermissions\\n  }\\n  ... on SampleBasisConfig {\\n    titlePlaceholder\\n    readPermissions\\n  }\\n  ... on SampleDeclarationConfig {\\n    addEntryButtonLabel\\n    minEntries\\n    maxEntries\\n    templateId\\n    esiTemplateId\\n    templateCategory\\n    required\\n    small_label\\n    readPermissions\\n  }\\n  ... on SubTemplateConfig {\\n    addEntryButtonLabel\\n    copyButtonLabel\\n    canCopy\\n    isMultipleCopySelect\\n    isCompleteOnCopy\\n    minEntries\\n    maxEntries\\n    templateId\\n    templateCategory\\n    required\\n    small_label\\n    readPermissions\\n  }\\n  ... on SelectionFromOptionsConfig {\\n    variant\\n    options\\n    isMultipleSelect\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on TextInputConfig {\\n    min\\n    max\\n    multiline\\n    placeholder\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n    htmlQuestion\\n    isHtmlQuestion\\n    isCounterHidden\\n  }\\n  ... on ShipmentBasisConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on RichTextInputConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n    max\\n    allowImages\\n  }\\n  ... on DynamicMultipleChoiceConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n    url\\n    jsonPath\\n    apiCallRequestHeaders {\\n      name\\n      value\\n    }\\n    isMultipleSelect\\n    variant\\n  }\\n  ... on VisitBasisConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on FapReviewBasisConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n    minGrade\\n    maxGrade\\n    decimalPoints\\n    nonNumericOptions\\n  }\\n  ... on TechnicalReviewBasisConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on GenericTemplateBasisConfig {\\n    titlePlaceholder\\n    questionLabel\\n    readPermissions\\n  }\\n  ... on FeedbackBasisConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on ExperimentSafetyReviewBasisConfig {\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n  }\\n  ... on InstrumentPickerConfig {\\n    variant\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n    isMultipleSelect\\n    requestTime\\n    instruments {\\n      id\\n      name\\n    }\\n  }\\n  ... on TechniquePickerConfig {\\n    variant\\n    small_label\\n    required\\n    tooltip\\n    readPermissions\\n    isMultipleSelect\\n    techniques {\\n      id\\n      name\\n    }\\n  }\\n}\\n\\nfragment unit on Unit {\\n  id\\n  unit\\n  quantity\\n  symbol\\n  siConversionFormula\\n}\\n\\nfragment fieldCondition on FieldCondition {\\n  condition\\n  params\\n}\\n\\nfragment coreTechnicalReview on TechnicalReview {\\n  id\\n  comment\\n  publicComment\\n  timeAllocation\\n  status\\n  proposalPk\\n  submitted\\n  files\\n  technicalReviewAssigneeId\\n  technicalReviewAssignee {\\n    id\\n    firstname\\n    lastname\\n  }\\n  instrumentId\\n  questionaryId\\n}\",\"variables\":{\"callId\":42,\"proposalsToClonePk\":[469]}}}}"}
Platform: duo-backend
Environment: staging
Title: Error received from client
Source: duo-backend-staging-84c7d8b4b9-glgx4

Timestamp: 2025-11-21T12:24:53.598Z 

Motivation and Context

Rework logic on when user is added a co-proposer for the cloned proposal.

Changes

  1. Removed the dependency on TemplateDataSource from ProposalMutations.ts, as it was not used.
  2. Add user as co-proposer if he is not on the cloned proposal already.

How Has This Been Tested?

Fixes Jira Issue

https://jira.esss.lu.se/browse/SWAP-5131

Depends On

Tests included/Docs Updated?

  • I have added tests to cover my changes.
  • All relevant doc has been updated

@jekabs-karklins
jekabs-karklins requested a review from a team as a code owner December 8, 2025 19:13
@jekabs-karklins
jekabs-karklins requested review from deepaksftc and mutambaraf and removed request for a team December 8, 2025 19:13
@jekabs-karklins
jekabs-karklins merged commit 09ec125 into develop Jan 13, 2026
22 checks passed
@jekabs-karklins
jekabs-karklins deleted the SWAP-5131-uo-could-not-clone-the-proposal-error branch January 13, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants