From 6fd6b6260848bb639b710b4eee87cf75373367a4 Mon Sep 17 00:00:00 2001
From: Chaitanya Sonwane <chaitanyadsonwane2002@gmail.com>
Date: Fri, 7 Mar 2025 02:49:02 +0530
Subject: [PATCH] Add created and closed date filters to work items

---
 .../get_work_item_state_counts.query.graphql  | 12 +++++++++
 .../graphql/list/get_work_items.query.graphql | 12 +++++++++
 app/assets/javascripts/work_items/index.js    |  2 ++
 .../work_items/pages/work_items_list_app.vue  | 27 +++++++++++++++++++
 app/helpers/work_items_helper.rb              |  5 +++-
 .../components/work_items_list_app_spec.js    |  1 +
 .../components/work_items_list_app_spec.js    | 16 +++++++++++
 7 files changed, 74 insertions(+), 1 deletion(-)

diff --git a/app/assets/javascripts/work_items/graphql/list/get_work_item_state_counts.query.graphql b/app/assets/javascripts/work_items/graphql/list/get_work_item_state_counts.query.graphql
index 0f842a4777a6..d802c4260d65 100644
--- a/app/assets/javascripts/work_items/graphql/list/get_work_item_state_counts.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/list/get_work_item_state_counts.query.graphql
@@ -18,6 +18,10 @@ query getWorkItemStateCounts(
   $in: [IssuableSearchableField!]
   $not: NegatedWorkItemFilterInput
   $or: UnionedWorkItemFilterInput
+  $createdAfter: Time
+  $createdBefore: Time
+  $closedAfter: Time
+  $closedBefore: Time
 ) {
   group(fullPath: $fullPath) @include(if: $isGroup) {
     id
@@ -39,6 +43,10 @@ query getWorkItemStateCounts(
       in: $in
       not: $not
       or: $or
+      createdAfter: $createdAfter
+      createdBefore: $createdBefore
+      closedAfter: $closedAfter
+      closedBefore: $closedBefore
     ) {
       all
       closed
@@ -63,6 +71,10 @@ query getWorkItemStateCounts(
       in: $in
       not: $not
       or: $or
+      createdAfter: $createdAfter
+      createdBefore: $createdBefore
+      closedAfter: $closedAfter
+      closedBefore: $closedBefore
     ) {
       all
       closed
diff --git a/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql b/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql
index 1f2b0f98c5d8..a63e53a258b6 100644
--- a/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql
@@ -25,6 +25,10 @@ query getWorkItems(
   $beforeCursor: String
   $firstPageSize: Int
   $lastPageSize: Int
+  $createdAfter: Time
+  $createdBefore: Time
+  $closedAfter: Time
+  $closedBefore: Time
 ) {
   group(fullPath: $fullPath) @include(if: $isGroup) {
     id
@@ -51,6 +55,10 @@ query getWorkItems(
       before: $beforeCursor
       first: $firstPageSize
       last: $lastPageSize
+      createdAfter: $createdAfter
+      createdBefore: $createdBefore
+      closedAfter: $closedAfter
+      closedBefore: $closedBefore
     ) {
       pageInfo {
         ...PageInfo
@@ -113,6 +121,10 @@ query getWorkItems(
       before: $beforeCursor
       first: $firstPageSize
       last: $lastPageSize
+      createdAfter: $createdAfter
+      createdBefore: $createdBefore
+      closedAfter: $closedAfter
+      closedBefore: $closedBefore
     ) {
       pageInfo {
         ...PageInfo
diff --git a/app/assets/javascripts/work_items/index.js b/app/assets/javascripts/work_items/index.js
index b866db0d330f..21bf4869ac86 100644
--- a/app/assets/javascripts/work_items/index.js
+++ b/app/assets/javascripts/work_items/index.js
@@ -57,6 +57,7 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType, withTabs } = {}
     hasLinkedItemsEpicsFeature,
     canCreateProjects,
     newProjectPath,
+    hasIssueDateFilterFeature,
   } = el.dataset;
 
   const isGroup = workspaceType === WORKSPACE_GROUP;
@@ -131,6 +132,7 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType, withTabs } = {}
       canCreateProjects: parseBoolean(canCreateProjects),
       newIssuePath: '',
       newProjectPath,
+      hasIssueDateFilterFeature: parseBoolean(hasIssueDateFilterFeature),
     },
     mounted() {
       performanceMarkAndMeasure({
diff --git a/app/assets/javascripts/work_items/pages/work_items_list_app.vue b/app/assets/javascripts/work_items/pages/work_items_list_app.vue
index e48c58590b63..73e2eda04606 100644
--- a/app/assets/javascripts/work_items/pages/work_items_list_app.vue
+++ b/app/assets/javascripts/work_items/pages/work_items_list_app.vue
@@ -44,9 +44,12 @@ import {
   OPERATOR_IS,
   OPERATORS_IS,
   OPERATORS_IS_NOT_OR,
+  OPERATORS_AFTER_BEFORE,
   TOKEN_TITLE_ASSIGNEE,
   TOKEN_TITLE_AUTHOR,
+  TOKEN_TITLE_CLOSED,
   TOKEN_TITLE_CONFIDENTIAL,
+  TOKEN_TITLE_CREATED,
   TOKEN_TITLE_GROUP,
   TOKEN_TITLE_LABEL,
   TOKEN_TITLE_MILESTONE,
@@ -56,7 +59,9 @@ import {
   TOKEN_TITLE_STATE,
   TOKEN_TYPE_ASSIGNEE,
   TOKEN_TYPE_AUTHOR,
+  TOKEN_TYPE_CLOSED,
   TOKEN_TYPE_CONFIDENTIAL,
+  TOKEN_TYPE_CREATED,
   TOKEN_TYPE_GROUP,
   TOKEN_TYPE_LABEL,
   TOKEN_TYPE_MILESTONE,
@@ -69,6 +74,7 @@ import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_ro
 import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue';
 import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants';
 import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import DateToken from '~/vue_shared/components/filtered_search_bar/tokens/date_token.vue';
 import { getParameterByName, removeParams, updateHistory } from '~/lib/utils/url_utility';
 import {
   STATE_CLOSED,
@@ -117,6 +123,7 @@ export default {
     'isGroup',
     'isSignedIn',
     'workItemType',
+    'hasIssueDateFilterFeature',
   ],
   props: {
     eeWorkItemUpdateCount: {
@@ -408,6 +415,26 @@ export default {
         });
       }
 
+      if (this.hasIssueDateFilterFeature) {
+        tokens.push({
+          type: TOKEN_TYPE_CLOSED,
+          title: TOKEN_TITLE_CLOSED,
+          icon: 'history',
+          unique: true,
+          token: DateToken,
+          operators: OPERATORS_AFTER_BEFORE,
+        });
+
+        tokens.push({
+          type: TOKEN_TYPE_CREATED,
+          title: TOKEN_TITLE_CREATED,
+          icon: 'history',
+          unique: true,
+          token: DateToken,
+          operators: OPERATORS_AFTER_BEFORE,
+        });
+      }
+
       if (this.eeSearchTokens.length) {
         tokens.push(...this.eeSearchTokens);
       }
diff --git a/app/helpers/work_items_helper.rb b/app/helpers/work_items_helper.rb
index a6619d57dfea..11f3372a46a1 100644
--- a/app/helpers/work_items_helper.rb
+++ b/app/helpers/work_items_helper.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 module WorkItemsHelper
+  include IssuesHelper
+
   def work_items_show_data(resource_parent, current_user)
     group = resource_parent.is_a?(Group) ? resource_parent : resource_parent.group
 
@@ -23,7 +25,8 @@ def work_items_show_data(resource_parent, current_user)
       show_new_issue_link: can?(current_user, :create_work_item, group).to_s,
       can_create_projects: can?(current_user, :create_projects, group).to_s,
       new_project_path: new_project_path(namespace_id: group&.id),
-      group_id: group&.id
+      group_id: group&.id,
+      has_issue_date_filter_feature: has_issue_date_filter_feature?(resource_parent, current_user).to_s
     }
   end
 
diff --git a/ee/spec/frontend/work_items/list/components/work_items_list_app_spec.js b/ee/spec/frontend/work_items/list/components/work_items_list_app_spec.js
index 6c71d7903383..9b758b2793f3 100644
--- a/ee/spec/frontend/work_items/list/components/work_items_list_app_spec.js
+++ b/ee/spec/frontend/work_items/list/components/work_items_list_app_spec.js
@@ -56,6 +56,7 @@ describeSkipVue3(skipReason, () => {
     hasOkrsFeature: true,
     hasQualityManagementFeature: true,
     hasIssuableHealthStatusFeature: true,
+    hasIssueDateFilterFeature: false,
   };
 
   const mountComponent = ({
diff --git a/spec/frontend/work_items/list/components/work_items_list_app_spec.js b/spec/frontend/work_items/list/components/work_items_list_app_spec.js
index edeee4a3bad4..4273981e619b 100644
--- a/spec/frontend/work_items/list/components/work_items_list_app_spec.js
+++ b/spec/frontend/work_items/list/components/work_items_list_app_spec.js
@@ -28,7 +28,9 @@ import {
   OPERATOR_IS,
   TOKEN_TYPE_ASSIGNEE,
   TOKEN_TYPE_AUTHOR,
+  TOKEN_TYPE_CLOSED,
   TOKEN_TYPE_CONFIDENTIAL,
+  TOKEN_TYPE_CREATED,
   TOKEN_TYPE_GROUP,
   TOKEN_TYPE_LABEL,
   TOKEN_TYPE_MILESTONE,
@@ -116,6 +118,7 @@ describeSkipVue3(skipReason, () => {
         isGroup: true,
         isSignedIn: true,
         workItemType: null,
+        hasIssueDateFilterFeature: false,
         ...provide,
       },
       propsData: {
@@ -792,4 +795,17 @@ describeSkipVue3(skipReason, () => {
       });
     });
   });
+
+  describe('when issue_date_filter is enabled', () => {
+    it('includes created and closed date in searchTokens', async () => {
+      mountComponent({ provide: { hasIssueDateFilterFeature: true } });
+      await waitForPromises();
+
+      const tokenTypes = findIssuableList()
+        .props('searchTokens')
+        .map((token) => token.type);
+
+      expect(tokenTypes).toEqual(expect.arrayContaining([TOKEN_TYPE_CLOSED, TOKEN_TYPE_CREATED]));
+    });
+  });
 });
-- 
GitLab