# Azure DevOps

{% hint style="info" %}
Cortex connects to many third-party vendors whose system interfaces frequently change. As a result, integration behavior or configuration steps may shift without notice. If you encounter unexpected issues, check with your system administrator or refer to the vendor's documentation for the most current information. Additionally, integration sync times vary and are subject to scheduling overrides and timing variance.
{% endhint %}

[Azure DevOps](https://azure.microsoft.com/en-us/products/devops) is a Microsoft-owned version control system used for managing the software development lifecycle.

Integrating Cortex with Azure DevOps allows you to:

* Automatically discover and track ownership of Azure DevOps entities
* Follow a GitOps workflow with Azure DevOps
* View information about your Azure DevOps repositories on an entity's details page, including: The repo associated with the entity, recent commits and releases in the event timeline, the most-used language in the files for that entity, the top code contributors, and their number of contributions
  * If you pull in [Azure DevOps pipeline data](#define-pipelines), you can also see pipeline runs and builds in the CI/CD section of an entity's details page.
  * If you enable the [option to pull in Azure DevOps work items](#enable-or-disable-azure-devops-work-items), you will also see a list of open work items on entity pages.
* View information about pull requests and work items in the engineering homepage
* [Create a work item from an Initiative issue](#create-a-work-item-from-an-initiative-issue) in Cortex
* Use Azure DevOps metrics in Eng Intelligence to understand key metrics and gain insight into services, incident response, and more
* Create [Scorecards](#scorecards-and-cql) that track progress and drive alignment on projects involving your repositories, Azure DevOps work items, and Azure DevOps pipeline data

## How to configure Azure DevOps with Cortex

### Configure the integration in Cortex

You can configure Azure DevOps with a Personal Access Token (PAT) or with a Service Principal with Entra ID.

{% tabs %}
{% tab title="Personal Access Token" %}
**Prerequisite**

Before you get started:

* Add a [Azure DevOps personal access token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate) with at least the following scopes enabled:
  * Analytics: `read`
  * Build: `read`
  * Code: `read`
    * If using the [Scaffolder](https://docs.cortex.io/streamline/workflows/scaffolder) with Azure DevOps, you must also enable:
      * Code: `write`
      * Code: `manage`
  * Graph & Identity: `read`
  * Work Items: `read` and `write`

**Configure the Azure DevOps integration with a PAT**

1. In Cortex, navigate to the [Azure DevOps settings page](https://app.getcortexapp.com/admin/integrations/azuredevops).
   * Click **Integrations** from the main nav. Search for and select **Azure DevOps**.
2. Click **Add configuration** and select **Personal Access Token**.
3. Configure the Azure DevOps integration form:
   * **Organization**: Enter the slug for your Azure DevOps organization.
   * **Username**: Enter the username for your personal access token.
   * **Personal access token**: Enter your Azure DevOps personal access token.
   * **Host**: Optionally, if you are using a self-managed setup, enter your hostname.
4. Click **Save**.
   {% endtab %}

{% tab title="Service Principal" %}
**Prerequisite**

Before you get started:

* In your Azure portal under **Entra ID > App registrations**, [set up an app registration](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app#add-credentials).
  * You will need the Client ID and Azure Tenant ID associated with this app registration.
  * When you create an app registration, a Service Principal user is also generated.
* In your Azure portal, create a Client Secret for your app registration.
  * While viewing the overview of your app registration, click the link next to "Client credentials." You will be directed to the **Certificates & secrets** page, where you can click **+New client secret** to create a secret.

**Step 1: Add your Service Principal to your Azure DevOps organization**

You must add the Service Principal user to the organization you're integrating with Cortex.

1. In Azure DevOps, from the list of organizations, select the one you will be integrating with Cortex.
2. Navigate to **Organization Settings > General > Microsoft Entra**. Connect your Entra ID directory (the one containing the app registration) with the Azure DevOps organization.
3. Navigate to **Organization Settings > General > Users,** then click **Add users**.
4. In the side panel, configure the new user:
   * **Users or Service Principals**: Enter the Service Principal associated with your app registration.
   * **Access level**: Select `Basic`.
   * **Add to projects**: Add the user to the project(s) that your Cortex integration will need access to.
   * **Azure DevOps Groups**: Select security groups. `Project Contributors` should be sufficient, or you can choose a custom security group that has Code write permissions.
     * Learn more about [permissions for Service Principals in Azure's documentation](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops#step-3-configure-permissions).
5. At the bottom of the side panel, click **Add**.

**Step 2: Configure the Azure DevOps integration in Cortex**

1. In Cortex, navigate to the [Azure DevOps settings page](https://app.getcortexapp.com/admin/integrations/azuredevops).
   * Click **Integrations** from the main nav. Search for and select **Azure DevOps**.
2. Click **Add configuration** and select **Service Principal**.
3. Configure the Azure DevOps integration form:
   * **Category**: Select which integration categories this configuration will apply to.
   * **Configuration alias**: Enter an alias for this configuration.
   * **Organization**: Enter the slug for your Azure DevOps organization.
   * **Client ID**: Enter the Application Client ID for your app registration.
   * **Client Secret**: Enter the Client Secret for your app registration.
   * **Azure Tenant ID**: Enter the Directory (Tenant) ID for your app registration.
   * **Host**: Optionally, if you are using a self-managed setup, enter your hostname.
4. Click **Save**.
   {% endtab %}
   {% endtabs %}

Cortex supports mapping multiple identities for a single user if you have multiple configurations of Azure DevOps. See the [Identity mapping documentation](https://docs.cortex.io/configure/settings/managing-users/identity-mapping) for more information.

#### **Enable or disable Azure DevOps work items**

On the [Azure DevOps settings page in Cortex](https://app.getcortexapp.com/admin/settings/azuredevops), you can choose whether Azure DevOps work items should be pulled in from Azure DevOps. Cortex recommends disabling this option if your organization does not use work items or if you are worried about running into rate limit issues.

## How to connect Cortex entities to Azure DevOps

### Import entities from Azure DevOps

See the [Create services documentation](https://docs.cortex.io/entities/adding-entities/add-services#creating-services) for instructions on importing entities.

### Editing the entity descriptor

In an entity's YAML, you can define a [repository](#define-a-repository), [work items](#define-work-items), [ownership](#define-ownership), and [pipelines](#define-pipelines).

#### **Define a repository**

To define an Azure DevOps repository for a given entity, add the `x-cortex-git` block to the entity's descriptor.

```yaml
x-cortex-git:
  azure:
    project: cortex
    repository: docs
    basepath: myService
    alias: accountAlias
```

| Field        | Description                                                                                                                                | Required |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| `project`    | The name of the project as listed under the "Projects" tab when you are logged into Azure DevOps (on the <https://dev.azure.com//> screen) | **✓**    |
| `repository` | The repo name you see when you navigate to the "Repos" section of Azure DevOps                                                             | **✓**    |
| `basepath`   | If the entity is in a monorepo (e.g. in a subdirectory), use this field to define the subdir                                               |          |
| `alias`      | Alias for the configuration in Cortex (only needed if you have opted into multi-account support)                                           |          |

Only one repository can be defined for in a given entity's YAML in the `x-cortex-git` block.

#### **Define work items**

Before adding work items to your entity YAML, make sure you have [enabled the option to pull in Azure DevOps work items](#enable-or-disable-azure-devops-work-items) in your integration settings.

To define Azure DevOps work items for a given entity, add the `x-cortex-azure-devops` block to the entity's descriptor. If there is no work item registrations, but the entity matches a repository, we will pull in all work items from the repository's project with a tag that matches the Cortex entity name, [Cortex tag](https://docs.cortex.io/entities#cortex-tag), or the repository name.

<details>

<summary>Example WIQL</summary>

The example YAML below is based on the following example WIQL:

```
SELECT
    [System.Id],
    [System.AssignedTo],
    [System.State],
    [System.Title],
    [System.Tags]
FROM workitems
WHERE
    '[System.TeamProject] = Design Agile'
    AND '[System.WorkItemType] = User Story'
    AND '[System.State] = Active'
ORDER BY [System.ChangedDate] DESC
ASOF '02-11-2020'
```

Learn more about WIQL in [Microsoft's WIQL syntax reference](https://learn.microsoft.com/en-us/azure/devops/boards/queries/wiql-syntax?view=azure-devops).

</details>

```yaml
x-cortex-azure-devops:
    workItems:
        projects:
            - name: projectName1
              wiqls:
                - "[System.TeamProject] = 'Design Agile'"
                - "[System.WorkItemType] = 'User Story'"
                - "[System.State] = 'Active'"
                - ORDER BY [System.ChangedDate] DESC ASOF '02-11-2020'
            - name: projectName2
              alias: alias1
            - name: projectName3
              alias: alias2
```

| Field      | Description                                                                                                                         | Required |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `projects` | List of the projects                                                                                                                | **✓**    |
| `name`     | The project name as listed under the "Projects" tab when you are logged into Azure DevOps (on the <https://dev.azure.com//> screen) | **✓**    |
| `wiqls`    | List of WIQL conditions to filter work items fetched                                                                                |          |
| `alias`    | Alias for the configuration in Cortex (only needed if you have opted into multi-account support)                                    |          |

#### **Define ownership**

Ownership of each entity through Azure DevOps is defined through an owner of type `group`.

```yaml
x-cortex-owners:
  - type: group
    name: My Azure DevOps Team
    provider: AZURE_DEVOPS
    description: This is a description for this owner # optional
```

`name` is a case-sensitive field that corresponds to the upstream identifier of your owner from Azure DevOps.

Learn more about ownership in [Defining ownership](https://docs.cortex.io/ingesting-data-into-cortex/entities/ownership).

#### Define pipelines

You can add Azure DevOps pipelines under the `x-cortex-azure-devops` block:

```yaml
  x-cortex-azure-devops:
    pipelines:
      projects:
      - name: projectName1
        alias: config-1
        pipelines:
        - id: 1
      - name: projectName2
        alias: config-2
        pipelines:
        - id: 2
```

| Field          | Description                                                                                                                         | Required |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `piepelines`   | List of the pipelines                                                                                                               | **✓**    |
| `projects`     | List of the projects                                                                                                                | **✓**    |
| `name`         | The project name as listed under the "Projects" tab when you are logged into Azure DevOps (on the <https://dev.azure.com//> screen) | **✓**    |
| `alias`        | Alias for the configuration in Cortex (only needed if you have opted into multi-account support)                                    |          |
| `pipelines:id` | The Azure DevOps `system.definitionID` for the pipeline.                                                                            | **✓**    |

Learn more about Azure DevOps pipelines in [Microsoft's documentation](https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started/what-is-azure-pipelines?view=azure-devops).

### Identity mappings for Azure DevOps

Cortex maps users' email addresses to discovered Azure DevOps accounts.

You can confirm users' Azure DevOps accounts are connected from [Azure DevOps identity mappings in settings](https://app.getcortexapp.com/admin/settings/azuredevops-mappings).

### Create a work item from an Initiative issue

Initiatives allow you to set deadlines for specific rules or a set of rules in a given Scorecard and send notifications to users about upcoming due dates.

From the Issues tab of an Initiative, you can automatically create a Azure DevOps work item from a failing rule:

1. Click **Create issue**.
2. In the modal that appears, fill out the form:
   * **Integration**: If you have multiple task tracking tools, select Azure DevOps from the Integration dropdown.
   * **Name**: Enter a name for the configuration.
   * **Project**: Select from the dropdown.
     * Options available in the dropdown are pulled in from the specific Azure DevOps instances configured in Settings.
3. Select the [**Work item type**](https://learn.microsoft.com/en-us/azure/devops/boards/work-items/about-work-items?view=azure-devops\&tabs=agile-process/) and the **Sub-item Type** from the respective dropdowns. Then, select how the sub-items's fields should be populated on issue creation and status change.
4. Choose to include or exclude groups of entities, or define a more advanced filter.

The issue configuration will apply to all entities that meet the filter criteria. Once an entity is passing the rule, Cortex will automatically close the associated ticket.

## Using the Azure DevOps integration

### View Azure DevOps data on entity pages in Cortex

The Azure DevOps integration will populate the **Repo** detail block on an entity's details page.

In the **Recent activity preview**, you'll find the recent commits and releases. These will also appear in the event timeline.

These data will appear for entities imported from a Git source or those that have a Git repo defined in their YAMLs.

#### **Events**

On an entity's **Events** page, you can find all of the commits and releases associated with that entity. Each is hyperlinked to the commit or release page in Azure DevOps and includes a timestamp.

#### **CI/CD**

From the **CI/CD > Deploys** page in the entity's sidebar, see a history of pipeline runs.

#### **Repository**

You can access more detailed information pulled from Azure DevOps under **Repository** in the sidebar. At the top of the repository page, you'll find the repo associated with that entity and the most-used language in files for that entity. In the **Top contributors** block, you'll find the three users who have contributed the most code and the number of their contributions.

In the **Commits** section, you'll find the 10 most recent commits and metadata about each. Below **Commits** is the **Recent releases** section, which includes the 5 most recent releases.

#### **Issue tracking**

In the **Issue tracking** section, you can find a list of open [Azure DevOps work items](https://learn.microsoft.com/en-us/azure/devops/boards/work-items/about-work-items?view=azure-devops\&tabs=agile-process). Each work item will show the title, summary, assignees, priority, and date created.

#### **Packages**

Packages are automatically scraped from your Git repos or they can be submitted via the [packages API](https://app.gitbook.com/s/nPgS8L9MAPtoOtdWdeDp/readme/packages). The package file must be in the root of your repository — or, if you're using `basepath`, in the root of the subdirectory — to be scraped by Cortex. You can query an entity's packages in [CQL explorer](https://app.getcortexapp.com/admin/cql-explorer) using `packages()`.

To view packages, click **Packages** in the entity's sidebar.

The following package types are automatically scraped from repositories:

* JavaScript / Node.js: `package.json`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
* Python: `requirements.txt`, `pipfile.lock`
* .NET (C#): `packages.lock.json`
* Java: `pom.xml`
* Go: `go.sum`

All other files of these types can be added via the [packages API](https://app.gitbook.com/s/nPgS8L9MAPtoOtdWdeDp/readme/packages).

### Engineering homepage

The Azure DevOps integration enables Cortex to pull information about pull requests and work items into the [homepage](https://docs.cortex.io/streamline/homepage). You can find your open pull requests, any pull requests assigned to you for review, and any work items assigned to you.

Pull requests and work items from Azure DevOps are refreshed every 5 minutes.

### Eng Intelligence

The [Eng Intelligence tool](https://app.getcortexapp.com/admin/eng-intelligence) also uses pull request data from Azure DevOps to generate metrics:

* Average PR open to close time
* Avg time to first review
* Avg time to approval
* PRs opened
* Weekly PRs merged
* Avg PRs reviewed/week
* Avg commits per PR

Read more about how Eng Intelligence tracks metrics for teams and users in the [Eng Intelligence documentation](https://docs.cortex.io/improve/eng-intelligence).

### Scorecards and CQL

With the Azure DevOps integration, you can create Scorecard rules and write CQL queries based on Azure DevOps work items.

See more examples in the [CQL Explorer](https://app.getcortexapp.com/admin/cql-explorer) in Cortex.

<details>

<summary>Approvals required to merge</summary>

Total number of approval required to merge a pull request into a repository. Defaults to 0 if no approvals are defined.

**Definition:** `git.numOfRequiredApprovals()`

**Examples**

For a security or development maturity Scorecard, you can write a rule to make sure at least one approval is required for a pull request:

```
git.numOfRequiredApprovals() >= 1
```

By having a rigorous PR process in place for a repo, you can make sure changes aren't made that create vulnerabilities. This kind of rule could also be used in a best practices or project standards Scorecard.

You can also use a similar expression in the Query Builder to find entities lacking approval:

```
git.numOfRequiredApprovals() < 1
```

</details>

<details>

<summary>Branches</summary>

List all live branches with some basic metadata:

* Head
* Is protected
* Name

**Definition:** `git.branches(): List<GitBranch>`

**Example**

For a development best practices Scorecard, you can make sure that branches associated with an entity match a standard naming convention:

```
git.branches().all((branch) => branch.name.matches("(main|master|feat-.*|bug-.*|task-.*
```

</details>

<details>

<summary>Branch protection details</summary>

Find details for a specified branch or default branch if none is specified.

**Definition:** `git.branchProtection(branchName: Text?): GitBranchProtection`

**Examples**

For a security Scorecard, you can write a rule to make sure the default branch is protected:

```
git.branchProtection() != null
```

Or to make sure the main branch is protected:

```
git.branchProtection("main") != null
```

Because vulnerabilities in the default branch are critical, this rule should be in one of the first couple levels. A higher-level rule might make sure that branch protection checks are set:

```
git.branchProtection("main").requiredStatusChecks.length > 0
```

You can also use the Query Builder to find entities with unprotected default branches:

```
git.branchProtection() = null
```

</details>

<details>

<summary>Commits</summary>

Get the latest commits (to a maximum of 100) for a defined lookback period (defaulting to 7 days).

These results can be filtered based on branch name, using the default branch if no other branch is provided.

**Definition:** `git.commits()`

**Examples**

You can use the `git.commits()` expression in a security Scorecard to make sure entities have fewer than three commits to a "security-fixes" branch in the last week:

```
git.commits(branch="security-fixes").length < 3
```

Entities passing this rule will include those that haven't needed three or more security fixes. This can indicate that there aren't vulnerabilities in a given entity's code, but could also suggest that fixes aren't being implemented.

Using this rule in conjunction with one focused on vulnerabilities could provide the extra context needed to gain a better understanding of what's happening.

</details>

<details>

<summary>Default branch</summary>

Default branch for the entity's repository or `main` when null.

**Definition:** `git.defaultBranch()`

**Examples**

If default branches should always be named "main," you can write a rule in a best practices Scorecard to make sure entities are compliant:

```
git.defaultBranch().matches("main")
```

</details>

<details>

<summary>File contents</summary>

Load the contents of a file from the entity's associated repository.

The contents can be validated by using string comparison operations or parsed by the built-in `jq` function. The `jq` function will automatically coerce file contents of JSON or YAML formats.

**Definition:** `git.fileContents(<filename: Text>)`

**Examples**

For a Scorecard focused on development maturity, you could use the `git.fileContents()` rule to enforce that a CI pipeline exists, and that there is a testing step defined in the pipeline:

```
git.fileContents(“circleci/config.yml”).matches(“.*npm test.*”) - Enforce that a CI pipeline exists, and there is a testing step defined in the pipeline
```

A best practices Scorecard, meanwhile, could use this expression for a number of rules:

* To make sure node engine version in specified in the `package.json` file:

  ```
  jq(git.fileContents("package.json"), ".engines.node") != null
  ```
* To make sure TypeScript projects have a `tsconfig.json` file checked in:

  ```
  jq(git.fileContents("package.json"), ".devDependencies | with_entries(select(.key == \"typescript\")) | length") == 0 or git.fileExists("tsconfig.json")
  ```
* To make sure projects using yarn do not allow NPM:

  ```
  jq(git.fileContents("package.json"), ".engines.yarn") == null or jq(git.fileContents("package.json"), ".engine.npm") = "please-use-yarn"
  ```
* And to ensure the yarn version being used is not deprecated:

  ```
  jq(git.fileContents("package.json"), ".engines.yarn") == null or !(semver("1.2.0") ~= semverRange(jq(git.fileContents("package.json"), ".engines.yarn")))
  ```

</details>

<details>

<summary>File exists</summary>

Check if file exists from within the entity's associated repository.

**Definition:** `git.fileExists(<filename: Text>)`

**Examples**

For a development best practices Scorecard, this expression can be used for a rule that makes sure developers are checking in lockfiles to ensure repeatable builds:

```
git.fileExists(“package-lock.json”)
```

In the Query builder, you can use this expression with a wildcard to find entities with unit tests enabled:

```
git.fileExists(*Test.java”)
```

Or to find entities with an outdated Terraform version:

```
git.fileExists("terraform/versions.tf") and !git.fileContents("terraform/versions.tf").matchesIn("required_version =! \"[~>= ]{0,3}0\\.(12|13)")
```

</details>

<details>

<summary>Has Cortex YAML (GitOps)</summary>

When enabling GitOps to manage entity descriptors, Cortex checks for a checked in file `./cortex.yaml` at the root directory. This rule can help track migrations from UI editing to GitOps for entity descriptor management.

**Definition:** `git.hasCortexYaml()`

**Examples**

If you're using a Scorecard to track a migration from Cortex UI to GitOps, you can use this rule to make sure entities are set up for GitOps management of entity descriptors:

```
git.hasCortexYaml() == true
```

</details>

<details>

<summary>Git repository set</summary>

Check if entity has a registered Git repository.

**Definition:** `git (==/!=) null`

**Examples**

A Scorecard focused on best practices or standards will likely include a rule in its first level making sure a Git repository is set up:

```
git != null
```

If an entity is failing this rule, it can indicate broader issues with the integration or explain why an entity isn't functioning as expected.

</details>

<details>

<summary>Last commit details</summary>

Provides last commit details.

**Definition:** `git.lastCommit()`

**Examples**

Depending on best practices at your organization, you may want to confirm the last commit for a given entity is no older than 3 days:

```
datetime(git.lastCommit().date).fromNow() > duration("P-3D")
```

Confirming whether a service was updated recently can help team members catch outdated code sooner. Plus, if there is a security issue, you can quickly determine which services have or have not been updated to patch the vulnerability.

For a best practices Scorecard, you can also use this expression to make sure the entity's last commit message follows conventional commit guidelines:

```
git.lastCommit().message.matches("^(feat|fix|docs|style|refactor|test|chore)(\\(.+\\))?:*")
```

</details>

<details>

<summary>List pipelines</summary>

List pipelines with metadata, including name, id, url, and project name.

**Definition**: `azureDevops.pipelines()`

**Examples**

You could write a Scorecard rule to ensure that the entity has at least 1 pipeline that scans vulnerabilities:

```
azureDevops.pipelines().any(pipeline => pipeline.name.matchesIn("Vuln scan"))
```

You could write a Scorecard rule to ensure that the entity has an Azure DevOps pipeline set:

```
azureDevops.pipelines() != null
```

</details>

<details>

<summary>Pipeline build success rate</summary>

Percentage of build pipelines that complete successfully.

This is calculated against builds on the default branch for commits in the last 30 days: `# successful builds / (# successful + # failed)`.

**Definition:** `git.percentBuildSuccess()`

**Examples**

This expression can be used in a development maturity Scorecard to write a rule making sure at least 95% of build runs are successful:

```
git.percentBuildSuccess() >= 0.95
```

</details>

<details>

<summary>Pipeline metrics</summary>

List pipelines with metrics. Metrics contains the successRate & averageDuration of a pipeline.

**Definition**: `azureDevops.pipelineMetrics()`

**Examples**

You could write a Scorecard rule to ensure pipelines have a successRate higher than 95%:

```
azureDevops.pipelineMetrics().all((pipeline) => pipeline.metrics.successRate > 0.95)
```

</details>

<details>

<summary>Pipeline runs</summary>

Get pipelines runs meeting the given filter criteria, which includes results, states. Results include "succeeded", "failed", "canceled", "unknown", states include "completed", "inProgress", "canceling", "unknown".

**Definition**: `azureDevops.pipelineRuns()`

**Examples**

List pipeline runs that are opened and reviewed within 1 day:

```
azureDevops.pipelineRuns().filter(run => run.run.completedDate != null).map((run) => run.run.createdDate.until(run.run.completedDate)).averageDuration() < duration("P1D")
```

</details>

<details>

<summary>Recency of last commit</summary>

Calculates the duration of time between Scorecard evaluation and the date of the last commit from the entity's Git repository.

**Definition:** `git.lastCommit().freshness`

**Examples**

One of the first rules you might write for a Scorecard focused on development maturity or security is one validating that the last commit was within the last month:

```
git.lastCommit().freshness < duration("P1M")
```

As counterintuitive as it may seem, services that are committed too infrequently are actually at more risk. People who are familiar with the service may leave a team, institutional knowledge accumulates, and from a technical standpoint, the service may be running outdated versions of your platform tooling.

</details>

<details>

<summary>Reviews</summary>

List reviews left during a defined lookback period.

* Organization
* Repository
* Review date
* Reviewer

**Definition:** `git.reviews()`

**Examples**

A development maturity Scorecard might use the `git.reviews()` expression to make sure that there is a rigorous review process in place before changes are implemented:

```
git.reviews(lookback=duration("P7D")).length > 25
```

This rule makes sure that there are more than 25 reviews left in the last week.

</details>

<details>

<summary>Search repository files</summary>

Find all instances of code search query from within a repository.

Can filter by path, file name (extension required in file name), or extension. Filters can use \* for glob matching. Supplying a query is required.

**Definition:** `git.codeSearch((query = <query: Text>) (, path = <path: Text>) (, fileName = <fileName: Text>) (, fileExtension = <fileExtension: Text>)): List<GitSearchResult>`

**Examples**

You can use the `git.codeSearch()` expression to query for entities that have certain components, like icons:

```
git.codeSearch(query = "icon", fileExtension = "css").length > 0
```

</details>

<details>

<summary>Top repository language</summary>

Find top used language for a repository, if available.

**Definition:** `git.topLanguage()`

**Examples**

Let's say the primary language developers should be using is Kotlin. You can write a rule to make sure that the top language associated with entities is Kotlin:

```
git.topLanguage() == "kotlin"
```

You can also use this expression to query for entities that don't have Kotlin as the top language to identify those that need to be updated:

```
git.topLanguage() != "kotlin"
```

</details>

<details>

<summary>Work items</summary>

Number of **unresolved** work items associated with the entity, where unresolved is defined as the WIQL \[System.State] NOT IN ('Closed', 'Done', 'Completed', 'Inactive', 'Removed').

**Definition:** `azureDevops.workItems()`

**Examples**

For a Scorecard measuring entity maturity, you can use this expression to make sure entities have fewer than 10 Azure DevOps work items:

```
azureDevops.workItems().length <= 10
```

</details>

<details>

<summary>Work items from WIQL query</summary>

Number of work items associated with the entity based on arbitrary WIQL query.

**Definition:** `azureDevops.workItems(query: Text | Null)`

**Examples**

For a more specific rule in an entity maturity Scorecard, you can use this expression with a WIQL query to make sure entities have no more than 3 tickets with "Doing" status and highest priority.

```
jira.workItems("System.State = \"Doing\" AND Microsoft.VSTS.Common.Priority = 1").length <= 3
```

</details>

### View integration logs <a href="#still-need-help" id="still-need-help"></a>

{% hint style="info" %}
This feature is available to Cortex cloud customers.
{% endhint %}

On the integration settings page, click the **Logs** tab to view logs from the last 7 days. Learn more in [Troubleshooting with integration logs](https://docs.cortex.io/ingesting-data-into-cortex/integrations/..#troubleshooting-with-integration-logs).

## Background sync

Cortex conducts a background sync of Azure DevOps identities every day at 10 a.m. UTC. Pull requests and work items are refreshed every 5 minutes.

## Still need help?[​](https://docs.cortex.io/docs/reference/integrations/aws#still-need-help) <a href="#still-need-help" id="still-need-help"></a>

The following options are available to get assistance from the Cortex Customer Engineering team:

* **Email**: <help@cortex.io>, or open a support ticket in the in app Resource Center
* **Slack**: Users with a connected Slack channel will have a workflow added to their account. From here, you can either @CortexTechnicalSupport or add a `:ticket:` reaction to a question in Slack, and the team will respond directly.

Don’t have a Slack channel? Talk with your Customer Success Manager.
