YouTrack Cloud 2023.3 Help

Limit Work in Progress

Many development teams use a Kanban framework to manage a steady flow of incoming work. While Scrum teams use sprints to limit the amount of work that is performed in a specific time frame, Kanban teams can impose work in progress limits for all active stages of development.

Our product teams at JetBrains establish their own workflows to match their goals, working habits, and personality. The development teams for AppCode and CLion follow a Kanban-inspired framework. They use Kanban boards to manage everyday activities related to the development of these two IDEs.

Here's what the AppCode Kanban board looks like in our YouTrack installation:

Wip limit board

These boards support the following processes:

  • The teams decide whether to add an issue to the board based on a queue of prioritized tasks. This queue is represented by a saved search with a custom order.

  • The teams discuss priorities during daily standups and weekly meetings where members of the team agree on the list of tasks that should be fixed first. The highest priority issues are placed at the top of the queue.

  • When developers want to start progress on an issue from the queue, they just change the value of the Kanban State field.

  • The board is divided into two swimlanes that help the team prioritize their work.

    • Issues that are moved to the board are added to the General swimlane by default.

    • The Priority swimlane is for issues that require special attention and should be resolved as soon as possible.

    The highest priority issues are placed at the top of the queue.

  • The columns on the board use work-in-progress limits that correspond to the number of members in the team. This helps to control the number of activities that happen simultaneously and highlights areas where the teams have bottlenecks.

  • The team leads see the bigger picture, including all the incoming tasks and how are the tasks are balanced between the team members. This helps them decide who can take a new task and see who might be overwhelmed at the moment.

  • As all issues are added to the board automatically, the team doesn't have to work with a backlog.

The Kanban board helps these teams in the following ways:

  • The development team is able to concentrate on the work in progress, rather than sprint scheduling and estimations.

  • All the tasks that are in progress are shown on the board.

  • Team members can swarm to unload column that exceeds work-progress-limits. For example, the developers can prioritize code reviews or help with testing.

This type of board can be useful for any team that regularly prioritizes incoming feature requests and bug reports.

To build a board like this:

  1. Create your board.

    Wip limit create board
    • Use the Kanban board template.

    • Select the projects that you want to manage on the board.

    Wip limit new board settings
  2. Set sprint options and board behavior.

    Wip limit general settings
    • When you create a board with the Kanban board template, the Sprints option is disabled by default.

    • This board uses the Manually assign issues option. These teams use the queue to prioritize issues and manually add the issues to the board.

    • If you prefer to add cards the board automatically, enable the Filter cards to match a query option. Use the filter to exclude any issues that you don't want to manage on the board.

  3. Define columns.

    Wip limit define columns
    • On this board, columns are identified by values from the Kanban State field. The values in this field represent the stages in their development process. They use the stages Analyze, Develop, Review, Ready to test, Test, Merge, and Done. Merge is a special stage where a task is ready but not yet published in the release branch. When the task is merged to the release branch, it moves to the Done column. Every stage in the development process is used as a column on the board.

  4. Set WIP limits.

    Wip limit set work in progress
    • These teams limit the work in progress for every column except Done.

    • The WIP limits are tuned according to the number of team members who work on each stage and the number of tasks they are comfortable working on in parallel. When cards in a column exceed the WIP limit, this indicates that there is a bottleneck in this stage of development.

  5. Define swimlanes.

    Wip limit define swimlanes
    • Here, the teams use a dedicated custom field, Kanban Track, to separate tasks into different streams. This field stores two values: Priority and General.

    • Priority tasks go on the top and should be taken first.

  6. Configure cards.

    Wip limit card settings
    • The teams use the swimlanes to indicate which issues need the most attention, but they also use the values in the Severity field to highlight cards on the board. This helps them see if there are any issues in the General swimlane that need to be moved to the Priority track.

  7. Pick a chart.

    Wip limit chart settings
    • These teams don’t work with the chart on their agile boards — the WIP limits help them to identify bottlenecks.

    • The default chart option for a Kanban board is Cumulative flow. If you want to use it, it's already configured for you.

  8. Build the backlog.

    • Each of these teams uses a saved search that represents the incoming queue of issues. Each saved search sorts issues in a custom order. They use the custom order in the Issues list to prioritize the queue.

    • This search query simply shows any issue that is unresolved in either project.

      #Unresolved and (project: OC or project CPP)

Working With the Kanban Board

Prioritizing issues in the queue is an important part of the process for both teams. The teams organize the queue during planning sessions and revisit the priority on a regular basis. Planning sessions occur after every major release. The teams make a preliminary agreement on the priority for each task and who will take it.

The team updates the queue when new critical issues or regressions appear, or whenever the teams just feel that the order needs to change.

Developers also have their own lists of things to do. The queue is simply a guideline. When developers choose their next task, they either look into the backlog for an issue in the queue or check their personal list.

When you view the board with the columns expanded, it looks like it might be difficult to work with. In reality, team members with different roles collapse the columns that aren't related to their role.

  • Developers can focus on issues that are in the analysis, development, and review stages.

    Wip limit board developer view
  • Quality Assurance Engineers can focus on issues that are ready to test, in testing, or ready to merge into the main branch.

    Wip limit board q a view
  • Members of the team can also collapse the Priority swimlane if it is empty.

On any Kanban board, tasks eventually start to pile up in the Done column. When these teams publish an EAP or release version of their product, they remove all the issues that are included in the release from the board. One way to clean up the Done column is to search for all the resolved issues that were included in the latest release, for example, in: CPP version: {CLion 2017.1.1} State: Fixed and update them with a command. The command to remove issues from the CLion board is remove Board CLion Kanban. The teams have implemented a workflow that performs this task automatically, as described below.

Workflow Support

The CLion and AppCode teams use several workflow rules that automatically update values in issues that are managed on the board. The following workflows are triggered by events on the board:

  1. When a developer changes takes an issue for analysis or development, the workflow adds the issue to the board and assigns the card to the current user. This workflow also assigns a card to the QA Engineer who moves it to the Test column. The assignee is not updated when cards move between other columns — these assignments are set manually.

  2. When an issue is added to the board, the value in the Kanban Track field is set to General (if the field was empty). This workflow fires when a developer adds an issue to the board with a command or by setting a value of the Kanban State field in the issue itself. This ensures that the issue is added to the correct swimlane. If the value in the Kanban Track field is empty, the card appears in the swimlane for uncategorized cards, which is not where it belongs.

  3. When a card moves to the Test column, the no testing tag is removed from the issue. The team uses the no testing tag to flag trivial fixes. The developer and reviewer can agree that an issue doesn't require testing and add the tag. However, if QA disagrees and decides it should be tested, the tag is removed automatically. When QA agrees to pass the issue without testing, the tag remains as an indicator that the fix was not tested manually.

  4. When a card is moved from one column to another, changes are synchronized with the value that is stored in the State field. The Kanban State field stores values as enumerated types, not states. If the value for the State field is not synchronized with changes on the board, the issues are never moved to a state that is considered to be resolved. This workflow sets the value for the State when the following changes are applied to an issue:

    • When a card moves to the Done column, the state is set to Fixed. The issue is then considered to be resolved. If the card is currently assigned to the Priority swimlane, it moves to General.

    • When a card is added to the board (meaning the Kanban State field is set to any value other than Done), the state is set to In Progress.

    • If an issue is removed from the board (meaning the Kanban State field is set to an empty value) and an issue has the In Progress state, the state is set to Open. In this case, the workflow sets the Kanban Track field to an empty value.

Here's the workflow code:

var entities = require('v1/entities'); var message = require('v1/workflow').message; exports.rule = entities.Issue.onChange({ action: (ctx) => { var issue = ctx.issue; var KANBAN_STATE = 'CIDR Kanban State'; var KANBAN_TRACK = 'CIDR Kanban Track'; var kanbanStateField = issue.project.findFieldByName(KANBAN_STATE); var KANBAN_STATE_ANALYZE = kanbanStateField.findValueByName("Analyse"); var KANBAN_STATE_DEVELOP = kanbanStateField.findValueByName('Develop'); var KANBAN_STATE_TEST = kanbanStateField.findValueByName('Test'); var KANBAN_STATE_DONE = kanbanStateField.findValueByName('Done'); var KANBAN_TRACK_GENERAL = issue.project.findFieldByName(KANBAN_TRACK).findValueByName('General'); var stateField = issue.project.findFieldByName('State'); var STATE_OPEN = stateField.findValueByName('Open'); var STATE_IN_PROGRESS = stateField.findValueByName('In Progress'); var STATE_FIXED = stateField.findValueByName('Fixed'); var putOnBoard = function() { console.log("Placing the issue on the board"); issue.applyCommand("add board " + issue.project.name); if (issue[KANBAN_TRACK] === null) { issue[KANBAN_TRACK] = KANBAN_TRACK_GENERAL; } message('Issue is placed on the board ' + issue.project.name); }; var removeFromBoard = function() { console.log("Removing the issue from the board"); issue[KANBAN_STATE] = null; issue[KANBAN_TRACK] = null; issue.sprints.clear(); if (issue.State.name == STATE_IN_PROGRESS.name) { console.log("Setting open state"); issue.State = STATE_OPEN; } message('Issue is removed from the board'); }; var removeFromBoardIfReleased = function() { if (issue[KANBAN_STATE] !== null && issue[KANBAN_STATE].name == KANBAN_STATE_DONE.name) { console.log("Checking if the issue is released"); var released = !issue['Fix versions'].find(function(each) { if (each.name.indexOf("Next") != -1) return true; return false; }); if (released) { console.log("Issue is released"); removeFromBoard(); } } }; if (issue.isChanged(KANBAN_STATE)) { console.log(KANBAN_STATE + " changed"); var curr = issue[KANBAN_STATE]; var prev = issue.oldValue(KANBAN_STATE); if (curr !== null /* issue is moved on board */ ) { issue.State = STATE_IN_PROGRESS; var needToAssign = curr.name == KANBAN_STATE_TEST.name /* when issue is taken into testing */ ; if (issue.sprints.size === 0 /* issue is placed on board */ ) { putOnBoard(); needToAssign = needToAssign || issue.Assignee === null || /* when issue is taken into development or testing */ curr.name == KANBAN_STATE_ANALYZE.name || curr.name == KANBAN_STATE_DEVELOP.name || curr.name == KANBAN_STATE_TEST.name; } if (needToAssign) { issue.Assignee = ctx.currentUser; } /* if (prev !== null) { var lastItem = issue.workItems.last(); var workType = entities.WorkItemType.findByProject(issue.project).find(function(each) { return each.name == prev.name; }); message("Warning: Work type not found for " + prev.name); if (lastItem !== null && workType !== null) { var now = new Date(); console.log("last " + lastItem + " date " + lastItem.date); var durationInMinutes = 100;//(now.getTime() - lastItem.date.time) / (1000 * 60); console.log(prev.name + " took " + durationInMinutes); issue.addWorkItem(prev.name, now.getTime(), ctx.currentUser, durationInMinutes, workType); } } */ if (curr.name == KANBAN_STATE_TEST.name) { issue.removeTag("no testing"); } else if (curr.name == KANBAN_STATE_DONE.name) { console.log("Setting the issue to the Done state"); issue.State = STATE_FIXED; issue[KANBAN_TRACK] = KANBAN_TRACK_GENERAL; removeFromBoardIfReleased(); } } else if (prev !== null /* issue is removed from the board */ ) { removeFromBoard(); } } else if (issue.isChanged(KANBAN_TRACK)) { console.log(KANBAN_TRACK + " changed"); if (issue[KANBAN_TRACK] === null /* issue is removed from the board */ ) { removeFromBoard(); } else { issue.required(KANBAN_STATE, 'Please specify the column on the board'); } } else if (issue.isChanged('sprints')) { console.log("sprints changed"); if (issue.sprints.size === 0) { // when issue is moved between AppCode/CLion projects if (issue.isChanged("project") && (issue.oldValue("project").name == "AppCode" || issue.oldValue("project").name == "CLion") && issue.oldValue("sprints").size !== 0) { putOnBoard(); } else { removeFromBoard(); } } else { issue.required(KANBAN_STATE, 'Please specify the column on the board'); } } if (issue.isChanged("Fix versions")) { removeFromBoardIfReleased(); } } });
Last modified: 29 February 2024