diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 38df3ee0e43d09788087b6593e8a5b0b52f6a353..217679e733c35429bcc5560c7925df7bce94ac5d 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -47,6 +47,7 @@ def handle_changes(merge_request, options)
       handle_draft_status_change(merge_request, changed_fields)
 
       track_title_and_desc_edits(changed_fields)
+      track_discussion_lock_toggle(merge_request, changed_fields)
 
       notify_if_labels_added(merge_request, old_labels)
       notify_if_mentions_added(merge_request, old_mentioned_users)
@@ -95,6 +96,16 @@ def track_title_and_desc_edits(changed_fields)
       end
     end
 
+    def track_discussion_lock_toggle(merge_request, changed_fields)
+      return unless changed_fields.include?('discussion_locked')
+
+      if merge_request.discussion_locked
+        merge_request_activity_counter.track_discussion_locked_action(user: current_user)
+      else
+        merge_request_activity_counter.track_discussion_unlocked_action(user: current_user)
+      end
+    end
+
     def notify_if_labels_added(merge_request, old_labels)
       added_labels = merge_request.labels - old_labels
 
diff --git a/changelogs/unreleased/292824-track-mr-lock-changes.yml b/changelogs/unreleased/292824-track-mr-lock-changes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..89102528aed349a77effa2b019975b4363f8fd39
--- /dev/null
+++ b/changelogs/unreleased/292824-track-mr-lock-changes.yml
@@ -0,0 +1,5 @@
+---
+title: Track usage pings when MR gets locked/unlocked
+merge_request: 55069
+author:
+type: other
diff --git a/changelogs/unreleased/expose_project_container_registry_url.yml b/changelogs/unreleased/expose_project_container_registry_url.yml
new file mode 100644
index 0000000000000000000000000000000000000000..923d14ed5a6f6c84b3fbd76552c9996540ca5402
--- /dev/null
+++ b/changelogs/unreleased/expose_project_container_registry_url.yml
@@ -0,0 +1,5 @@
+---
+title: Expose container_registry_image_prefix to project API
+merge_request: 54090
+author: Mathieu Parent
+type: added
diff --git a/config/feature_flags/development/usage_data_i_code_review_user_mr_discussion_locked.yml b/config/feature_flags/development/usage_data_i_code_review_user_mr_discussion_locked.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e6e81f48028f2efc18494086e48ac3e66e34622a
--- /dev/null
+++ b/config/feature_flags/development/usage_data_i_code_review_user_mr_discussion_locked.yml
@@ -0,0 +1,8 @@
+---
+name: usage_data_i_code_review_user_mr_discussion_locked
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069
+rollout_issue_url:
+milestone: '13.10'
+type: development
+group: group::code review
+default_enabled: true
diff --git a/config/feature_flags/development/usage_data_i_code_review_user_mr_discussion_unlocked.yml b/config/feature_flags/development/usage_data_i_code_review_user_mr_discussion_unlocked.yml
new file mode 100644
index 0000000000000000000000000000000000000000..03ec6cde34b1fe4a408042979041ddca1f5de492
--- /dev/null
+++ b/config/feature_flags/development/usage_data_i_code_review_user_mr_discussion_unlocked.yml
@@ -0,0 +1,8 @@
+---
+name: usage_data_i_code_review_user_mr_discussion_unlocked
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069
+rollout_issue_url:
+milestone: '13.10'
+type: development
+group: group::code review
+default_enabled: true
diff --git a/config/metrics/counts_28d/20210301103859_i_code_review_user_mr_discussion_locked_monthly.yml b/config/metrics/counts_28d/20210301103859_i_code_review_user_mr_discussion_locked_monthly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9ad7ff81231ee03eff962e78af968b58e90c1201
--- /dev/null
+++ b/config/metrics/counts_28d/20210301103859_i_code_review_user_mr_discussion_locked_monthly.yml
@@ -0,0 +1,20 @@
+---
+key_path: redis_hll_counters.code_review.i_code_review_user_mr_discussion_locked_monthly
+description: Count of unique users per month who locked a MR
+product_section: dev
+product_stage: create
+product_group: group::code review
+product_category: code_review
+value_type: number
+status: implemented
+milestone: "13.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069
+time_frame: 28d
+data_source: redis_hll
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_28d/20210301103925_i_code_review_user_mr_discussion_unlocked_monthly.yml b/config/metrics/counts_28d/20210301103925_i_code_review_user_mr_discussion_unlocked_monthly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..707a2fc76d10e46e5c0874c2c452fe53167b7705
--- /dev/null
+++ b/config/metrics/counts_28d/20210301103925_i_code_review_user_mr_discussion_unlocked_monthly.yml
@@ -0,0 +1,20 @@
+---
+key_path: redis_hll_counters.code_review.i_code_review_user_mr_discussion_unlocked_monthly
+description: Count of unique users per month who unlocked a MR
+product_section: dev
+product_stage: create
+product_group: group::code review
+product_category: code_review
+value_type: number
+status: implemented
+milestone: "13.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069
+time_frame: 28d
+data_source: redis_hll
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_7d/20210302105258_i_code_review_user_mr_discussion_unlocked_weekly.yml b/config/metrics/counts_7d/20210302105258_i_code_review_user_mr_discussion_unlocked_weekly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..80471ed836ac1b8ead9420276f3ab51e989b988f
--- /dev/null
+++ b/config/metrics/counts_7d/20210302105258_i_code_review_user_mr_discussion_unlocked_weekly.yml
@@ -0,0 +1,20 @@
+---
+key_path: redis_hll_counters.code_review.i_code_review_user_mr_discussion_unlocked_weekly
+description: Count of unique users per week who unlocked a MR
+product_section: dev
+product_stage: create
+product_group: group::code review
+product_category: code_review
+value_type: number
+status: implemented
+milestone: "13.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069
+time_frame: 7d
+data_source: redis_hll
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_7d/20210302105318_i_code_review_user_mr_discussion_locked_weekly.yml b/config/metrics/counts_7d/20210302105318_i_code_review_user_mr_discussion_locked_weekly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2295fb75a48abc13e736cc5dfccbd3f1c6f378c6
--- /dev/null
+++ b/config/metrics/counts_7d/20210302105318_i_code_review_user_mr_discussion_locked_weekly.yml
@@ -0,0 +1,20 @@
+---
+key_path: redis_hll_counters.code_review.i_code_review_user_mr_discussion_locked_weekly
+description: Count of unique users per week who locked a MR
+product_section: dev
+product_stage: create
+product_group: group::code review
+product_category: code_review
+value_type: number
+status: implemented
+milestone: "13.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069
+time_frame: 7d
+data_source: redis_hll
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/doc/api/projects.md b/doc/api/projects.md
index b8687314697250d513d6442b6b6f81c69cf18662..da883d376b07e6f5fea56cec80a5fd9653f61e7d 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -179,6 +179,7 @@ When the user is authenticated and `simple` is not set this returns something li
       "packages_size": 0,
       "snippets_size": 0
     },
+    "container_registry_image_prefix": "registry.example.com/diaspora/diaspora-client",
     "_links": {
       "self": "http://example.com/api/v4/projects",
       "issues": "http://example.com/api/v4/projects/1/issues",
@@ -284,6 +285,7 @@ When the user is authenticated and `simple` is not set this returns something li
       "packages_size": 0,
       "snippets_size": 0
     },
+    "container_registry_image_prefix": "registry.example.com/brightbox/puppet",
     "_links": {
       "self": "http://example.com/api/v4/projects",
       "issues": "http://example.com/api/v4/projects/1/issues",
@@ -439,6 +441,7 @@ GET /users/:user_id/projects
       "packages_size": 0,
       "snippets_size": 0
     },
+    "container_registry_image_prefix": "registry.example.com/diaspora/diaspora-client",
     "_links": {
       "self": "http://example.com/api/v4/projects",
       "issues": "http://example.com/api/v4/projects/1/issues",
@@ -544,6 +547,7 @@ GET /users/:user_id/projects
       "packages_size": 0,
       "snippets_size": 0
     },
+    "container_registry_image_prefix": "registry.example.com/brightbox/puppet",
     "_links": {
       "self": "http://example.com/api/v4/projects",
       "issues": "http://example.com/api/v4/projects/1/issues",
@@ -658,6 +662,7 @@ Example response:
       "lfs_objects_size": 0,
       "job_artifacts_size": 0
     },
+    "container_registry_image_prefix": "registry.example.com/diaspora/diaspora-client",
     "_links": {
       "self": "http://example.com/api/v4/projects",
       "issues": "http://example.com/api/v4/projects/1/issues",
@@ -758,6 +763,7 @@ Example response:
       "lfs_objects_size": 0,
       "job_artifacts_size": 0
     },
+    "container_registry_image_prefix": "registry.example.com/brightbox/puppet",
     "_links": {
       "self": "http://example.com/api/v4/projects",
       "issues": "http://example.com/api/v4/projects/1/issues",
@@ -921,6 +927,7 @@ GET /projects/:id
     "packages_size": 0,
     "snippets_size": 0
   },
+  "container_registry_image_prefix": "registry.example.com/diaspora/diaspora-client",
   "_links": {
     "self": "http://example.com/api/v4/projects",
     "issues": "http://example.com/api/v4/projects/1/issues",
@@ -1373,6 +1380,7 @@ Example responses:
     "merge_method": "merge",
     "autoclose_referenced_issues": true,
     "suggestion_commit_message": null,
+    "container_registry_image_prefix": "registry.example.com/diaspora/diaspora-project-site",
     "_links": {
       "self": "http://example.com/api/v4/projects",
       "issues": "http://example.com/api/v4/projects/1/issues",
@@ -1467,6 +1475,7 @@ Example response:
   "merge_method": "merge",
   "autoclose_referenced_issues": true,
   "suggestion_commit_message": null,
+  "container_registry_image_prefix": "registry.example.com/diaspora/diaspora-project-site",
   "_links": {
     "self": "http://example.com/api/v4/projects",
     "issues": "http://example.com/api/v4/projects/1/issues",
@@ -1559,6 +1568,7 @@ Example response:
   "merge_method": "merge",
   "autoclose_referenced_issues": true,
   "suggestion_commit_message": null,
+  "container_registry_image_prefix": "registry.example.com/diaspora/diaspora-project-site",
   "_links": {
     "self": "http://example.com/api/v4/projects",
     "issues": "http://example.com/api/v4/projects/1/issues",
@@ -1745,6 +1755,7 @@ Example response:
   "merge_method": "merge",
   "autoclose_referenced_issues": true,
   "suggestion_commit_message": null,
+  "container_registry_image_prefix": "registry.example.com/diaspora/diaspora-project-site",
   "_links": {
     "self": "http://example.com/api/v4/projects",
     "issues": "http://example.com/api/v4/projects/1/issues",
@@ -1858,6 +1869,7 @@ Example response:
   "merge_method": "merge",
   "autoclose_referenced_issues": true,
   "suggestion_commit_message": null,
+  "container_registry_image_prefix": "registry.example.com/diaspora/diaspora-project-site",
   "_links": {
     "self": "http://example.com/api/v4/projects",
     "issues": "http://example.com/api/v4/projects/1/issues",
@@ -2354,6 +2366,7 @@ Example response:
     "avatar_url": null,
     "web_url": "https://gitlab.example.com/groups/cute-cats"
   },
+  "container_registry_image_prefix": "registry.example.com/cute-cats/hello-world",
   "_links": {
     "self": "https://gitlab.example.com/api/v4/projects/7",
     "issues": "https://gitlab.example.com/api/v4/projects/7/issues",
diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md
index 512f31903c8bf592f68572805cc592911b4dc94c..93ce26fceb535a8399eb39abbcfc808782453588 100644
--- a/doc/development/usage_ping/dictionary.md
+++ b/doc/development/usage_ping/dictionary.md
@@ -13384,6 +13384,86 @@ Count of unique users per week|month who merged a MR
 | `tier` |  |
 | `skip_validation` | true |
 
+## `redis_hll_counters.code_review.i_code_review_user_mr_discussion_locked_monthly`
+
+Count of unique users per month who locked a MR
+
+| field | value |
+| --- | --- |
+| `key_path` | **`redis_hll_counters.code_review.i_code_review_user_mr_discussion_locked_monthly`** |
+| `product_section` | dev |
+| `product_stage` | create |
+| `product_group` | `group::code review` |
+| `product_category` | `code_review` |
+| `value_type` | number |
+| `status` | implemented |
+| `milestone` | 13.10 |
+| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069) |
+| `time_frame` | 28d |
+| `data_source` | Redis_hll |
+| `distribution` | ce, ee |
+| `tier` | free, premium, ultimate |
+
+## `redis_hll_counters.code_review.i_code_review_user_mr_discussion_locked_weekly`
+
+Count of unique users per week who locked a MR
+
+| field | value |
+| --- | --- |
+| `key_path` | **`redis_hll_counters.code_review.i_code_review_user_mr_discussion_locked_weekly`** |
+| `product_section` | dev |
+| `product_stage` | create |
+| `product_group` | `group::code review` |
+| `product_category` | `code_review` |
+| `value_type` | number |
+| `status` | implemented |
+| `milestone` | 13.10 |
+| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069) |
+| `time_frame` | 7d |
+| `data_source` | Redis_hll |
+| `distribution` | ce, ee |
+| `tier` | free, premium, ultimate |
+
+## `redis_hll_counters.code_review.i_code_review_user_mr_discussion_unlocked_monthly`
+
+Count of unique users per month who unlocked a MR
+
+| field | value |
+| --- | --- |
+| `key_path` | **`redis_hll_counters.code_review.i_code_review_user_mr_discussion_unlocked_monthly`** |
+| `product_section` | dev |
+| `product_stage` | create |
+| `product_group` | `group::code review` |
+| `product_category` | `code_review` |
+| `value_type` | number |
+| `status` | implemented |
+| `milestone` | 13.10 |
+| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069) |
+| `time_frame` | 28d |
+| `data_source` | Redis_hll |
+| `distribution` | ce, ee |
+| `tier` | free, premium, ultimate |
+
+## `redis_hll_counters.code_review.i_code_review_user_mr_discussion_unlocked_weekly`
+
+Count of unique users per week who unlocked a MR
+
+| field | value |
+| --- | --- |
+| `key_path` | **`redis_hll_counters.code_review.i_code_review_user_mr_discussion_unlocked_weekly`** |
+| `product_section` | dev |
+| `product_stage` | create |
+| `product_group` | `group::code review` |
+| `product_category` | `code_review` |
+| `value_type` | number |
+| `status` | implemented |
+| `milestone` | 13.10 |
+| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55069) |
+| `time_frame` | 7d |
+| `data_source` | Redis_hll |
+| `distribution` | ce, ee |
+| `tier` | free, premium, ultimate |
+
 ## `redis_hll_counters.code_review.i_code_review_user_publish_review_monthly`
 
 Missing description
diff --git a/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/add_edit_rotation_form.vue b/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/add_edit_rotation_form.vue
index 51bcb5cfcd5492d2deae8e89c6b3e40e13cc0b39..3954184de9cb2bee69a4f85f6ee4b6e577627c3d 100644
--- a/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/add_edit_rotation_form.vue
+++ b/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/add_edit_rotation_form.vue
@@ -40,9 +40,10 @@ export const i18n = {
       title: __('Starts on'),
       error: s__('OnCallSchedules|Rotation start date cannot be empty'),
     },
-    endsOn: {
+    endsAt: {
       enableToggle: s__('OnCallSchedules|Enable end date'),
       title: __('Ends on'),
+      error: s__('OnCallSchedules|Rotation end date/time must come after start date/time'),
     },
     restrictToTime: {
       enableToggle: s__('OnCallSchedules|Restrict to time intervals'),
@@ -234,7 +235,7 @@ export default {
     <div class="gl-display-inline-block">
       <gl-toggle
         v-model="endDateEnabled"
-        :label="$options.i18n.fields.endsOn.enableToggle"
+        :label="$options.i18n.fields.endsAt.enableToggle"
         label-position="left"
         class="gl-mb-5"
       />
@@ -245,28 +246,43 @@ export default {
         class="gl-border-gray-400 gl-bg-gray-10"
       >
         <gl-form-group
-          :label="$options.i18n.fields.endsOn.title"
+          :label="$options.i18n.fields.endsAt.title"
           label-size="sm"
-          :invalid-feedback="$options.i18n.fields.endsOn.error"
+          :state="validationState.endsAt"
+          :invalid-feedback="$options.i18n.fields.endsAt.error"
           class="gl-mb-0"
         >
           <div class="gl-display-flex gl-align-items-center">
             <gl-datepicker
               class="gl-mr-3"
-              @input="$emit('update-rotation-form', { type: 'endsOn.date', value: $event })"
-            />
+              @input="$emit('update-rotation-form', { type: 'endsAt.date', value: $event })"
+            >
+              <template #default="{ formattedDate }">
+                <gl-form-input
+                  class="gl-w-full"
+                  :value="formattedDate"
+                  :placeholder="__(`YYYY-MM-DD`)"
+                  @blur="
+                    $emit('update-rotation-form', {
+                      type: 'endsAt.date',
+                      value: $event.target.value,
+                    })
+                  "
+                />
+              </template>
+            </gl-datepicker>
             <span> {{ __('at') }} </span>
             <gl-dropdown
               data-testid="rotation-end-time"
-              :text="format24HourTimeStringFromInt(form.endsOn.time)"
+              :text="format24HourTimeStringFromInt(form.endsAt.time)"
               class="gl-px-3"
             >
               <gl-dropdown-item
                 v-for="time in $options.HOURS_IN_DAY"
                 :key="time"
-                :is-checked="form.endsOn.time === time"
+                :is-checked="form.endsAt.time === time"
                 is-check-item
-                @click="$emit('update-rotation-form', { type: 'endsOn.time', value: time })"
+                @click="$emit('update-rotation-form', { type: 'endsAt.time', value: time })"
               >
                 <span class="gl-white-space-nowrap">
                   {{ format24HourTimeStringFromInt(time) }}</span
@@ -294,7 +310,7 @@ export default {
         <gl-form-group
           :label="$options.i18n.fields.restrictToTime.title"
           label-size="sm"
-          :invalid-feedback="$options.i18n.fields.endsOn.error"
+          :invalid-feedback="$options.i18n.fields.endsAt.error"
           class="gl-mb-0"
         >
           <div class="gl-display-flex gl-align-items-center">
diff --git a/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/add_edit_rotation_modal.vue b/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/add_edit_rotation_modal.vue
index 4b01a6f41838f0ea0f918bee2de11a5572e8de4f..f7672bbfda992394904f5d8e486de6eba91dad2f 100644
--- a/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/add_edit_rotation_modal.vue
+++ b/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/add_edit_rotation_modal.vue
@@ -78,7 +78,7 @@ export default {
           date: null,
           time: 0,
         },
-        endsOn: {
+        endsAt: {
           date: null,
           time: 0,
         },
@@ -92,6 +92,7 @@ export default {
         name: true,
         participants: true,
         startsAt: true,
+        endsAt: true,
       },
     };
   },
@@ -129,7 +130,8 @@ export default {
         name,
         rotationLength,
         participants,
-        startsAt: { date, time },
+        startsAt: { date: startDate, time: startTime },
+        endsAt: { date: endDate, time: endTime },
       } = this.form;
 
       return {
@@ -137,9 +139,15 @@ export default {
         scheduleIid: this.schedule.iid,
         name,
         startsAt: {
-          date: formatDate(date, 'yyyy-mm-dd'),
-          time: format24HourTimeStringFromInt(time),
+          date: formatDate(startDate, 'yyyy-mm-dd'),
+          time: format24HourTimeStringFromInt(startTime),
         },
+        endsAt: endDate
+          ? {
+              date: formatDate(endDate, 'yyyy-mm-dd'),
+              time: format24HourTimeStringFromInt(endTime),
+            }
+          : null,
         rotationLength: {
           ...rotationLength,
           length: parseInt(rotationLength.length, 10),
@@ -150,6 +158,20 @@ export default {
     title() {
       return this.isEditMode ? this.$options.i18n.editRotation : this.$options.i18n.addRotation;
     },
+    isEndDateValid() {
+      const startsAt = this.form.startsAt.date?.getTime();
+      const endsAt = this.form.endsAt.date?.getTime();
+
+      if (!startsAt || !endsAt) {
+        // If start or end is not present, we consider the end date valid
+        return true;
+      } else if (startsAt < endsAt) {
+        return true;
+      } else if (startsAt === endsAt) {
+        return this.form.startsAt.time < this.form.endsAt.time;
+      }
+      return false;
+    },
   },
   methods: {
     createRotation() {
@@ -244,8 +266,11 @@ export default {
         this.validationState.name = isNameFieldValid(this.form.name);
       } else if (key === 'participants') {
         this.validationState.participants = this.form.participants.length > 0;
-      } else if (key === 'startsAt.date') {
+      } else if (key === 'startsAt.date' || key === 'startsAt.time') {
         this.validationState.startsAt = Boolean(this.form.startsAt.date);
+        this.validationState.endsAt = this.isEndDateValid;
+      } else if (key === 'endsAt.date' || key === 'endsAt.time') {
+        this.validationState.endsAt = this.isEndDateValid;
       }
     },
   },
diff --git a/ee/app/assets/javascripts/oncall_schedules/graphql/fragments/oncall_schedule_rotation.fragment.graphql b/ee/app/assets/javascripts/oncall_schedules/graphql/fragments/oncall_schedule_rotation.fragment.graphql
index b762fd6c401d56882ae910f329add962df4b83a6..cfc34635eea1162fb9d246add4f64c46704ec02b 100644
--- a/ee/app/assets/javascripts/oncall_schedules/graphql/fragments/oncall_schedule_rotation.fragment.graphql
+++ b/ee/app/assets/javascripts/oncall_schedules/graphql/fragments/oncall_schedule_rotation.fragment.graphql
@@ -4,6 +4,7 @@ fragment OnCallRotation on IncidentManagementOncallRotation {
   id
   name
   startsAt
+  endsAt
   length
   lengthUnit
   participants {
diff --git a/ee/app/assets/javascripts/security_configuration/components/app.vue b/ee/app/assets/javascripts/security_configuration/components/app.vue
index e5109053691abf5c0288357d5a81389167db8a91..96ff21892b5b6e8c661831be66e72305f3686b6a 100644
--- a/ee/app/assets/javascripts/security_configuration/components/app.vue
+++ b/ee/app/assets/javascripts/security_configuration/components/app.vue
@@ -171,7 +171,13 @@ export default {
       </gl-sprintf>
     </gl-alert>
 
-    <gl-table ref="securityControlTable" :items="features" :fields="fields" stacked="md">
+    <gl-table
+      ref="securityControlTable"
+      :items="features"
+      :fields="fields"
+      stacked="md"
+      :tbody-tr-attr="{ 'data-testid': 'security-scanner-row' }"
+    >
       <template #cell(feature)="{ item }">
         <div class="gl-text-gray-900">{{ item.name }}</div>
         <div>
diff --git a/ee/spec/features/projects/security/user_views_security_configuration_spec.rb b/ee/spec/features/projects/security/user_views_security_configuration_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f850fd731d3e173a1307abe7de0649920b76d14e
--- /dev/null
+++ b/ee/spec/features/projects/security/user_views_security_configuration_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User sees Security Configuration table', :js do
+  let_it_be(:user) { create(:user) }
+  let_it_be(:project) { create(:project, :repository) }
+
+  before_all do
+    project.add_developer(user)
+  end
+
+  before do
+    sign_in(user)
+  end
+
+  context 'with security_dashboard feature available' do
+    before do
+      stub_licensed_features(security_dashboard: true)
+    end
+
+    context 'with no SAST report' do
+      it 'shows SAST is not enabled' do
+        visit(project_security_configuration_path(project))
+
+        within_sast_row do
+          expect(page).to have_text('SAST')
+          expect(page).to have_text('Not enabled')
+          expect(page).to have_css('[data-testid="enableButton"]')
+        end
+      end
+    end
+
+    context 'with SAST report' do
+      before do
+        pipeline = create(:ci_pipeline, project: project)
+        create(:ci_build, :sast, pipeline: pipeline, status: 'success')
+      end
+
+      it 'shows SAST is enabled' do
+        visit(project_security_configuration_path(project))
+
+        within_sast_row do
+          expect(page).to have_text('SAST')
+          expect(page).to have_text('Enabled')
+          expect(page).to have_css('[data-testid="configureButton"]')
+        end
+      end
+    end
+  end
+
+  def within_sast_row
+    within '[data-testid="security-scanner-row"]:nth-of-type(1)' do
+      yield
+    end
+  end
+end
diff --git a/ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js b/ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js
index fa363ea81be218f266e3f3b0bbf07d6063723fca..3b62df980e5e8b25f9607f54058eba88a132f527 100644
--- a/ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js
+++ b/ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js
@@ -138,7 +138,8 @@ export const createRotationResponse = {
       oncallRotation: {
         id: '44',
         name: 'Test',
-        startsAt: '2020-12-17T12:00:00Z',
+        startsAt: '2020-12-20T12:00:00Z',
+        endsAt: '2021-03-17T12:00:00Z',
         length: 5,
         lengthUnit: 'WEEKS',
         participants: {
@@ -171,7 +172,8 @@ export const createRotationResponseWithErrors = {
       oncallRotation: {
         id: '44',
         name: 'Test',
-        startsAt: '2020-12-17T12:00:00Z',
+        startsAt: '2020-12-20T12:00:00Z',
+        endsAt: '2021-03-17T12:00:00Z',
         length: 5,
         lengthUnit: 'WEEKS',
         participants: {
diff --git a/ee/spec/frontend/oncall_schedule/mocks/mock_rotation.json b/ee/spec/frontend/oncall_schedule/mocks/mock_rotation.json
index 863835cad4568f66fde25d375d0bca33e676c7ce..00e2152f750703d68e93dfd24459479edc0bf1d5 100644
--- a/ee/spec/frontend/oncall_schedule/mocks/mock_rotation.json
+++ b/ee/spec/frontend/oncall_schedule/mocks/mock_rotation.json
@@ -2,6 +2,7 @@
     "id": "gid://gitlab/IncidentManagement::OncallRotation/2",
     "name": "Rotation 242",
     "startsAt": "2021-01-13T10:04:56.333Z",
+    "endsAt": "2021-03-13T10:04:56.333Z",
     "length": 1,
     "lengthUnit": "WEEKS",
     "participants": {
@@ -54,6 +55,7 @@
     "id": "gid://gitlab/IncidentManagement::OncallRotation/55",
     "name": "Rotation 242",
     "startsAt": "2021-01-13T10:04:56.333Z",
+    "endsAt": "2021-03-13T10:04:56.333Z",
     "length": 1,
     "lengthUnit": "WEEKS",
     "participants": {
@@ -102,6 +104,7 @@
     "id": "gid://gitlab/IncidentManagement::OncallRotation/3",
     "name": "Rotation 244",
     "startsAt": "2021-01-06T10:04:56.333Z",
+    "endsAt": "2021-01-10T10:04:56.333Z",
     "length": 1,
     "lengthUnit": "WEEKS",
     "participants": {
@@ -150,6 +153,7 @@
   "id": "gid://gitlab/IncidentManagement::OncallRotation/5",
   "name": "Rotation 247",
   "startsAt": "2021-01-06T10:04:56.333Z",
+  "endsAt": "2021-01-11T10:04:56.333Z",
   "length": 1,
   "lengthUnit": "WEEKS",
   "participants": {
diff --git a/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_form_spec.js b/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_form_spec.js
index e32b77a64a9fc162a86207dcb1c729a202d3f956..bf7b5150d6cee08c88d8ff269dd61d3b68a88a8e 100644
--- a/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_form_spec.js
+++ b/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_form_spec.js
@@ -40,7 +40,7 @@ describe('AddEditRotationForm', () => {
             date: null,
             time: 0,
           },
-          endsOn: {
+          endsAt: {
             date: null,
             time: 0,
           },
@@ -160,7 +160,7 @@ describe('AddEditRotationForm', () => {
       await wrapper.vm.$nextTick();
       const emittedEvent = wrapper.emitted('update-rotation-form');
       expect(emittedEvent).toHaveLength(1);
-      expect(emittedEvent[0][0]).toEqual({ type: 'endsOn.time', value: option + 1 });
+      expect(emittedEvent[0][0]).toEqual({ type: 'endsAt.time', value: option + 1 });
     });
 
     it('should add a checkmark to a selected end time', async () => {
@@ -168,7 +168,7 @@ describe('AddEditRotationForm', () => {
       const time = 5;
       wrapper.setProps({
         form: {
-          endsOn: {
+          endsAt: {
             time,
           },
           startsAt: {
@@ -221,7 +221,7 @@ describe('AddEditRotationForm', () => {
 
       wrapper.setProps({
         form: {
-          endsOn: {
+          endsAt: {
             time: 0,
           },
           startsAt: {
diff --git a/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_modal_spec.js b/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_modal_spec.js
index 7457244c32ca6a6d288b646fb55c54d034618d49..8de9aa5b22a205ed45a2837d02bcf4e0a6829b89 100644
--- a/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_modal_spec.js
+++ b/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_modal_spec.js
@@ -1,6 +1,7 @@
 import { GlModal, GlAlert } from '@gitlab/ui';
 import { shallowMount, createLocalVue } from '@vue/test-utils';
 import VueApollo from 'vue-apollo';
+import AddEditRotationForm from 'ee/oncall_schedules/components/rotations/components/add_edit_rotation_form.vue';
 import AddEditRotationModal, {
   i18n,
 } from 'ee/oncall_schedules/components/rotations/components/add_edit_rotation_modal.vue';
@@ -129,8 +130,9 @@ describe('AddEditRotationModal', () => {
     wrapper = null;
   });
 
-  const findModal = () => wrapper.find(GlModal);
-  const findAlert = () => wrapper.find(GlAlert);
+  const findModal = () => wrapper.findComponent(GlModal);
+  const findAlert = () => wrapper.findComponent(GlAlert);
+  const findForm = () => wrapper.findComponent(AddEditRotationForm);
 
   it('renders rotation modal layout', () => {
     expect(wrapper.element).toMatchSnapshot();
@@ -155,6 +157,149 @@ describe('AddEditRotationModal', () => {
       expect(findAlert().exists()).toBe(true);
       expect(findAlert().text()).toContain(error);
     });
+
+    describe('Validation', () => {
+      describe('name', () => {
+        it('is valid when name is NOT empty', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', { type: 'name', value: '' });
+          expect(form.props('validationState').name).toBe(false);
+        });
+
+        it('is NOT valid when name is empty', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', { type: 'name', value: 'Some value' });
+          expect(form.props('validationState').name).toBe(true);
+        });
+      });
+
+      describe('participants', () => {
+        it('is valid when participants array is NOT empty', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', {
+            type: 'participants',
+            value: ['user1', 'user2'],
+          });
+          expect(form.props('validationState').participants).toBe(true);
+        });
+
+        it('is NOT valid when participants array is empty', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', { type: 'participants', value: [] });
+          expect(form.props('validationState').participants).toBe(false);
+        });
+      });
+
+      describe('startsAt date', () => {
+        it('is valid when date is NOT empty', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', {
+            type: 'startsAt.date',
+            value: new Date('10/12/2021'),
+          });
+          expect(form.props('validationState').startsAt).toBe(true);
+        });
+
+        it('is NOT valid when date is empty', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', { type: 'startsAt.time', value: null });
+          expect(form.props('validationState').startsAt).toBe(false);
+        });
+      });
+
+      describe('endsAt date', () => {
+        it('is valid when date is empty', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', { type: 'endsAt.date', value: null });
+          expect(form.props('validationState').endsAt).toBe(true);
+        });
+
+        it('is valid when start date is smaller then end date', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', {
+            type: 'startsAt.date',
+            value: new Date('9/11/2021'),
+          });
+          form.vm.$emit('update-rotation-form', {
+            type: 'endsAt.date',
+            value: new Date('10/11/2021'),
+          });
+          expect(form.props('validationState').endsAt).toBe(true);
+        });
+
+        it('is invalid when start date is larger then end date', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', {
+            type: 'startsAt.date',
+            value: new Date('11/11/2021'),
+          });
+          form.vm.$emit('update-rotation-form', {
+            type: 'endsAt.date',
+            value: new Date('10/11/2021'),
+          });
+          expect(form.props('validationState').endsAt).toBe(false);
+        });
+
+        it('is valid when start and end dates are equal but time is smaller on start date', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', {
+            type: 'startsAt.date',
+            value: new Date('11/11/2021'),
+          });
+          form.vm.$emit('update-rotation-form', { type: 'startsAt.time', value: 10 });
+          form.vm.$emit('update-rotation-form', {
+            type: 'endsAt.date',
+            value: new Date('11/11/2021'),
+          });
+          form.vm.$emit('update-rotation-form', { type: 'endsAt.time', value: 22 });
+          expect(form.props('validationState').endsAt).toBe(true);
+        });
+
+        it('is invalid when start and end dates are equal but time is larger on start date', () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', {
+            type: 'startsAt.date',
+            value: new Date('11/11/2021'),
+          });
+          form.vm.$emit('update-rotation-form', { type: 'startsAt.time', value: 10 });
+          form.vm.$emit('update-rotation-form', {
+            type: 'endsAt.date',
+            value: new Date('11/11/2021'),
+          });
+          form.vm.$emit('update-rotation-form', { type: 'endsAt.time', value: 8 });
+          expect(form.props('validationState').endsAt).toBe(false);
+        });
+      });
+
+      describe('Toggle primary button state', () => {
+        it('should disable primary button when any of the fields is invalid', async () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', { type: 'name', value: 'lalal' });
+          await wrapper.vm.$nextTick();
+          expect(findModal().props('actionPrimary').attributes).toEqual(
+            expect.arrayContaining([{ disabled: true }]),
+          );
+        });
+
+        it('should enable primary button when all fields are valid', async () => {
+          const form = findForm();
+          form.vm.$emit('update-rotation-form', { type: 'name', value: 'Value' });
+          form.vm.$emit('update-rotation-form', { type: 'participants', value: [1, 2, 3] });
+          form.vm.$emit('update-rotation-form', {
+            type: 'startsAt.date',
+            value: new Date('11/10/2021'),
+          });
+          form.vm.$emit('update-rotation-form', {
+            type: 'endsAt.date',
+            value: new Date('12/10/2021'),
+          });
+          await wrapper.vm.$nextTick();
+          expect(findModal().props('actionPrimary').attributes).toEqual(
+            expect.arrayContaining([{ disabled: false }]),
+          );
+        });
+      });
+    });
   });
 
   describe('with mocked Apollo client', () => {
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index 6ad6123a20e389c876752483c189d83edc504329..e332e5e40fa15495183cc535224eb9907a2070cc 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -5,6 +5,8 @@ module Entities
     class Project < BasicProjectDetails
       include ::API::Helpers::RelatedResourcesHelpers
 
+      expose :container_registry_url, as: :container_registry_image_prefix, if: -> (_, _) { Gitlab.config.registry.enabled }
+
       expose :_links do
         expose :self do |project|
           expose_url(api_v4_projects_path(id: project.id))
diff --git a/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml b/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml
index 4f86d5001648885a46b11891d2bcac7d802e7788..c856d0a5eeebf732257b5bc1fff5b86f9939b6ce 100644
--- a/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml
+++ b/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml
@@ -42,7 +42,9 @@
     'i_code_review_user_approval_rule_edited',
     'i_code_review_user_vs_code_api_request',
     'i_code_review_user_toggled_task_item_status',
-    'i_code_review_user_create_mr_from_issue'
+    'i_code_review_user_create_mr_from_issue',
+    'i_code_review_user_mr_discussion_locked',
+    'i_code_review_user_mr_discussion_unlocked'
   ]
 - name: code_review_category_monthly_active_users
   operator: OR
@@ -78,7 +80,9 @@
     'i_code_review_user_approval_rule_deleted',
     'i_code_review_user_approval_rule_edited',
     'i_code_review_user_toggled_task_item_status',
-    'i_code_review_user_create_mr_from_issue'
+    'i_code_review_user_create_mr_from_issue',
+    'i_code_review_user_mr_discussion_locked',
+    'i_code_review_user_mr_discussion_unlocked'
   ]
 - name: code_review_extension_category_monthly_active_users
   operator: OR
diff --git a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
index d657c5487d79d4ad4dd481a4854c4979d8a9ce72..21613740142ce2edb2671d22c1e6ceb90d0cdd63 100644
--- a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
@@ -164,3 +164,13 @@
   category: code_review
   aggregation: weekly
   feature_flag: usage_data_i_code_review_user_create_mr_from_issue
+- name: i_code_review_user_mr_discussion_locked
+  redis_slot: code_review
+  category: code_review
+  aggregation: weekly
+  feature_flag: usage_data_i_code_review_user_mr_discussion_locked
+- name: i_code_review_user_mr_discussion_unlocked
+  redis_slot: code_review
+  category: code_review
+  aggregation: weekly
+  feature_flag: usage_data_i_code_review_user_mr_discussion_unlocked
diff --git a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
index b9856e1f74ad9015c673001738d88900f547f42c..b94caa32bf7f55d738944c008c5d84cdcbb9eac7 100644
--- a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
@@ -35,6 +35,8 @@ module MergeRequestActivityUniqueCounter
       MR_EDIT_MR_TITLE_ACTION = 'i_code_review_edit_mr_title'
       MR_EDIT_MR_DESC_ACTION = 'i_code_review_edit_mr_desc'
       MR_CREATE_FROM_ISSUE_ACTION = 'i_code_review_user_create_mr_from_issue'
+      MR_DISCUSSION_LOCKED_ACTION = 'i_code_review_user_mr_discussion_locked'
+      MR_DISCUSSION_UNLOCKED_ACTION = 'i_code_review_user_mr_discussion_unlocked'
 
       class << self
         def track_mr_diffs_action(merge_request:)
@@ -153,6 +155,14 @@ def track_mr_create_from_issue(user:)
           track_unique_action_by_user(MR_CREATE_FROM_ISSUE_ACTION, user)
         end
 
+        def track_discussion_locked_action(user:)
+          track_unique_action_by_user(MR_DISCUSSION_LOCKED_ACTION, user)
+        end
+
+        def track_discussion_unlocked_action(user:)
+          track_unique_action_by_user(MR_DISCUSSION_UNLOCKED_ACTION, user)
+        end
+
         private
 
         def track_unique_action_by_merge_request(action, merge_request)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4c4f25cf7a41aff643153d685459a68be2fe4956..d8e05fd9bafe5b0e00f7b02178ca6bf1e7f8bb28 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -21028,6 +21028,9 @@ msgstr ""
 msgid "OnCallSchedules|Restrict to time intervals"
 msgstr ""
 
+msgid "OnCallSchedules|Rotation end date/time must come after start date/time"
+msgstr ""
+
 msgid "OnCallSchedules|Rotation length"
 msgstr ""
 
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
index a604de4a61fe202c23087b5598131718c38de916..6bc4243088988364a288ba533e5fdf96a8c29ab9 100644
--- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
@@ -284,4 +284,20 @@
       let(:action) { described_class::MR_CREATE_FROM_ISSUE_ACTION }
     end
   end
+
+  describe '.track_discussion_locked_action' do
+    subject { described_class.track_discussion_locked_action(user: user) }
+
+    it_behaves_like 'a tracked merge request unique event' do
+      let(:action) { described_class::MR_DISCUSSION_LOCKED_ACTION }
+    end
+  end
+
+  describe '.track_discussion_unlocked_action' do
+    subject { described_class.track_discussion_unlocked_action(user: user) }
+
+    it_behaves_like 'a tracked merge request unique event' do
+      let(:action) { described_class::MR_DISCUSSION_UNLOCKED_ACTION }
+    end
+  end
 end
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index 181fcafd57725828e25e53c37f5608c5786cdf6c..104918810f8a81858568e6a5c95d4b5a21e766fe 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -56,6 +56,7 @@ itself: # project
     - can_create_merge_request_in
     - compliance_frameworks
     - container_expiration_policy
+    - container_registry_image_prefix
     - default_branch
     - empty_repo
     - forks_count
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 8a68db6ae4b9cb7d4ec650afb43a059fc5a6cf88..19cab51ef3414f7b809339fe65344a7921448d91 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1540,6 +1540,10 @@
     end
 
     context 'when authenticated as an admin' do
+      before do
+        stub_container_registry_config(enabled: true, host_port: 'registry.example.org:5000')
+      end
+
       let(:project_attributes_file) { 'spec/requests/api/project_attributes.yml' }
       let(:project_attributes) { YAML.load_file(project_attributes_file) }
 
@@ -1569,7 +1573,7 @@
         keys
       end
 
-      it 'returns a project by id' do
+      it 'returns a project by id', :aggregate_failures do
         project
         project_member
         group = create(:group)
@@ -1587,6 +1591,7 @@
         expect(json_response['ssh_url_to_repo']).to be_present
         expect(json_response['http_url_to_repo']).to be_present
         expect(json_response['web_url']).to be_present
+        expect(json_response['container_registry_image_prefix']).to eq("registry.example.org:5000/#{project.full_path}")
         expect(json_response['owner']).to be_a Hash
         expect(json_response['name']).to eq(project.name)
         expect(json_response['path']).to be_present
@@ -1644,9 +1649,10 @@ def failure_message(diff)
       before do
         project
         project_member
+        stub_container_registry_config(enabled: true, host_port: 'registry.example.org:5000')
       end
 
-      it 'returns a project by id' do
+      it 'returns a project by id', :aggregate_failures do
         group = create(:group)
         link = create(:project_group_link, project: project, group: group)
 
@@ -1662,6 +1668,7 @@ def failure_message(diff)
         expect(json_response['ssh_url_to_repo']).to be_present
         expect(json_response['http_url_to_repo']).to be_present
         expect(json_response['web_url']).to be_present
+        expect(json_response['container_registry_image_prefix']).to eq("registry.example.org:5000/#{project.full_path}")
         expect(json_response['owner']).to be_a Hash
         expect(json_response['name']).to eq(project.name)
         expect(json_response['path']).to be_present
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index a6bd8416e58414a002ad758aa6e15926c419e269..e9ec3bccda3fd1a0a71754fe641b5d2d57042f34 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -48,6 +48,8 @@ def update_merge_request(opts)
     end
 
     context 'valid params' do
+      let(:locked) { true }
+
       let(:opts) do
         {
           title: 'New title',
@@ -58,7 +60,7 @@ def update_merge_request(opts)
           label_ids: [label.id],
           target_branch: 'target',
           force_remove_source_branch: '1',
-          discussion_locked: true
+          discussion_locked: locked
         }
       end
 
@@ -117,6 +119,56 @@ def update_merge_request(opts)
 
           MergeRequests::UpdateService.new(project, user, opts).execute(draft_merge_request)
         end
+
+        context 'when MR is locked' do
+          context 'when locked again' do
+            it 'does not track discussion locking' do
+              expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+                .not_to receive(:track_discussion_locked_action)
+
+              opts[:discussion_locked] = true
+
+              MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+            end
+          end
+
+          context 'when unlocked' do
+            it 'tracks dicussion unlocking' do
+              expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+                .to receive(:track_discussion_unlocked_action).once.with(user: user)
+
+              opts[:discussion_locked] = false
+
+              MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+            end
+          end
+        end
+
+        context 'when MR is unlocked' do
+          let(:locked) { false }
+
+          context 'when unlocked again' do
+            it 'does not track discussion unlocking' do
+              expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+                .not_to receive(:track_discussion_unlocked_action)
+
+              opts[:discussion_locked] = false
+
+              MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+            end
+          end
+
+          context 'when locked' do
+            it 'tracks dicussion locking' do
+              expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+                .to receive(:track_discussion_locked_action).once.with(user: user)
+
+              opts[:discussion_locked] = true
+
+              MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+            end
+          end
+        end
       end
 
       context 'updating milestone' do