# Duplicates

This workflow provides several options for managing duplicate issues.

| Name | @jetbrains/youtrack-workflow-duplicates |
| Auto-attached | yes |
| Modules |    [Attach duplicate links to single duplicated issue](#tree-structure) (on-change rule)      [Raise priority when issue is duplicated by another issue](#raise-priority) (on-change rule)      [Reopen issue when all duplicates links are removed](#reopen-duplicate) (on-change rule)      [Set state to "Duplicate" when duplicates link is added to issue](#set-duplicate) (on-change rule)      [Require links to duplicate issue when state becomes "Duplicate"](#must-have-duplicate-link) (on-change rule)     |

## Use Case

This workflow supports several different use cases that help you automatically process duplicate issues.

* Prevent users from deleting a duplicates link from an issue in a Duplicate state.

* Ensure that all issues with a Duplicate state are linked to a duplicated issue.

* Automatically change the issue state to Duplicate when a duplicates link is added.

* Raise the priority of an issue when a duplicates link is added.

* Attach duplicates links to a single issue instead of creating duplicates of duplicate issues.

## Modules

This workflow includes several different rules that can be applied to manage duplicate issues.

### Attach duplicate links to single duplicated issue

The first rule automatically attaches duplicate issues to a single issue. This prevents users from creating a duplicates 'tree' and consolidates all duplicates links in a single issue.

```JAVASCRIPT
const workflow = require('@jetbrains/youtrack-scripting-api/workflow');
const entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onChange({
  title: 'Attach duplicate links to single duplicated issue',
  guard: (ctx) => {
    const issue = ctx.issue;
    return issue.links.duplicates.isChanged || issue.links['is duplicated by'].isChanged || issue.isChanged('duplicateCluster');
  },
  action: (ctx) => {
    const issue = ctx.issue;
    const duplicateRoot = issue.duplicateRoot;
    if (duplicateRoot !== null && duplicateRoot.id !== (issue.links.duplicates.first() || {}).id) {
      workflow.message('Link was reattached to {0}', duplicateRoot.id);
      issue.links.duplicates.clear();
      if (issue.id !== duplicateRoot.id) {
        issue.links.duplicates.add(duplicateRoot);
        duplicateRoot.links.duplicates.clear();
      }
    }
  },
  requirements: {
    Duplicate: {
      type: entities.IssueLinkPrototype,
      outward: 'is duplicated by',
      inward: 'duplicates'
    }
  }
});
```

### Raise priority when issue is duplicated by another issue

The next rule attempts to raise the priority of an issue when it is duplicated by one or more issues. If a duplicate issue has a higher priority than the issue it duplicates, the priority of the original issue is raised.

```JAVASCRIPT
const entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onChange({
  title: 'Raise priority when issue is duplicated by another issue',
  guard: function (ctx) {
    return ctx.issue.links['is duplicated by'].added.isNotEmpty() && ctx.issue.fields.Priority;
  },
  action: function (ctx) {
    const issue = ctx.issue;
    let currentPriorityOrdinal = issue.fields.Priority.ordinal;
    issue.links['is duplicated by'].added.forEach(function (added) {
      if (added.project.key !== issue.project.key) {
        const anotherProjectField = added.project.findFieldByName(ctx.Priority.name);
        const bundlesAreEqual = function () {
          const set1 = anotherProjectField.values.entries();
          const set2 = ctx.Priority.values.entries();
          for (let next1 = set1.next(), next2 = set2.next(); ; next1 = set1.next(), next2 = set2.next()) {
            if (next1.done !== next2.done || (next1.value || {}).name !== (next2.value || {}).name) {
              return false;
            }
            if (next1.done || next2.done) {
              return true;
            }
          }
        };
        if (!anotherProjectField || !bundlesAreEqual()) {
          console.trace('Can not copy priority of a duplicate as there\'s no respective state in project ' + added.project.key + ' or value sets are not identical');
          return;
        }
      }
      if (added.fields.Priority && added.fields.Priority.ordinal < currentPriorityOrdinal) {
        issue.fields.Priority = added.fields.Priority.name; // need to use .name here as issues can belong to different projects
        currentPriorityOrdinal = issue.fields.Priority.ordinal;
      }
    });
  },
  requirements: {
    Priority: {
      type: entities.EnumField.fieldType
    },
    Duplicate: {
      type: entities.IssueLinkPrototype,
      outward: 'is duplicated by',
      inward: 'duplicates'
    }
  }
});
```

### Reopen issue when all duplicates links are removed

The next rule automatically changes the issue state from Duplicate to Open  if there are no duplicates links attached to the issue.

```JAVASCRIPT
const entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onChange({
  title: 'Reopen issue when all duplicates links are removed',
  guard: (ctx) => {
    const issue = ctx.issue;
    return issue.fields.is(ctx.State, ctx.State.Duplicate) &&
            issue.links.duplicates.removed.isNotEmpty() && issue.links.duplicates.isEmpty();
  },
  action: (ctx) => {
    ctx.issue.fields.State = ctx.State.Open;
  },
  requirements: {
    State: {
      type: entities.State.fieldType,
      Duplicate: {},
      Open: {}
    },
    Duplicate: {
      type: entities.IssueLinkPrototype,
      outward: 'is duplicated by',
      inward: 'duplicates'
    }
  }
});
```

### Set state to "Duplicate" when duplicates link is added to issue

The next rule automatically changes the issue state to Duplicate when a user adds a  duplicates  link.

When an issue is updated, this rule checks that the list of issues linked as duplicates is not empty and the state of this issue is not Duplicate. If a duplicates link is added and the issue state is not Duplicate, the rule verifies that the user has permission to update the State field. If the user has sufficient permission, the issue state is set to Duplicate.

```JAVASCRIPT
const entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onChange({
    title: 'Set state to "Duplicate" when duplicates link is added to issue',
    guard: (ctx) => {
        return ctx.issue.links.duplicates.added.isNotEmpty() && !ctx.issue.fields.is(ctx.State, ctx.State.Duplicate);
    },
    action: (ctx) => {
        if (ctx.issue.canBeWrittenBy(ctx.State.name, ctx.currentUser)) {
            ctx.issue.fields.State = ctx.State.Duplicate;
        }
    },
    requirements: {
        State: {
            type: entities.State.fieldType,
            Duplicate: {}
        },
        Duplicate: {
            type: entities.IssueLinkPrototype,
            outward: 'is duplicated by',
            inward: 'duplicates'
        }
    }
});
```

### Require links to duplicate issue when state becomes "Duplicate"

The next rule forces users to add a link to a duplicated issue when they change an issue state to  Duplicate.

When issue is updated, this rule verifies that the issue state is changed to Duplicate. If the state is changed to Duplicate and the list of duplicates-type links is empty, the user is notified to add a link to a duplicated issue.

```JAVASCRIPT
const workflow = require('@jetbrains/youtrack-scripting-api/workflow');
const entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onChange({
    title: 'Require links to duplicate issue when state becomes "Duplicate"',
    guard: (ctx) => {
        return ctx.issue.fields.becomes(ctx.State, ctx.State.Duplicate);
    },
    action: (ctx) => {
        ctx.issue.required('duplicates', 'Add link to duplicate issue.');
    },
    requirements: {
        State: {
            type: entities.State.fieldType,
            Duplicate: {}
        },
        Duplicate: {
            type: entities.IssueLinkPrototype,
            outward: 'is duplicated by',
            inward: 'duplicates'
        }
    }
});
```

> **Warning: Localized Messages**
> The original workflow script provided by YouTrack uses the `workflow.i18n` function. This is an internal function that is only meant to be used to reference localized message strings. To ensure that you can copy and customize this workflow code, we have removed this function from the sample. If you are editing the default workflow and want to customize any message text, we recommend that you remove these functions from your code as well.
>
>
>
> To learn more about localized messages in workflows, see [Localized Workflow Messages](Workflow-Localization.html).

