Commit f5dd3a30 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '218330-issue-list-health-status' into 'master'

Add health status in issuable list

Closes #218330

See merge request gitlab-org/gitlab!38040
parents d596b260 217734c3
...@@ -23,6 +23,8 @@ import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; ...@@ -23,6 +23,8 @@ import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
import { isScopedLabel } from '~/lib/utils/common_utils'; import { isScopedLabel } from '~/lib/utils/common_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { convertToCamelCase } from '~/lib/utils/text_utility';
export default { export default {
i18n: { i18n: {
openedAgo: __('opened %{timeAgoString} by %{user}'), openedAgo: __('opened %{timeAgoString} by %{user}'),
...@@ -34,6 +36,8 @@ export default { ...@@ -34,6 +36,8 @@ export default {
GlLabel, GlLabel,
GlIcon, GlIcon,
GlSprintf, GlSprintf,
IssueHealthStatus: () =>
import('ee_component/related_items_tree/components/issue_health_status.vue'),
}, },
directives: { directives: {
GlTooltip, GlTooltip,
...@@ -195,6 +199,9 @@ export default { ...@@ -195,6 +199,9 @@ export default {
}, },
]; ];
}, },
healthStatus() {
return convertToCamelCase(this.issuable.health_status);
},
}, },
mounted() { mounted() {
// TODO: Refactor user popover to use its own component instead of // TODO: Refactor user popover to use its own component instead of
...@@ -288,7 +295,7 @@ export default { ...@@ -288,7 +295,7 @@ export default {
</div> </div>
<div class="issuable-info"> <div class="issuable-info">
<span class="js-ref-path"> <span class="js-ref-path gl-mr-4 mr-sm-0">
<span <span
v-if="isJiraIssue" v-if="isJiraIssue"
class="svg-container jira-logo-container" class="svg-container jira-logo-container"
...@@ -298,7 +305,7 @@ export default { ...@@ -298,7 +305,7 @@ export default {
{{ referencePath }} {{ referencePath }}
</span> </span>
<span data-testid="openedByMessage" class="gl-display-none d-sm-inline-block gl-mr-2"> <span data-testid="openedByMessage" class="gl-display-none d-sm-inline-block gl-mr-4">
&middot; &middot;
<gl-sprintf <gl-sprintf
:message="isJiraIssue ? $options.i18n.openedAgoJira : $options.i18n.openedAgo" :message="isJiraIssue ? $options.i18n.openedAgoJira : $options.i18n.openedAgo"
...@@ -321,7 +328,7 @@ export default { ...@@ -321,7 +328,7 @@ export default {
<gl-link <gl-link
v-if="issuable.milestone" v-if="issuable.milestone"
v-gl-tooltip v-gl-tooltip
class="gl-display-none d-sm-inline-block gl-mr-2 js-milestone" class="gl-display-none d-sm-inline-block gl-mr-4 js-milestone milestone"
:href="milestoneLink" :href="milestoneLink"
:title="milestoneTooltipText" :title="milestoneTooltipText"
> >
...@@ -332,7 +339,7 @@ export default { ...@@ -332,7 +339,7 @@ export default {
<span <span
v-if="dueDate" v-if="dueDate"
v-gl-tooltip v-gl-tooltip
class="gl-display-none d-sm-inline-block gl-mr-2 js-due-date" class="gl-display-none d-sm-inline-block gl-mr-4 js-due-date"
:class="{ cred: isOverdue }" :class="{ cred: isOverdue }"
:title="__('Due date')" :title="__('Due date')"
> >
...@@ -340,6 +347,24 @@ export default { ...@@ -340,6 +347,24 @@ export default {
{{ dueDateWords }} {{ dueDateWords }}
</span> </span>
<span
v-if="hasWeight"
v-gl-tooltip
:title="__('Weight')"
class="gl-display-none d-sm-inline-block gl-mr-4"
data-testid="weight"
data-qa-selector="issuable_weight_content"
>
<gl-icon name="weight" class="align-text-bottom" />
{{ issuable.weight }}
</span>
<issue-health-status
v-if="issuable.health_status"
:health-status="healthStatus"
class="gl-mr-4 issuable-tag-valign"
/>
<gl-label <gl-label
v-for="label in issuable.labels" v-for="label in issuable.labels"
:key="label.id" :key="label.id"
...@@ -351,21 +376,9 @@ export default { ...@@ -351,21 +376,9 @@ export default {
:title="label.name" :title="label.name"
:scoped="isScoped(label)" :scoped="isScoped(label)"
size="sm" size="sm"
class="gl-mr-2" class="gl-mr-2 issuable-tag-valign"
>{{ label.name }}</gl-label >{{ label.name }}</gl-label
> >
<span
v-if="hasWeight"
v-gl-tooltip
:title="__('Weight')"
class="gl-display-none d-sm-inline-block"
data-testid="weight"
data-qa-selector="issuable_weight_content"
>
<gl-icon name="weight" class="align-text-bottom" />
{{ issuable.weight }}
</span>
</div> </div>
</div> </div>
......
...@@ -81,27 +81,6 @@ $item-remove-button-space: 42px; ...@@ -81,27 +81,6 @@ $item-remove-button-space: 42px;
max-width: 0; max-width: 0;
} }
.status {
&-at-risk {
color: $red-500;
background-color: $red-100;
}
&-needs-attention {
color: $orange-700;
background-color: $orange-100;
}
&-on-track {
color: $green-600;
background-color: $green-100;
}
}
.gl-label-text {
font-weight: $gl-font-weight-bold;
}
.bullet-separator { .bullet-separator {
font-size: 9px; font-size: 9px;
color: $gray-200; color: $gray-200;
......
...@@ -804,6 +804,10 @@ ...@@ -804,6 +804,10 @@
} }
} }
} }
.milestone {
color: $gray-700;
}
} }
@media(max-width: map-get($grid-breakpoints, lg)-1) { @media(max-width: map-get($grid-breakpoints, lg)-1) {
......
...@@ -179,6 +179,20 @@ the `weight` parameter: ...@@ -179,6 +179,20 @@ the `weight` parameter:
] ]
``` ```
Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will also see
the `health_status` parameter:
```json
[
{
"state" : "opened",
"description" : "Ratione dolores corrupti mollitia soluta quia.",
"health_status": "on_track",
...
}
]
```
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists. **Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
...@@ -338,6 +352,20 @@ the `weight` parameter: ...@@ -338,6 +352,20 @@ the `weight` parameter:
] ]
``` ```
Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will also see
the `health_status` parameter:
```json
[
{
"project_id" : 4,
"description" : "Omnis vero earum sunt corporis dolor et placeat.",
"health_status": "at_risk",
...
}
]
```
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists. **Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
...@@ -503,6 +531,20 @@ the `weight` parameter: ...@@ -503,6 +531,20 @@ the `weight` parameter:
] ]
``` ```
Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will also see
the `health_status` parameter:
```json
[
{
"project_id" : 4,
"description" : "Omnis vero earum sunt corporis dolor et placeat.",
"health_status": "at_risk",
...
}
]
```
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists. **Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
...@@ -642,6 +684,20 @@ the `epic` property: ...@@ -642,6 +684,20 @@ the `epic` property:
} }
``` ```
Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will also additionally see
the `health_status` property:
```json
[
{
"project_id" : 4,
"description" : "Omnis vero earum sunt corporis dolor et placeat.",
"health_status": "on_track",
...
}
]
```
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists. **Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
...@@ -752,6 +808,20 @@ the `weight` parameter: ...@@ -752,6 +808,20 @@ the `weight` parameter:
} }
``` ```
Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will also see
the `health_status` parameter:
```json
[
{
"project_id" : 4,
"description" : "Omnis vero earum sunt corporis dolor et placeat.",
"health_status": "on_track",
...
}
]
```
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists. **Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
...@@ -874,6 +944,20 @@ the `weight` parameter: ...@@ -874,6 +944,20 @@ the `weight` parameter:
} }
``` ```
Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will also see
the `health_status` parameter:
```json
[
{
"project_id" : 4,
"description" : "Omnis vero earum sunt corporis dolor et placeat.",
"health_status": "on_track",
...
}
]
```
NOTE: **Note:** NOTE: **Note:**
At least one of following parameters is required to be passed for the request to be successful: `:assignee_id`, `:assignee_ids`, `:confidential`, `:created_at`, `:description`, `:discussion_locked`, `:due_date`, `:labels`, `:milestone_id`, `:state_event`, or `:title`. At least one of following parameters is required to be passed for the request to be successful: `:assignee_id`, `:assignee_ids`, `:confidential`, `:created_at`, `:description`, `:discussion_locked`, `:due_date`, `:labels`, `:milestone_id`, `:state_event`, or `:title`.
...@@ -1027,6 +1111,20 @@ the `weight` parameter: ...@@ -1027,6 +1111,20 @@ the `weight` parameter:
} }
``` ```
Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will also see
the `health_status` parameter:
```json
[
{
"project_id" : 4,
"description" : "Omnis vero earum sunt corporis dolor et placeat.",
"health_status": "on_track",
...
}
]
```
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API. **Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists. **Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
......
...@@ -50,11 +50,11 @@ export default { ...@@ -50,11 +50,11 @@ export default {
{{ healthStatus.issuesOnTrack }} {{ healthStatus.issuesOnTrack }}
</span> </span>
</span> </span>
<span class="gl-mr-2 mr-md-2 gl-text-gray-700 health-label-long gl-display-none"> <span class="gl-mr-2 mr-md-2 gl-text-gray-700 health-label-long gl-display-none!">
{{ __('issues on track') }} {{ __('issues on track') }}
</span> </span>
<span <span
class="gl-mr-2 mr-md-2 gl-text-gray-700 gl-str-truncated health-label-short gl-display-none" class="gl-mr-2 mr-md-2 gl-text-gray-700 gl-str-truncated health-label-short gl-display-none!"
>{{ __('on track') }}</span >{{ __('on track') }}</span
> >
...@@ -63,11 +63,11 @@ export default { ...@@ -63,11 +63,11 @@ export default {
{{ healthStatus.issuesNeedingAttention }} {{ healthStatus.issuesNeedingAttention }}
</span> </span>
</span> </span>
<span class="gl-mr-2 mr-md-2 gl-text-gray-700 health-label-long gl-display-none"> <span class="gl-mr-2 mr-md-2 gl-text-gray-700 health-label-long gl-display-none!">
{{ __('issues need attention') }} {{ __('issues need attention') }}
</span> </span>
<span <span
class="gl-mr-2 mr-md-2 gl-text-gray-700 gl-str-truncated health-label-short gl-display-none" class="gl-mr-2 mr-md-2 gl-text-gray-700 gl-str-truncated health-label-short gl-display-none!"
>{{ __('need attention') }}</span >{{ __('need attention') }}</span
> >
...@@ -76,10 +76,10 @@ export default { ...@@ -76,10 +76,10 @@ export default {
{{ healthStatus.issuesAtRisk }} {{ healthStatus.issuesAtRisk }}
</span> </span>
</span> </span>
<span class="gl-text-gray-700 health-label-long gl-display-none"> <span class="gl-text-gray-700 health-label-long gl-display-none!">
{{ __('issues at risk') }} {{ __('issues at risk') }}
</span> </span>
<span class="gl-text-gray-700 gl-str-truncated health-label-short gl-display-none"> <span class="gl-text-gray-700 gl-str-truncated health-label-short gl-display-none!">
{{ __('at risk') }} {{ __('at risk') }}
</span> </span>
</div> </div>
......
...@@ -199,7 +199,7 @@ export default { ...@@ -199,7 +199,7 @@ export default {
<div <div
class="item-meta gl-display-flex gl-flex-wrap mt-xl-0 flex-xl-nowrap gl-align-items-center gl-py-2 gl-ml-6" class="item-meta gl-display-flex gl-flex-wrap mt-xl-0 flex-xl-nowrap gl-align-items-center gl-py-2 gl-ml-6"
> >
<span class="gl-mr-4">{{ itemHierarchy }}</span> <span class="gl-mr-5">{{ itemHierarchy }}</span>
<gl-tooltip v-if="isEpic" :target="() => $refs.countBadge"> <gl-tooltip v-if="isEpic" :target="() => $refs.countBadge">
<p v-if="allowSubEpics" class="gl-font-weight-bold gl-m-0"> <p v-if="allowSubEpics" class="gl-font-weight-bold gl-m-0">
{{ __('Epics') }} &#8226; {{ __('Epics') }} &#8226;
...@@ -232,15 +232,12 @@ export default { ...@@ -232,15 +232,12 @@ export default {
> >
<span <span
v-if="allowSubEpics" v-if="allowSubEpics"
class="gl-display-inline-flex gl-align-items-center gl-mr-3" class="gl-display-inline-flex gl-align-items-center gl-mr-5"
> >
<gl-icon name="epic" class="gl-mr-2" /> <gl-icon name="epic" class="gl-mr-2" />
{{ totalEpicsCount }} {{ totalEpicsCount }}
</span> </span>
<span <span class="gl-display-inline-flex gl-align-items-center gl-mr-5">
class="gl-display-inline-flex gl-align-items-center gl-mr-3"
:class="{ 'ml-2': allowSubEpics }"
>
<gl-icon name="issues" class="gl-mr-2" /> <gl-icon name="issues" class="gl-mr-2" />
{{ totalIssuesCount }} {{ totalIssuesCount }}
</span> </span>
...@@ -249,38 +246,40 @@ export default { ...@@ -249,38 +246,40 @@ export default {
<item-milestone <item-milestone
v-if="hasMilestone" v-if="hasMilestone"
:milestone="item.milestone" :milestone="item.milestone"
class="item-milestone gl-display-flex gl-align-items-center gl-mr-4" class="item-milestone gl-display-flex gl-align-items-center gl-mr-5"
/> />
<item-due-date <item-due-date
v-if="item.dueDate" v-if="item.dueDate"
:date="item.dueDate" :date="item.dueDate"
tooltip-placement="top" tooltip-placement="top"
css-class="item-due-date gl-display-flex gl-align-items-center gl-mr-4" css-class="item-due-date gl-display-flex gl-align-items-center gl-mr-5!"
/> />
<item-weight <item-weight
v-if="item.weight" v-if="item.weight"
:weight="item.weight" :weight="item.weight"
class="item-weight gl-display-flex gl-align-items-center gl-mr-4" class="item-weight gl-display-flex gl-align-items-center gl-mr-5!"
tag-name="span" tag-name="span"
/> />
<item-assignees <item-assignees
v-if="hasAssignees" v-if="hasAssignees"
:assignees="item.assignees" :assignees="item.assignees"
class="item-assignees gl-display-inline-flex gl-align-items-center gl-mr-4 mb-md-0 flex-xl-grow-0" class="item-assignees gl-display-inline-flex gl-align-items-center gl-mr-5 mb-md-0 flex-xl-grow-0"
/> />
<epic-health-status <epic-health-status
v-if="showEpicHealthStatus" v-if="showEpicHealthStatus"
:health-status="item.healthStatus" :health-status="item.healthStatus"
data-testid="epic-health-status" data-testid="epic-health-status"
class="issuable-tag-valign"
/> />
<issue-health-status <issue-health-status
v-if="showIssueHealthStatus" v-if="showIssueHealthStatus"
:health-status="item.healthStatus" :health-status="item.healthStatus"
data-testid="issue-health-status" data-testid="issue-health-status"
class="issuable-tag-valign"
/> />
</div> </div>
</div> </div>
......
...@@ -95,3 +95,32 @@ ...@@ -95,3 +95,32 @@
display: none; display: none;
} }
} }
.health-status {
line-height: $gl-line-height;
.status {
&-at-risk {
color: $red-500;
background-color: $red-100;
}
&-needs-attention {
color: $orange-700;
background-color: $orange-100;
}
&-on-track {
color: $green-600;
background-color: $green-100;
}
}
.gl-label-text {
font-weight: $gl-font-weight-bold;
}
}
.issuable-tag-valign {
vertical-align: 1px;
}
---
title: Add health status in issuable list
merge_request: 38040
author:
type: added
...@@ -20,6 +20,10 @@ module EE ...@@ -20,6 +20,10 @@ module EE
issue.epic if ::Ability.allowed?(options[:current_user], :read_epic, issue.epic) issue.epic if ::Ability.allowed?(options[:current_user], :read_epic, issue.epic)
end end
end end
with_options if: -> (issue) { issue.project.feature_available?(:issuable_health_status) } do
expose :health_status
end
end end
end end
end end
......
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
"title": { "type": "string" }, "title": { "type": "string" },
"url": { "type": "string" } "url": { "type": "string" }
} }
},
"health_status": {
"type": ["string", "null"]
} }
} }
} }
......
...@@ -99,6 +99,7 @@ describe('Issuable component', () => { ...@@ -99,6 +99,7 @@ describe('Issuable component', () => {
const findIssuableTitle = () => wrapper.find('[data-testid="issuable-title"]'); const findIssuableTitle = () => wrapper.find('[data-testid="issuable-title"]');
const findIssuableStatus = () => wrapper.find('[data-testid="issuable-status"]'); const findIssuableStatus = () => wrapper.find('[data-testid="issuable-status"]');
const containsJiraLogo = () => wrapper.contains('[data-testid="jira-logo"]'); const containsJiraLogo = () => wrapper.contains('[data-testid="jira-logo"]');
const findHealthStatus = () => wrapper.find('.health-status');
describe('when mounted', () => { describe('when mounted', () => {
it('initializes user popovers', () => { it('initializes user popovers', () => {
...@@ -474,4 +475,19 @@ describe('Issuable component', () => { ...@@ -474,4 +475,19 @@ describe('Issuable component', () => {
}); });
}); });
}); });
if (IS_EE) {
describe('with health status', () => {
it('renders health status tag', () => {
factory({ issuable });
expect(findHealthStatus().exists()).toBe(true);
});
it('does not render when health status is absent', () => {
issuable.health_status = null;
factory({ issuable });
expect(findHealthStatus().exists()).toBe(false);
});
});
}
}); });
...@@ -30,6 +30,7 @@ export const simpleIssue = { ...@@ -30,6 +30,7 @@ export const simpleIssue = {
references: { references: {
relative: 'html-boilerplate#45', relative: 'html-boilerplate#45',
}, },
health_status: 'on_track',
}; };
export const testLabels = [ export const testLabels = [
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment