Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
63a83b9b
Commit
63a83b9b
authored
May 27, 2021
by
Simon Knox
Committed by
Olena Horal-Koretska
May 27, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Show iterations for cadence
parent
11a4bfc7
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
512 additions
and
35 deletions
+512
-35
ee/app/assets/javascripts/iterations/components/iteration_cadence.vue
...s/javascripts/iterations/components/iteration_cadence.vue
+0
-16
ee/app/assets/javascripts/iterations/components/iteration_cadence_list_item.vue
...pts/iterations/components/iteration_cadence_list_item.vue
+217
-0
ee/app/assets/javascripts/iterations/components/iteration_cadences_list.vue
...scripts/iterations/components/iteration_cadences_list.vue
+14
-11
ee/app/assets/javascripts/iterations/components/iteration_report.vue
...ts/javascripts/iterations/components/iteration_report.vue
+1
-4
ee/app/assets/javascripts/iterations/index.js
ee/app/assets/javascripts/iterations/index.js
+3
-1
ee/app/assets/javascripts/iterations/queries/iteration_cadences_list.query.graphql
.../iterations/queries/iteration_cadences_list.query.graphql
+0
-2
ee/app/assets/javascripts/iterations/queries/iterations_in_cadence.query.graphql
...ts/iterations/queries/iterations_in_cadence.query.graphql
+30
-0
ee/app/assets/javascripts/iterations/router.js
ee/app/assets/javascripts/iterations/router.js
+6
-0
ee/spec/features/groups/iterations/user_views_iteration_cadence_spec.rb
...es/groups/iterations/user_views_iteration_cadence_spec.rb
+41
-0
ee/spec/frontend/iterations/components/iteration_cadence_list_item_spec.js
...iterations/components/iteration_cadence_list_item_spec.js
+188
-0
ee/spec/frontend/iterations/components/iteration_report_spec.js
...c/frontend/iterations/components/iteration_report_spec.js
+6
-0
locale/gitlab.pot
locale/gitlab.pot
+6
-1
No files found.
ee/app/assets/javascripts/iterations/components/iteration_cadence.vue
deleted
100644 → 0
View file @
11a4bfc7
<
script
>
export
default
{
props
:
{
title
:
{
type
:
String
,
required
:
true
,
},
},
};
</
script
>
<
template
>
<li>
{{
title
}}
</li>
</
template
>
ee/app/assets/javascripts/iterations/components/iteration_cadence_list_item.vue
0 → 100644
View file @
63a83b9b
<
script
>
import
{
GlAlert
,
GlButton
,
GlCollapse
,
GlIcon
,
GlInfiniteScroll
,
GlSkeletonLoader
,
}
from
'
@gitlab/ui
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
query
from
'
../queries/iterations_in_cadence.query.graphql
'
;
const
pageSize
=
20
;
const
i18n
=
Object
.
freeze
({
noResults
:
s__
(
'
Iterations|No iterations in cadence.
'
),
error
:
__
(
'
Error loading iterations
'
),
});
export
default
{
i18n
,
components
:
{
GlAlert
,
GlButton
,
GlCollapse
,
GlIcon
,
GlInfiniteScroll
,
GlSkeletonLoader
,
},
apollo
:
{
group
:
{
skip
()
{
return
!
this
.
expanded
;
},
query
,
variables
()
{
return
this
.
queryVariables
;
},
error
()
{
this
.
error
=
i18n
.
error
;
},
},
},
inject
:
[
'
groupPath
'
],
props
:
{
title
:
{
type
:
String
,
required
:
true
,
},
durationInWeeks
:
{
type
:
Number
,
required
:
false
,
default
:
null
,
},
cadenceId
:
{
type
:
String
,
required
:
true
,
},
iterationState
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
i18n
,
expanded
:
false
,
// query response
group
:
{
iterations
:
{
nodes
:
[],
pageInfo
:
{
hasNextPage
:
true
,
},
},
},
afterCursor
:
null
,
showMoreEnabled
:
true
,
error
:
''
,
};
},
computed
:
{
queryVariables
()
{
return
{
fullPath
:
this
.
groupPath
,
iterationCadenceId
:
this
.
cadenceId
,
firstPageSize
:
pageSize
,
state
:
this
.
iterationState
,
};
},
pageInfo
()
{
return
this
.
group
.
iterations
?.
pageInfo
||
{};
},
hasNextPage
()
{
return
this
.
pageInfo
.
hasNextPage
;
},
iterations
()
{
return
this
.
group
?.
iterations
?.
nodes
||
[];
},
loading
()
{
return
this
.
$apollo
.
queries
.
group
.
loading
;
},
editCadence
()
{
return
{
name
:
'
edit
'
,
params
:
{
cadenceId
:
getIdFromGraphQLId
(
this
.
cadenceId
),
},
};
},
},
methods
:
{
fetchMore
()
{
if
(
this
.
iterations
.
length
===
0
||
!
this
.
hasNextPage
||
this
.
loading
)
{
return
;
}
// Fetch more data and transform the original result
this
.
$apollo
.
queries
.
group
.
fetchMore
({
variables
:
{
...
this
.
queryVariables
,
afterCursor
:
this
.
pageInfo
.
endCursor
,
},
// Transform the previous result with new data
updateQuery
:
(
previousResult
,
{
fetchMoreResult
})
=>
{
const
newIterations
=
fetchMoreResult
.
group
?.
iterations
.
nodes
||
[];
return
{
group
:
{
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename
:
'
Group
'
,
iterations
:
{
__typename
:
'
IterationConnection
'
,
// Merging the list
nodes
:
[...
previousResult
.
group
.
iterations
.
nodes
,
...
newIterations
],
pageInfo
:
fetchMoreResult
.
group
?.
iterations
.
pageInfo
||
{},
},
},
};
},
});
},
path
(
iterationId
)
{
return
{
name
:
'
iteration
'
,
params
:
{
cadenceId
:
getIdFromGraphQLId
(
this
.
cadenceId
),
iterationId
:
getIdFromGraphQLId
(
iterationId
),
},
};
},
},
};
</
script
>
<
template
>
<li
class=
"gl-py-0!"
>
<div
class=
"gl-display-flex gl-align-items-center"
>
<gl-button
variant=
"link"
class=
"gl-font-weight-bold gl-text-body! gl-py-5! gl-px-3! gl-mr-auto"
:aria-expanded=
"expanded"
@
click=
"expanded = !expanded"
>
<gl-icon
name=
"chevron-right"
class=
"gl-transition-medium"
:class=
"
{ 'gl-rotate-90': expanded }"
/>
{{
title
}}
</gl-button>
<span
v-if=
"durationInWeeks"
class=
"gl-mr-5"
>
<gl-icon
name=
"clock"
class=
"gl-mr-3"
/>
{{
n__
(
'
Every week
'
,
'
Every %d weeks
'
,
durationInWeeks
)
}}
</span
>
</div>
<gl-alert
v-if=
"error"
variant=
"danger"
:dismissible=
"true"
@
dismiss=
"error = ''"
>
{{
error
}}
</gl-alert>
<gl-collapse
:visible=
"expanded"
>
<div
v-if=
"loading && iterations.length === 0"
class=
"gl-p-5"
>
<gl-skeleton-loader
:lines=
"2"
/>
</div>
<gl-infinite-scroll
v-else-if=
"iterations.length || loading"
:fetched-items=
"iterations.length"
:max-list-height=
"250"
@
bottomReached=
"fetchMore"
>
<template
#items
>
<ol
class=
"gl-pl-0"
>
<li
v-for=
"iteration in iterations"
:key=
"iteration.id"
class=
"gl-bg-gray-10 gl-p-5 gl-border-t-solid gl-border-gray-100 gl-border-t-1 gl-list-style-position-inside"
>
<router-link
:to=
"path(iteration.id)"
>
{{
iteration
.
title
}}
</router-link>
</li>
</ol>
<div
v-if=
"loading"
class=
"gl-p-5"
>
<gl-skeleton-loader
:lines=
"2"
/>
</div>
</
template
>
</gl-infinite-scroll>
<p
v-else-if=
"!loading"
class=
"gl-px-5"
>
{{ i18n.noResults }}
</p>
</gl-collapse>
</li>
</template>
ee/app/assets/javascripts/iterations/components/iteration_cadences_list.vue
View file @
63a83b9b
...
...
@@ -2,14 +2,14 @@
import
{
GlAlert
,
GlButton
,
GlLoadingIcon
,
GlKeysetPagination
,
GlTab
,
GlTabs
}
from
'
@gitlab/ui
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
query
from
'
../queries/iteration_cadences_list.query.graphql
'
;
import
IterationCadence
from
'
./iteration_cadence
.vue
'
;
import
IterationCadence
ListItem
from
'
./iteration_cadence_list_item
.vue
'
;
const
pageSize
=
20
;
export
default
{
tabTitles
:
[
__
(
'
Open
'
),
__
(
'
Done
'
),
__
(
'
All
'
)],
components
:
{
IterationCadence
,
IterationCadence
ListItem
,
GlAlert
,
GlButton
,
GlLoadingIcon
,
...
...
@@ -49,10 +49,6 @@ export default {
fullPath
:
this
.
groupPath
,
};
if
(
this
.
active
!==
undefined
)
{
vars
.
active
=
this
.
active
;
}
if
(
this
.
pagination
.
beforeCursor
)
{
vars
.
beforeCursor
=
this
.
pagination
.
beforeCursor
;
vars
.
lastPageSize
=
pageSize
;
...
...
@@ -72,15 +68,15 @@ export default {
loading
()
{
return
this
.
$apollo
.
queries
.
group
.
loading
;
},
activ
e
()
{
stat
e
()
{
switch
(
this
.
tabIndex
)
{
default
:
case
0
:
return
true
;
return
'
opened
'
;
case
1
:
return
false
;
return
'
closed
'
;
case
2
:
return
undefined
;
return
'
all
'
;
}
},
},
...
...
@@ -115,7 +111,14 @@ export default {
</gl-alert>
<
template
v-else
>
<ul
v-if=
"cadences.length"
class=
"content-list"
>
<iteration-cadence
v-for=
"cadence in cadences"
:key=
"cadence.id"
:title=
"cadence.title"
/>
<iteration-cadence-list-item
v-for=
"cadence in cadences"
:key=
"cadence.id"
:cadence-id=
"cadence.id"
:duration-in-weeks=
"cadence.durationInWeeks"
:title=
"cadence.title"
:iteration-state=
"state"
/>
</ul>
<p
v-else
class=
"nothing-here-block"
>
{{
s__
(
'
Iterations|No iteration cadences to show.
'
)
}}
...
...
ee/app/assets/javascripts/iterations/components/iteration_report.vue
View file @
63a83b9b
...
...
@@ -64,11 +64,8 @@ export default {
},
},
mixins
:
[
glFeatureFlagsMixin
()],
inject
:
[
'
fullPath
'
],
props
:
{
fullPath
:
{
type
:
String
,
required
:
true
,
},
hasScopedLabelsFeature
:
{
type
:
Boolean
,
required
:
false
,
...
...
ee/app/assets/javascripts/iterations/index.js
View file @
63a83b9b
...
...
@@ -73,10 +73,12 @@ export function initIterationReport({ namespaceType, initiallyEditing } = {}) {
return
new
Vue
({
el
,
apolloProvider
,
provide
:
{
fullPath
,
},
render
(
createElement
)
{
return
createElement
(
IterationReport
,
{
props
:
{
fullPath
,
hasScopedLabelsFeature
:
parseBoolean
(
hasScopedLabelsFeature
),
iterationId
,
labelsFetchPath
,
...
...
ee/app/assets/javascripts/iterations/queries/iteration_cadences_list.query.graphql
View file @
63a83b9b
...
...
@@ -2,7 +2,6 @@
query
IterationCadences
(
$fullPath
:
ID
!
$active
:
Boolean
$beforeCursor
:
String
=
""
$afterCursor
:
String
=
""
$firstPageSize
:
Int
...
...
@@ -10,7 +9,6 @@ query IterationCadences(
)
{
group
(
fullPath
:
$fullPath
)
{
iterationCadences
(
active
:
$active
before
:
$beforeCursor
after
:
$afterCursor
first
:
$firstPageSize
...
...
ee/app/assets/javascripts/iterations/queries/iterations_in_cadence.query.graphql
0 → 100644
View file @
63a83b9b
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "./iteration_list_item.fragment.graphql"
query
Iterations
(
$fullPath
:
ID
!
$iterationCadenceId
:
ID
!
$state
:
IterationState
!
$beforeCursor
:
String
$afterCursor
:
String
$firstPageSize
:
Int
$lastPageSize
:
Int
)
{
group
(
fullPath
:
$fullPath
)
{
iterations
(
iterationCadenceIds
:
[
$iterationCadenceId
]
state
:
$state
before
:
$beforeCursor
after
:
$afterCursor
first
:
$firstPageSize
last
:
$lastPageSize
)
{
nodes
{
...
IterationListItem
}
pageInfo
{
...
PageInfo
}
}
}
}
ee/app/assets/javascripts/iterations/router.js
View file @
63a83b9b
...
...
@@ -2,6 +2,7 @@ import Vue from 'vue';
import
VueRouter
from
'
vue-router
'
;
import
IterationCadenceForm
from
'
./components/iteration_cadence_form.vue
'
;
import
IterationCadenceList
from
'
./components/iteration_cadences_list.vue
'
;
import
IterationReport
from
'
./components/iteration_report.vue
'
;
Vue
.
use
(
VueRouter
);
...
...
@@ -16,6 +17,11 @@ const routes = [
path
:
'
/
'
,
component
:
IterationCadenceList
,
},
{
name
:
'
iteration
'
,
path
:
'
/:cadenceId/iterations/:iterationId
'
,
component
:
IterationReport
,
},
];
export
default
function
createRouter
(
base
)
{
...
...
ee/spec/features/groups/iterations/user_views_iteration_cadence_spec.rb
0 → 100644
View file @
63a83b9b
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'User views iteration cadences'
,
:js
do
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:cadence
)
{
create
(
:iterations_cadence
,
group:
group
)
}
let_it_be
(
:other_cadence
)
{
create
(
:iterations_cadence
,
group:
group
)
}
let_it_be
(
:iteration_in_cadence
)
{
create
(
:iteration
,
group:
group
,
iterations_cadence:
cadence
)
}
let_it_be
(
:closed_iteration_in_cadence
)
{
create
(
:closed_iteration
,
group:
group
,
iterations_cadence:
cadence
)
}
let_it_be
(
:iteration_in_other_cadence
)
{
create
(
:iteration
,
group:
group
,
iterations_cadence:
other_cadence
)
}
before
do
stub_licensed_features
(
iterations:
true
)
visit
group_iteration_cadences_path
(
group
)
end
it
'shows iteration cadences with iterations when expanded'
,
:aggregate_failures
do
expect
(
page
).
to
have_title
(
'Iteration cadences'
)
expect
(
page
).
to
have_content
(
cadence
.
title
)
expect
(
page
).
to
have_content
(
other_cadence
.
title
)
expect
(
page
).
not_to
have_content
(
iteration_in_cadence
.
title
)
expect
(
page
).
not_to
have_content
(
iteration_in_other_cadence
.
title
)
click_button
cadence
.
title
expect
(
page
).
to
have_content
(
iteration_in_cadence
.
title
)
expect
(
page
).
not_to
have_content
(
iteration_in_other_cadence
.
title
)
expect
(
page
).
not_to
have_content
(
closed_iteration_in_cadence
.
title
)
end
it
'only shows completed iterations on Done tab'
,
:aggregate_failures
do
click_link
'Done'
click_button
cadence
.
title
expect
(
page
).
not_to
have_content
(
iteration_in_cadence
.
title
)
expect
(
page
).
to
have_content
(
closed_iteration_in_cadence
.
title
)
end
end
ee/spec/frontend/iterations/components/iteration_cadence_list_item_spec.js
0 → 100644
View file @
63a83b9b
import
{
GlInfiniteScroll
,
GlSkeletonLoader
}
from
'
@gitlab/ui
'
;
import
{
createLocalVue
,
RouterLinkStub
}
from
'
@vue/test-utils
'
;
import
{
nextTick
}
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
IterationCadenceListItem
from
'
ee/iterations/components/iteration_cadence_list_item.vue
'
;
import
iterationsInCadenceQuery
from
'
ee/iterations/queries/iterations_in_cadence.query.graphql
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
{
mountExtended
as
mount
}
from
'
helpers/vue_test_utils_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
const
push
=
jest
.
fn
();
const
$router
=
{
push
,
};
const
localVue
=
createLocalVue
();
function
createMockApolloProvider
(
requestHandlers
)
{
localVue
.
use
(
VueApollo
);
return
createMockApollo
(
requestHandlers
);
}
describe
(
'
Iteration cadence list item
'
,
()
=>
{
let
wrapper
;
let
apolloProvider
;
const
groupPath
=
'
gitlab-org
'
;
const
iterations
=
[
{
dueDate
:
'
2021-08-14
'
,
id
:
'
gid://gitlab/Iteration/41
'
,
scopedPath
:
'
/groups/group1/-/iterations/41
'
,
startDate
:
'
2021-08-13
'
,
state
:
'
upcoming
'
,
title
:
'
My title 44
'
,
webPath
:
'
/groups/group1/-/iterations/41
'
,
__typename
:
'
Iteration
'
,
},
];
const
cadence
=
{
id
:
'
gid://gitlab/Iterations::Cadence/561
'
,
title
:
'
Weekly cadence
'
,
durationInWeeks
:
3
,
};
const
startCursor
=
'
MQ
'
;
const
endCursor
=
'
MjA
'
;
const
querySuccessResponse
=
{
data
:
{
group
:
{
iterations
:
{
nodes
:
iterations
,
pageInfo
:
{
hasNextPage
:
true
,
hasPreviousPage
:
false
,
startCursor
,
endCursor
,
},
},
},
},
};
const
queryEmptyResponse
=
{
data
:
{
group
:
{
iterations
:
{
nodes
:
[],
pageInfo
:
{
hasNextPage
:
false
,
hasPreviousPage
:
false
,
startCursor
:
null
,
endCursor
:
null
,
},
},
},
},
};
function
createComponent
({
props
=
{},
canCreateCadence
,
resolverMock
=
jest
.
fn
().
mockResolvedValue
(
querySuccessResponse
),
}
=
{})
{
apolloProvider
=
createMockApolloProvider
([[
iterationsInCadenceQuery
,
resolverMock
]]);
wrapper
=
mount
(
IterationCadenceListItem
,
{
localVue
,
apolloProvider
,
mocks
:
{
$router
,
},
stubs
:
{
RouterLink
:
RouterLinkStub
,
},
provide
:
{
groupPath
,
canCreateCadence
,
},
propsData
:
{
title
:
cadence
.
title
,
cadenceId
:
cadence
.
id
,
iterationState
:
'
open
'
,
...
props
,
},
});
return
nextTick
();
}
const
findLoader
=
()
=>
wrapper
.
findComponent
(
GlSkeletonLoader
);
const
expand
=
()
=>
wrapper
.
findByRole
(
'
button
'
,
{
text
:
cadence
.
title
}).
trigger
(
'
click
'
);
afterEach
(()
=>
{
wrapper
.
destroy
();
apolloProvider
=
null
;
});
it
(
'
does not query iterations when component mounted
'
,
async
()
=>
{
const
resolverMock
=
jest
.
fn
();
await
createComponent
({
resolverMock
,
});
expect
(
resolverMock
).
not
.
toHaveBeenCalled
();
});
it
(
'
shows empty text when no results
'
,
async
()
=>
{
await
createComponent
({
resolverMock
:
jest
.
fn
().
mockResolvedValue
(
queryEmptyResponse
),
});
expand
();
await
waitForPromises
();
expect
(
findLoader
().
exists
()).
toBe
(
false
);
expect
(
wrapper
.
text
()).
toContain
(
IterationCadenceListItem
.
i18n
.
noResults
);
});
it
(
'
shows iterations after loading
'
,
async
()
=>
{
await
createComponent
();
expand
();
await
waitForPromises
();
iterations
.
forEach
(({
title
})
=>
{
expect
(
wrapper
.
text
()).
toContain
(
title
);
});
});
it
(
'
shows alert on query error
'
,
async
()
=>
{
await
createComponent
({
resolverMock
:
jest
.
fn
().
mockRejectedValue
(
queryEmptyResponse
),
});
await
expand
();
await
waitForPromises
();
expect
(
findLoader
().
exists
()).
toBe
(
false
);
expect
(
wrapper
.
text
()).
toContain
(
IterationCadenceListItem
.
i18n
.
error
);
});
it
(
'
calls fetchMore after scrolling down
'
,
async
()
=>
{
await
createComponent
();
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
.
queries
.
group
,
'
fetchMore
'
).
mockResolvedValue
({});
expand
();
await
waitForPromises
();
wrapper
.
findComponent
(
GlInfiniteScroll
).
vm
.
$emit
(
'
bottomReached
'
);
expect
(
wrapper
.
vm
.
$apollo
.
queries
.
group
.
fetchMore
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
variables
:
expect
.
objectContaining
({
afterCursor
:
endCursor
,
}),
}),
);
});
});
ee/spec/frontend/iterations/components/iteration_report_spec.js
View file @
63a83b9b
...
...
@@ -45,6 +45,9 @@ describe('Iterations report', () => {
localVue
,
apolloProvider
:
mockApollo
,
propsData
:
props
,
provide
:
{
fullPath
:
props
.
fullPath
,
},
stubs
:
{
GlLoadingIcon
,
GlTab
,
...
...
@@ -114,6 +117,9 @@ describe('Iterations report', () => {
queries
:
{
iteration
:
{
loading
}
},
},
},
provide
:
{
fullPath
:
props
.
fullPath
,
},
stubs
:
{
GlLoadingIcon
,
GlTab
,
...
...
locale/gitlab.pot
View file @
63a83b9b
...
...
@@ -13157,7 +13157,9 @@ msgid "Every two weeks"
msgstr ""
msgid "Every week"
msgstr ""
msgid_plural "Every %d weeks"
msgstr[0] ""
msgstr[1] ""
msgid "Every week (%{weekday} at %{time})"
msgstr ""
...
...
@@ -18536,6 +18538,9 @@ msgstr ""
msgid "Iterations|No iteration cadences to show."
msgstr ""
msgid "Iterations|No iterations in cadence."
msgstr ""
msgid "Iterations|Number of future iterations you would like to have scheduled"
msgstr ""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment