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
9144ffb5
Commit
9144ffb5
authored
Sep 02, 2020
by
Tristan Read
Committed by
Kushal Pandya
Sep 02, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add a Summary tab for incident issues
parent
921a6ad5
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
271 additions
and
41 deletions
+271
-41
app/assets/javascripts/issue_show/components/app.vue
app/assets/javascripts/issue_show/components/app.vue
+20
-2
app/assets/javascripts/issue_show/components/incident_tabs.vue
...ssets/javascripts/issue_show/components/incident_tabs.vue
+26
-0
app/assets/javascripts/issue_show/components/pinned_links.vue
...assets/javascripts/issue_show/components/pinned_links.vue
+1
-1
app/assets/javascripts/issue_show/incident.js
app/assets/javascripts/issue_show/incident.js
+21
-0
app/assets/javascripts/issue_show/issue.js
app/assets/javascripts/issue_show/issue.js
+2
-3
app/assets/javascripts/pages/projects/issues/show.js
app/assets/javascripts/pages/projects/issues/show.js
+11
-2
app/helpers/issuables_helper.rb
app/helpers/issuables_helper.rb
+3
-2
changelogs/unreleased/tr-incident-tabs.yml
changelogs/unreleased/tr-incident-tabs.yml
+5
-0
spec/features/issues/incident_issue_spec.rb
spec/features/issues/incident_issue_spec.rb
+26
-0
spec/frontend/issue_show/components/app_spec.js
spec/frontend/issue_show/components/app_spec.js
+73
-22
spec/frontend/issue_show/components/description_spec.js
spec/frontend/issue_show/components/description_spec.js
+1
-8
spec/frontend/issue_show/components/incident_tabs_spec.js
spec/frontend/issue_show/components/incident_tabs_spec.js
+44
-0
spec/frontend/issue_show/issue_spec.js
spec/frontend/issue_show/issue_spec.js
+26
-0
spec/frontend/issue_show/mock_data.js
spec/frontend/issue_show/mock_data.js
+10
-0
spec/helpers/issuables_helper_spec.rb
spec/helpers/issuables_helper_spec.rb
+2
-1
No files found.
app/assets/javascripts/issue_show/components/app.vue
View file @
9144ffb5
...
...
@@ -20,7 +20,6 @@ export default {
components
:
{
GlIcon
,
GlIntersectionObserver
,
descriptionComponent
,
titleComponent
,
editedComponent
,
formComponent
,
...
...
@@ -152,6 +151,18 @@ export default {
required
:
false
,
default
:
0
,
},
descriptionComponent
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
{
return
descriptionComponent
;
},
},
showTitleBorder
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
},
data
()
{
const
store
=
new
Store
({
...
...
@@ -209,6 +220,11 @@ export default {
isOpenStatus
()
{
return
this
.
issuableStatus
===
IssuableStatus
.
Open
;
},
pinnedLinkClasses
()
{
return
this
.
showTitleBorder
?
'
gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-mb-6
'
:
''
;
},
statusIcon
()
{
return
this
.
isOpenStatus
?
'
issue-open-m
'
:
'
mobile-issue-close
'
;
},
...
...
@@ -447,9 +463,11 @@ export default {
<pinned-links
:zoom-meeting-url=
"zoomMeetingUrl"
:published-incident-url=
"publishedIncidentUrl"
:class=
"pinnedLinkClasses"
/>
<description-component
<component
:is=
"descriptionComponent"
v-if=
"state.descriptionHtml"
:can-update=
"canUpdate"
:description-html=
"state.descriptionHtml"
...
...
app/assets/javascripts/issue_show/components/incident_tabs.vue
0 → 100644
View file @
9144ffb5
<
script
>
import
{
GlTab
,
GlTabs
}
from
'
@gitlab/ui
'
;
import
DescriptionComponent
from
'
./description.vue
'
;
export
default
{
components
:
{
GlTab
,
GlTabs
,
DescriptionComponent
,
},
};
</
script
>
<
template
>
<div>
<gl-tabs
content-class=
"gl-reset-line-height gl-mt-3"
class=
"gl-mt-n3"
data-testid=
"incident-tabs"
>
<gl-tab
:title=
"__('Summary')"
>
<description-component
v-bind=
"$attrs"
/>
</gl-tab>
</gl-tabs>
</div>
</
template
>
app/assets/javascripts/issue_show/components/pinned_links.vue
View file @
9144ffb5
...
...
@@ -45,7 +45,7 @@ export default {
</
script
>
<
template
>
<div
class=
"
border-bottom gl-mb-6
gl-display-flex gl-justify-content-start"
>
<div
class=
"gl-display-flex gl-justify-content-start"
>
<template
v-for=
"(link, i) in pinnedLinks"
>
<div
v-if=
"link.url"
:key=
"link.id"
:class=
"
{ 'gl-pr-3': needsPaddingClass(i) }">
<gl-button
...
...
app/assets/javascripts/issue_show/incident.js
0 → 100644
View file @
9144ffb5
import
Vue
from
'
vue
'
;
import
issuableApp
from
'
./components/app.vue
'
;
import
incidentTabs
from
'
./components/incident_tabs.vue
'
;
export
default
function
initIssuableApp
(
issuableData
=
{})
{
return
new
Vue
({
el
:
document
.
getElementById
(
'
js-issuable-app
'
),
components
:
{
issuableApp
,
},
render
(
createElement
)
{
return
createElement
(
'
issuable-app
'
,
{
props
:
{
...
issuableData
,
descriptionComponent
:
incidentTabs
,
showTitleBorder
:
false
,
},
});
},
});
}
app/assets/javascripts/issue_show/i
ndex
.js
→
app/assets/javascripts/issue_show/i
ssue
.js
View file @
9144ffb5
import
Vue
from
'
vue
'
;
import
issuableApp
from
'
./components/app.vue
'
;
import
{
parseIssuableData
}
from
'
./utils/parse_data
'
;
export
default
function
initIssu
eableApp
(
)
{
export
default
function
initIssu
ableApp
(
issuableData
)
{
return
new
Vue
({
el
:
document
.
getElementById
(
'
js-issuable-app
'
),
components
:
{
...
...
@@ -10,7 +9,7 @@ export default function initIssueableApp() {
},
render
(
createElement
)
{
return
createElement
(
'
issuable-app
'
,
{
props
:
parseIssuableData
()
,
props
:
issuableData
,
});
},
});
...
...
app/assets/javascripts/pages/projects/issues/show.js
View file @
9144ffb5
...
...
@@ -4,14 +4,23 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import
ZenMode
from
'
~/zen_mode
'
;
import
'
~/notes/index
'
;
import
{
store
}
from
'
~/notes/stores
'
;
import
initIssueableApp
from
'
~/issue_show
'
;
import
initIssueApp
from
'
~/issue_show/issue
'
;
import
initIncidentApp
from
'
~/issue_show/incident
'
;
import
initIssuableHeaderWarning
from
'
~/vue_shared/components/issuable/init_issuable_header_warning
'
;
import
initSentryErrorStackTraceApp
from
'
~/sentry_error_stack_trace
'
;
import
initRelatedMergeRequestsApp
from
'
~/related_merge_requests
'
;
import
initVueIssuableSidebarApp
from
'
~/issuable_sidebar/sidebar_bundle
'
;
import
{
parseIssuableData
}
from
'
~/issue_show/utils/parse_data
'
;
export
default
function
()
{
initIssueableApp
();
const
{
issueType
,
...
issuableData
}
=
parseIssuableData
();
if
(
issueType
===
'
incident
'
)
{
initIncidentApp
(
issuableData
);
}
else
{
initIssueApp
(
issuableData
);
}
initIssuableHeaderWarning
(
store
);
initSentryErrorStackTraceApp
();
initRelatedMergeRequestsApp
();
...
...
app/helpers/issuables_helper.rb
View file @
9144ffb5
...
...
@@ -292,6 +292,7 @@ module IssuablesHelper
{
hasClosingMergeRequest:
issuable
.
merge_requests_count
(
current_user
)
!=
0
,
issueType:
issuable
.
issue_type
,
zoomMeetingUrl:
ZoomMeeting
.
canonical_meeting_url
(
issuable
),
sentryIssueIdentifier:
SentryIssue
.
find_by
(
issue:
issuable
)
&
.
sentry_issue_identifier
# rubocop:disable CodeReuse/ActiveRecord
}
...
...
@@ -301,8 +302,8 @@ module IssuablesHelper
return
{
groupPath:
parent
.
path
}
if
parent
.
is_a?
(
Group
)
{
projectPath:
ref_project
.
path
,
projectNamespace:
ref_project
.
namespace
.
full_path
projectPath:
ref_project
.
path
,
projectNamespace:
ref_project
.
namespace
.
full_path
}
end
...
...
changelogs/unreleased/tr-incident-tabs.yml
0 → 100644
View file @
9144ffb5
---
title
:
Add Summary tab for incident issues
merge_request
:
39822
author
:
type
:
added
spec/features/issues/incident_issue_spec.rb
0 → 100644
View file @
9144ffb5
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'Incident Detail'
,
:js
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
,
:public
)
}
let
(
:incident
)
{
create
(
:issue
,
project:
project
,
author:
user
,
issue_type:
'incident'
,
description:
'hello'
)
}
context
'when user displays the incident'
do
before
do
visit
project_issue_path
(
project
,
incident
)
wait_for_requests
end
it
'shows the incident tabs'
do
page
.
within
(
'.issuable-details'
)
do
incident_tabs
=
find
(
'[data-testid="incident-tabs"]'
)
expect
(
find
(
'h2'
)).
to
have_content
(
incident
.
title
)
expect
(
incident_tabs
).
to
have_content
(
'Summary'
)
expect
(
incident_tabs
).
to
have_content
(
incident
.
description
)
end
end
end
end
spec/frontend/issue_show/components/app_spec.js
View file @
9144ffb5
...
...
@@ -9,6 +9,9 @@ import '~/behaviors/markdown/render_gfm';
import
IssuableApp
from
'
~/issue_show/components/app.vue
'
;
import
eventHub
from
'
~/issue_show/event_hub
'
;
import
{
initialRequest
,
secondRequest
}
from
'
../mock_data
'
;
import
IncidentTabs
from
'
~/issue_show/components/incident_tabs.vue
'
;
import
DescriptionComponent
from
'
~/issue_show/components/description.vue
'
;
import
PinnedLinks
from
'
~/issue_show/components/pinned_links.vue
'
;
function
formatText
(
text
)
{
return
text
.
trim
().
replace
(
/
\s\s
+/g
,
'
'
);
...
...
@@ -22,6 +25,27 @@ const REALTIME_REQUEST_STACK = [initialRequest, secondRequest];
const
zoomMeetingUrl
=
'
https://gitlab.zoom.us/j/95919234811
'
;
const
publishedIncidentUrl
=
'
https://status.com/
'
;
const
defaultProps
=
{
canUpdate
:
true
,
canDestroy
:
true
,
endpoint
:
'
/gitlab-org/gitlab-shell/-/issues/9/realtime_changes
'
,
updateEndpoint
:
TEST_HOST
,
issuableRef
:
'
#1
'
,
issuableStatus
:
'
opened
'
,
initialTitleHtml
:
''
,
initialTitleText
:
''
,
initialDescriptionHtml
:
'
test
'
,
initialDescriptionText
:
'
test
'
,
lockVersion
:
1
,
markdownPreviewPath
:
'
/
'
,
markdownDocsPath
:
'
/
'
,
projectNamespace
:
'
/
'
,
projectPath
:
'
/
'
,
issuableTemplateNamesPath
:
'
/issuable-templates-path
'
,
zoomMeetingUrl
,
publishedIncidentUrl
,
};
describe
(
'
Issuable output
'
,
()
=>
{
useMockIntersectionObserver
();
...
...
@@ -31,6 +55,12 @@ describe('Issuable output', () => {
const
findStickyHeader
=
()
=>
wrapper
.
find
(
'
[data-testid="issue-sticky-header"]
'
);
const
mountComponent
=
(
props
=
{})
=>
{
wrapper
=
mount
(
IssuableApp
,
{
propsData
:
{
...
defaultProps
,
...
props
},
});
};
beforeEach
(()
=>
{
setFixtures
(
`
<div>
...
...
@@ -57,28 +87,7 @@ describe('Issuable output', () => {
return
res
;
});
wrapper
=
mount
(
IssuableApp
,
{
propsData
:
{
canUpdate
:
true
,
canDestroy
:
true
,
endpoint
:
'
/gitlab-org/gitlab-shell/-/issues/9/realtime_changes
'
,
updateEndpoint
:
TEST_HOST
,
issuableRef
:
'
#1
'
,
issuableStatus
:
'
opened
'
,
initialTitleHtml
:
''
,
initialTitleText
:
''
,
initialDescriptionHtml
:
'
test
'
,
initialDescriptionText
:
'
test
'
,
lockVersion
:
1
,
markdownPreviewPath
:
'
/
'
,
markdownDocsPath
:
'
/
'
,
projectNamespace
:
'
/
'
,
projectPath
:
'
/
'
,
issuableTemplateNamesPath
:
'
/issuable-templates-path
'
,
zoomMeetingUrl
,
publishedIncidentUrl
,
},
});
mountComponent
();
});
afterEach
(()
=>
{
...
...
@@ -562,4 +571,46 @@ describe('Issuable output', () => {
});
});
});
describe
(
'
Composable description component
'
,
()
=>
{
const
findIncidentTabs
=
()
=>
wrapper
.
find
(
IncidentTabs
);
const
findDescriptionComponent
=
()
=>
wrapper
.
find
(
DescriptionComponent
);
const
findPinnedLinks
=
()
=>
wrapper
.
find
(
PinnedLinks
);
const
borderClass
=
'
gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-mb-6
'
;
describe
(
'
when using description component
'
,
()
=>
{
it
(
'
renders the description component
'
,
()
=>
{
expect
(
findDescriptionComponent
().
exists
()).
toBe
(
true
);
});
it
(
'
does not render incident tabs
'
,
()
=>
{
expect
(
findIncidentTabs
().
exists
()).
toBe
(
false
);
});
it
(
'
adds a border below the header
'
,
()
=>
{
expect
(
findPinnedLinks
().
attributes
(
'
class
'
)).
toContain
(
borderClass
);
});
});
describe
(
'
when using incident tabs description wrapper
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
({
descriptionComponent
:
IncidentTabs
,
showTitleBorder
:
false
,
});
});
it
(
'
renders the description component
'
,
()
=>
{
expect
(
findDescriptionComponent
().
exists
()).
toBe
(
true
);
});
it
(
'
renders incident tabs
'
,
()
=>
{
expect
(
findIncidentTabs
().
exists
()).
toBe
(
true
);
});
it
(
'
does not add a border below the header
'
,
()
=>
{
expect
(
findPinnedLinks
().
attributes
(
'
class
'
)).
not
.
toContain
(
borderClass
);
});
});
});
});
spec/frontend/issue_show/components/description_spec.js
View file @
9144ffb5
...
...
@@ -5,20 +5,13 @@ import mountComponent from 'helpers/vue_mount_component_helper';
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
Description
from
'
~/issue_show/components/description.vue
'
;
import
TaskList
from
'
~/task_list
'
;
import
{
descriptionProps
as
props
}
from
'
../mock_data
'
;
jest
.
mock
(
'
~/task_list
'
);
describe
(
'
Description component
'
,
()
=>
{
let
vm
;
let
DescriptionComponent
;
const
props
=
{
canUpdate
:
true
,
descriptionHtml
:
'
test
'
,
descriptionText
:
'
test
'
,
updatedAt
:
new
Date
().
toString
(),
taskStatus
:
''
,
updateUrl
:
TEST_HOST
,
};
beforeEach
(()
=>
{
DescriptionComponent
=
Vue
.
extend
(
Description
);
...
...
spec/frontend/issue_show/components/incident_tabs_spec.js
0 → 100644
View file @
9144ffb5
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlTab
}
from
'
@gitlab/ui
'
;
import
IncidentTabs
from
'
~/issue_show/components/incident_tabs.vue
'
;
import
{
descriptionProps
}
from
'
../mock_data
'
;
import
DescriptionComponent
from
'
~/issue_show/components/description.vue
'
;
describe
(
'
Incident Tabs component
'
,
()
=>
{
let
wrapper
;
const
mountComponent
=
()
=>
{
wrapper
=
shallowMount
(
IncidentTabs
,
{
propsData
:
{
...
descriptionProps
,
},
stubs
:
{
DescriptionComponent
:
true
,
},
});
};
beforeEach
(()
=>
{
mountComponent
();
});
const
findTabs
=
()
=>
wrapper
.
findAll
(
GlTab
);
const
findSummaryTab
=
()
=>
findTabs
().
at
(
0
);
const
findDescriptionComponent
=
()
=>
wrapper
.
find
(
DescriptionComponent
);
describe
(
'
default state
'
,
()
=>
{
it
(
'
renders the summary tab
'
,
async
()
=>
{
expect
(
findTabs
()).
toHaveLength
(
1
);
expect
(
findSummaryTab
().
exists
()).
toBe
(
true
);
expect
(
findSummaryTab
().
attributes
(
'
title
'
)).
toBe
(
'
Summary
'
);
});
it
(
'
renders the description component
'
,
()
=>
{
expect
(
findDescriptionComponent
().
exists
()).
toBe
(
true
);
});
it
(
'
passes all props to the description component
'
,
()
=>
{
expect
(
findDescriptionComponent
().
props
()).
toMatchObject
(
descriptionProps
);
});
});
});
spec/frontend/issue_show/i
ndex
_spec.js
→
spec/frontend/issue_show/i
ssue
_spec.js
View file @
9144ffb5
import
initIssueableApp
from
'
~/issue_show
'
;
import
initIssuableApp
from
'
~/issue_show/issue
'
;
import
{
parseIssuableData
}
from
'
~/issue_show/utils/parse_data
'
;
describe
(
'
Issue show index
'
,
()
=>
{
describe
(
'
initIssueableApp
'
,
()
=>
{
it
(
'
should initialize app with no potential XSS attack
'
,
()
=>
{
// Warning: this test is currently faulty.
// More details at https://gitlab.com/gitlab-org/gitlab/-/issues/241717
// eslint-disable-next-line jest/no-disabled-tests
it
.
skip
(
'
should initialize app with no potential XSS attack
'
,
()
=>
{
const
d
=
document
.
createElement
(
'
div
'
);
d
.
id
=
'
js-issuable-app-initial-data
'
;
d
.
innerHTML
=
JSON
.
stringify
({
initialDescriptionHtml
:
'
<img src=x onerror=alert(1)>
'
,
});
document
.
body
.
appendChild
(
d
);
const
alertSpy
=
jest
.
spyOn
(
window
,
'
alert
'
);
initIssueableApp
();
const
issuableData
=
parseIssuableData
();
initIssuableApp
(
issuableData
);
expect
(
alertSpy
).
not
.
toHaveBeenCalled
();
});
...
...
spec/frontend/issue_show/mock_data.js
View file @
9144ffb5
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
export
const
initialRequest
=
{
title
:
'
<p>this is a title</p>
'
,
title_text
:
'
this is a title
'
,
...
...
@@ -21,3 +23,11 @@ export const secondRequest = {
updated_by_path
:
'
/other_user
'
,
lock_version
:
2
,
};
export
const
descriptionProps
=
{
canUpdate
:
true
,
descriptionHtml
:
'
test
'
,
descriptionText
:
'
test
'
,
taskStatus
:
''
,
updateUrl
:
TEST_HOST
,
};
spec/helpers/issuables_helper_spec.rb
View file @
9144ffb5
...
...
@@ -197,7 +197,8 @@ RSpec.describe IssuablesHelper do
initialTitleText:
issue
.
title
,
initialDescriptionHtml:
'<p dir="auto">issue text</p>'
,
initialDescriptionText:
'issue text'
,
initialTaskStatus:
'0 of 0 tasks completed'
initialTaskStatus:
'0 of 0 tasks completed'
,
issueType:
'issue'
}
expect
(
helper
.
issuable_initial_data
(
issue
)).
to
match
(
hash_including
(
expected_data
))
end
...
...
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