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
05090671
Commit
05090671
authored
Jun 17, 2020
by
Simon Knox
Committed by
Peter Leitzen
Jun 17, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add basic iteration report view
Filtering iterations by ID, and display just info No edit view for now
parent
f6082c02
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
339 additions
and
13 deletions
+339
-13
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+5
-0
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+10
-0
ee/app/assets/javascripts/iterations/components/iteration_report.vue
...ts/javascripts/iterations/components/iteration_report.vue
+123
-0
ee/app/assets/javascripts/iterations/components/iterations_list.vue
...ets/javascripts/iterations/components/iterations_list.vue
+7
-8
ee/app/assets/javascripts/iterations/index.js
ee/app/assets/javascripts/iterations/index.js
+23
-0
ee/app/assets/javascripts/iterations/queries/group_iteration.query.graphql
...ascripts/iterations/queries/group_iteration.query.graphql
+15
-0
ee/app/assets/javascripts/pages/groups/iterations/show/index.js
.../assets/javascripts/pages/groups/iterations/show/index.js
+5
-0
ee/app/finders/iterations_finder.rb
ee/app/finders/iterations_finder.rb
+9
-0
ee/app/graphql/resolvers/iterations_resolver.rb
ee/app/graphql/resolvers/iterations_resolver.rb
+4
-0
ee/app/views/groups/iterations/show.html.haml
ee/app/views/groups/iterations/show.html.haml
+2
-2
ee/spec/features/groups/iterations/user_views_iteration_spec.rb
...c/features/groups/iterations/user_views_iteration_spec.rb
+29
-0
ee/spec/finders/iterations_finder_spec.rb
ee/spec/finders/iterations_finder_spec.rb
+6
-0
ee/spec/frontend/iterations/components/iteration_report_spec.js
...c/frontend/iterations/components/iteration_report_spec.js
+94
-0
ee/spec/graphql/resolvers/iterations_resolver_spec.rb
ee/spec/graphql/resolvers/iterations_resolver_spec.rb
+4
-3
locale/gitlab.pot
locale/gitlab.pot
+3
-0
No files found.
doc/api/graphql/reference/gitlab_schema.graphql
View file @
05090671
...
...
@@ -4819,6 +4819,11 @@ type Group {
"""
first
:
Int
"""
The
ID
of
the
Iteration
to
look
up
"""
id
:
ID
"""
Returns
the
last
_n_
elements
from
the
list
.
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
05090671
...
...
@@ -13275,6 +13275,16 @@
},
"defaultValue": null
},
{
"name": "id",
"description": "The ID of the Iteration to look up",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
ee/app/assets/javascripts/iterations/components/iteration_report.vue
0 → 100644
View file @
05090671
<
script
>
import
{
GlAlert
,
GlBadge
,
GlLoadingIcon
,
GlEmptyState
}
from
'
@gitlab/ui
'
;
import
{
formatDate
}
from
'
~/lib/utils/datetime_utility
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
query
from
'
../queries/group_iteration.query.graphql
'
;
const
iterationStates
=
{
closed
:
'
closed
'
,
upcoming
:
'
upcoming
'
,
expired
:
'
expired
'
,
};
export
default
{
components
:
{
GlAlert
,
GlBadge
,
GlLoadingIcon
,
GlEmptyState
,
},
apollo
:
{
group
:
{
query
,
variables
()
{
return
{
groupPath
:
this
.
groupPath
,
id
:
getIdFromGraphQLId
(
this
.
iterationId
),
};
},
update
(
data
)
{
const
iteration
=
data
?.
group
?.
iterations
?.
nodes
[
0
]
||
{};
return
{
iteration
,
};
},
error
(
err
)
{
this
.
error
=
err
.
message
;
},
},
},
props
:
{
groupPath
:
{
type
:
String
,
required
:
true
,
},
iterationId
:
{
type
:
String
,
required
:
true
,
},
canEdit
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
data
()
{
return
{
error
:
''
,
group
:
{
iteration
:
{},
},
};
},
computed
:
{
iteration
()
{
return
this
.
group
.
iteration
;
},
hasIteration
()
{
return
!
this
.
$apollo
.
queries
.
group
.
loading
&&
this
.
iteration
?.
title
;
},
status
()
{
switch
(
this
.
iteration
.
state
)
{
case
iterationStates
.
closed
:
return
{
text
:
__
(
'
Closed
'
),
variant
:
'
danger
'
,
};
case
iterationStates
.
expired
:
return
{
text
:
__
(
'
Past due
'
),
variant
:
'
warning
'
};
case
iterationStates
.
upcoming
:
return
{
text
:
__
(
'
Upcoming
'
),
variant
:
'
neutral
'
};
default
:
return
{
text
:
__
(
'
Open
'
),
variant
:
'
success
'
};
}
},
},
methods
:
{
formatDate
(
date
)
{
return
formatDate
(
date
,
'
mmm d, yyyy
'
);
},
},
};
</
script
>
<
template
>
<div>
<gl-alert
v-if=
"error"
variant=
"danger"
@
dismiss=
"error = ''"
>
{{
error
}}
</gl-alert>
<gl-loading-icon
v-if=
"$apollo.queries.group.loading"
class=
"gl-py-5"
size=
"lg"
/>
<gl-empty-state
v-else-if=
"!hasIteration"
:title=
"__('Could not find iteration')"
:compact=
"false"
/>
<template
v-else
>
<div
ref=
"topbar"
class=
"gl-display-flex gl-justify-items-center gl-align-items-center gl-py-3 gl-border-1 gl-border-b-solid gl-border-gray-200"
>
<gl-badge
:variant=
"status.variant"
>
{{
status
.
text
}}
</gl-badge>
<span
class=
"gl-ml-4"
>
{{
formatDate
(
iteration
.
startDate
)
}}
–
{{
formatDate
(
iteration
.
dueDate
)
}}
</span
>
</div>
<h3
ref=
"title"
class=
"page-title"
>
{{
iteration
.
title
}}
</h3>
<div
ref=
"description"
v-html=
"iteration.description"
></div>
</
template
>
</div>
</template>
ee/app/assets/javascripts/iterations/components/iterations_list.vue
View file @
05090671
<
script
>
import
{
GlLink
}
from
'
@gitlab/ui
'
;
import
dateFormat
from
'
dateformat
'
;
import
{
formatDate
}
from
'
~/lib/utils/datetime_utility
'
;
export
default
{
components
:
{
GlLink
,
},
filters
:
{
date
:
value
=>
{
const
date
=
new
Date
(
value
);
return
dateFormat
(
date
,
'
mmm d, yyyy
'
,
true
);
},
},
props
:
{
iterations
:
{
type
:
Array
,
...
...
@@ -19,6 +13,11 @@ export default {
default
:
()
=>
[],
},
},
methods
:
{
formatDate
(
date
)
{
return
formatDate
(
date
,
'
mmm d, yyyy
'
);
},
},
};
</
script
>
...
...
@@ -32,7 +31,7 @@ export default {
>
</div>
<div
class=
"text-secondary gl-mb-3"
>
{{
iteration
.
startDate
|
date
}}
–
{{
iteration
.
dueDate
|
date
}}
{{
formatDate
(
iteration
.
startDate
)
}}
–
{{
formatDate
(
iteration
.
dueDate
)
}}
</div>
</li>
</ul>
...
...
ee/app/assets/javascripts/iterations/index.js
View file @
05090671
...
...
@@ -4,6 +4,7 @@ import createDefaultClient from '~/lib/graphql';
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
Iterations
from
'
./components/iterations.vue
'
;
import
IterationForm
from
'
./components/iteration_form.vue
'
;
import
IterationReport
from
'
./components/iteration_report.vue
'
;
Vue
.
use
(
VueApollo
);
...
...
@@ -47,4 +48,26 @@ export function initIterationForm() {
});
}
export
function
initIterationReport
()
{
const
el
=
document
.
querySelector
(
'
.js-iteration
'
);
const
{
groupPath
,
iterationId
,
editIterationPath
}
=
el
.
dataset
;
const
canEdit
=
parseBoolean
(
el
.
dataset
.
canEdit
);
return
new
Vue
({
el
,
apolloProvider
,
render
(
createElement
)
{
return
createElement
(
IterationReport
,
{
props
:
{
groupPath
,
iterationId
,
canEdit
,
editIterationPath
,
},
});
},
});
}
export
default
{};
ee/app/assets/javascripts/iterations/queries/group_iteration.query.graphql
0 → 100644
View file @
05090671
query
GroupIteration
(
$groupPath
:
ID
!,
$id
:
ID
!)
{
group
(
fullPath
:
$groupPath
)
{
iterations
(
id
:
$id
,
first
:
1
)
{
nodes
{
title
state
id
description
webPath
startDate
dueDate
}
}
}
}
ee/app/assets/javascripts/pages/groups/iterations/show/index.js
0 → 100644
View file @
05090671
import
{
initIterationReport
}
from
'
ee/iterations
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
initIterationReport
();
});
ee/app/finders/iterations_finder.rb
View file @
05090671
...
...
@@ -21,6 +21,7 @@ class IterationsFinder
def
execute
items
=
Iteration
.
all
items
=
by_id
(
items
)
items
=
by_groups_and_projects
(
items
)
items
=
by_title
(
items
)
items
=
by_search_title
(
items
)
...
...
@@ -36,6 +37,14 @@ class IterationsFinder
items
.
for_projects_and_groups
(
params
[
:project_ids
],
params
[
:group_ids
])
end
def
by_id
(
items
)
if
params
[
:id
]
items
.
id_in
(
params
[
:id
])
else
items
end
end
def
by_title
(
items
)
if
params
[
:title
]
items
.
with_title
(
params
[
:title
])
...
...
ee/app/graphql/resolvers/iterations_resolver.rb
View file @
05090671
...
...
@@ -11,6 +11,9 @@ module Resolvers
argument
:title
,
GraphQL
::
STRING_TYPE
,
required:
false
,
description:
'Fuzzy search by title'
argument
:id
,
GraphQL
::
ID_TYPE
,
required:
false
,
description:
'The ID of the Iteration to look up'
type
Types
::
IterationType
,
null:
true
...
...
@@ -28,6 +31,7 @@ module Resolvers
def
iterations_finder_params
(
args
)
{
id:
args
[
:id
],
state:
args
[
:state
]
||
'all'
,
start_date:
args
[
:start_date
],
end_date:
args
[
:end_date
],
...
...
ee/app/views/groups/iterations/show.html.haml
View file @
05090671
...
...
@@ -2,5 +2,5 @@
-
breadcrumb_title
params
[
:id
]
-
page_title
_
(
"Iterations"
)
.js-iteration
{
data:
{
group_full_path:
@group
.
full_path
,
iteration_id:
params
[
:iteration_id
]
}
}
-
if
Feature
.
enabled?
(
:group_iterations
,
@group
)
.js-iteration
{
data:
{
group_path:
@group
.
full_path
,
iteration_id:
params
[
:id
],
can_edit:
can?
(
current_user
,
:admin_iteration
,
@group
).
to_s
,
preview_markdown_path:
preview_markdown_path
(
@group
)
}
}
ee/spec/features/groups/iterations/user_views_iteration_spec.rb
0 → 100644
View file @
05090671
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'User views iteration'
do
let_it_be
(
:now
)
{
Time
.
now
}
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:user
)
{
create
(
:group_member
,
:maintainer
,
user:
create
(
:user
),
group:
group
).
user
}
let_it_be
(
:iteration
)
{
create
(
:iteration
,
:skip_future_date_validation
,
group:
group
,
start_date:
now
-
1
.
day
,
due_date:
now
)
}
around
do
|
example
|
Timecop
.
freeze
{
example
.
run
}
end
before
do
sign_in
(
user
)
end
context
'view an iteration'
,
:js
do
before
do
visit
group_iteration_path
(
iteration
.
group
,
iteration
)
end
it
'shows iteration info and dates'
do
expect
(
page
).
to
have_content
(
iteration
.
title
)
expect
(
page
).
to
have_content
(
iteration
.
description
)
end
end
end
ee/spec/finders/iterations_finder_spec.rb
View file @
05090671
...
...
@@ -91,6 +91,12 @@ RSpec.describe IterationsFinder do
expect
(
subject
.
to_a
).
to
contain_exactly
(
started_group_iteration
)
end
it
'filters by ID'
do
params
[
:id
]
=
iteration_from_project_1
.
id
expect
(
subject
).
to
contain_exactly
(
iteration_from_project_1
)
end
context
'by timeframe'
do
it
'returns iterations with start_date and due_date between timeframe'
do
params
.
merge!
(
start_date:
now
-
1
.
day
,
end_date:
3
.
days
.
from_now
)
...
...
ee/spec/frontend/iterations/components/iteration_report_spec.js
0 → 100644
View file @
05090671
import
IterationReport
from
'
ee/iterations/components/iteration_report.vue
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlEmptyState
,
GlLoadingIcon
,
GlTab
,
GlTabs
}
from
'
@gitlab/ui
'
;
describe
(
'
Iterations tabs
'
,
()
=>
{
let
wrapper
;
const
defaultProps
=
{
groupPath
:
'
gitlab-org
'
,
iterationId
:
'
3
'
,
};
const
findTopbar
=
()
=>
wrapper
.
find
({
ref
:
'
topbar
'
});
const
findTitle
=
()
=>
wrapper
.
find
({
ref
:
'
title
'
});
const
findDescription
=
()
=>
wrapper
.
find
({
ref
:
'
description
'
});
const
mountComponent
=
({
props
=
defaultProps
,
loading
=
false
}
=
{})
=>
{
wrapper
=
shallowMount
(
IterationReport
,
{
propsData
:
props
,
mocks
:
{
$apollo
:
{
queries
:
{
group
:
{
loading
}
},
},
},
stubs
:
{
GlLoadingIcon
,
GlTab
,
GlTabs
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
(
'
shows spinner while loading
'
,
()
=>
{
mountComponent
({
loading
:
true
,
});
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
true
);
});
describe
(
'
empty state
'
,
()
=>
{
it
(
'
shows empty state if no item loaded
'
,
()
=>
{
mountComponent
({
loading
:
false
,
});
expect
(
wrapper
.
find
(
GlEmptyState
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
GlEmptyState
).
props
(
'
title
'
)).
toEqual
(
'
Could not find iteration
'
);
expect
(
findTitle
().
exists
()).
toBe
(
false
);
expect
(
findDescription
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
item loaded
'
,
()
=>
{
const
iteration
=
{
title
:
'
June week 1
'
,
description
:
'
The first week of June
'
,
startDate
:
'
2020-06-02
'
,
dueDate
:
'
2020-06-08
'
,
};
beforeEach
(()
=>
{
mountComponent
({
loading
:
false
,
});
wrapper
.
setData
({
group
:
{
iteration
,
},
});
});
it
(
'
shows status and date in header
'
,
()
=>
{
expect
(
findTopbar
().
text
()).
toContain
(
'
Open
'
);
expect
(
findTopbar
().
text
()).
toContain
(
'
Jun 2, 2020
'
);
expect
(
findTopbar
().
text
()).
toContain
(
'
Jun 8, 2020
'
);
});
it
(
'
hides empty region and loading spinner
'
,
()
=>
{
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
false
);
expect
(
wrapper
.
find
(
GlEmptyState
).
exists
()).
toBe
(
false
);
});
it
(
'
shows title and description
'
,
()
=>
{
expect
(
findTitle
().
text
()).
toContain
(
iteration
.
title
);
expect
(
findDescription
().
text
()).
toContain
(
iteration
.
description
);
});
});
});
ee/spec/graphql/resolvers/iterations_resolver_spec.rb
View file @
05090671
...
...
@@ -31,7 +31,7 @@ RSpec.describe Resolvers::IterationsResolver do
context
'without parameters'
do
it
'calls IterationsFinder to retrieve all iterations'
do
expect
(
IterationsFinder
).
to
receive
(
:new
)
.
with
(
group_ids:
group
.
id
,
state:
'all'
,
start_date:
nil
,
end_date:
nil
,
search_title:
nil
)
.
with
(
group_ids:
group
.
id
,
state:
'all'
,
start_date:
nil
,
end_date:
nil
,
search_title:
nil
,
id:
nil
)
.
and_call_original
resolve_group_iterations
...
...
@@ -43,12 +43,13 @@ RSpec.describe Resolvers::IterationsResolver do
start_date
=
now
end_date
=
start_date
+
1
.
hour
search
=
'wow'
id
=
1
expect
(
IterationsFinder
).
to
receive
(
:new
)
.
with
(
group_ids:
group
.
id
,
state:
'closed'
,
start_date:
start_date
,
end_date:
end_date
,
search_title:
search
)
.
with
(
group_ids:
group
.
id
,
state:
'closed'
,
start_date:
start_date
,
end_date:
end_date
,
search_title:
search
,
id:
id
)
.
and_call_original
resolve_group_iterations
(
start_date:
start_date
,
end_date:
end_date
,
state:
'closed'
,
title:
search
)
resolve_group_iterations
(
start_date:
start_date
,
end_date:
end_date
,
state:
'closed'
,
title:
search
,
id:
id
)
end
end
...
...
locale/gitlab.pot
View file @
05090671
...
...
@@ -6432,6 +6432,9 @@ msgstr ""
msgid "Could not find design."
msgstr ""
msgid "Could not find iteration"
msgstr ""
msgid "Could not remove the trigger."
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