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 0f842a4777a6b1980f702f7efad22a11afed79a8..d802c4260d65d0c447ca88fc31984e60c22a94e5 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 1f2b0f98c5d823f6c26bb8bcc5b800c24b1ffe03..a63e53a258b63926a54825fab1df38ae705f5df1 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 b866db0d330f24c5e77e2e20a26039d0c13fecf9..21bf4869ac8634439497fda934dff04c209979eb 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 e48c58590b63fa45f2532d7799c215a654376c0f..73e2eda046064cad58128212c928867f3a3e1fdb 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 a6619d57dfea8749db70379890a1afd7d3a38327..11f3372a46a15a6d2456a86b0dd9f7dc9de0f8bb 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 6c71d7903383175ac0370480a98c69dfa4033ab1..9b758b2793f3c1aea6c87b8f7492f2557bf524c5 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 edeee4a3bad4c209cf46d8eb50a4b90d62ef7d86..4273981e619b8fea3d6584927a4b82e022b87937 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])); + }); + }); });