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
b8955f6d
Commit
b8955f6d
authored
Apr 23, 2021
by
Lee Tickett
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add issuable time tracking report
Changelog: added
parent
a309f510
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
438 additions
and
6 deletions
+438
-6
app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
...aphql_shared/fragments/issuable_timelogs.fragment.graphql
+10
-0
app/assets/javascripts/sidebar/components/time_tracking/report.vue
...s/javascripts/sidebar/components/time_tracking/report.vue
+102
-0
app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
...scripts/sidebar/components/time_tracking/time_tracker.vue
+23
-1
app/assets/javascripts/sidebar/constants.js
app/assets/javascripts/sidebar/constants.js
+11
-0
app/assets/javascripts/sidebar/mount_sidebar.js
app/assets/javascripts/sidebar/mount_sidebar.js
+4
-4
app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql
...mponents/sidebar/queries/get_issue_timelogs.query.graphql
+14
-0
app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql
.../components/sidebar/queries/get_mr_timelogs.query.graphql
+14
-0
app/graphql/types/timelog_type.rb
app/graphql/types/timelog_type.rb
+4
-0
changelogs/unreleased/271409-time-tracking-reports.yml
changelogs/unreleased/271409-time-tracking-reports.yml
+5
-0
doc/user/project/img/time_tracking_report_v13_12.png
doc/user/project/img/time_tracking_report_v13_12.png
+0
-0
doc/user/project/img/time_tracking_sidebar_v13_12.png
doc/user/project/img/time_tracking_sidebar_v13_12.png
+0
-0
doc/user/project/img/time_tracking_sidebar_v8_16.png
doc/user/project/img/time_tracking_sidebar_v8_16.png
+0
-0
doc/user/project/time_tracking.md
doc/user/project/time_tracking.md
+15
-1
locale/gitlab.pot
locale/gitlab.pot
+9
-0
spec/frontend/sidebar/components/time_tracking/mock_data.js
spec/frontend/sidebar/components/time_tracking/mock_data.js
+102
-0
spec/frontend/sidebar/components/time_tracking/report_spec.js
.../frontend/sidebar/components/time_tracking/report_spec.js
+97
-0
spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
...end/sidebar/components/time_tracking/time_tracker_spec.js
+28
-0
No files found.
app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
0 → 100644
View file @
b8955f6d
fragment
TimelogFragment
on
Timelog
{
timeSpent
user
{
name
}
spentAt
note
{
body
}
}
app/assets/javascripts/sidebar/components/time_tracking/report.vue
0 → 100644
View file @
b8955f6d
<
script
>
import
{
GlLoadingIcon
,
GlTable
}
from
'
@gitlab/ui
'
;
import
createFlash
from
'
~/flash
'
;
import
{
convertToGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
formatDate
,
parseSeconds
,
stringifyTime
}
from
'
~/lib/utils/datetime_utility
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
timelogQueries
}
from
'
~/sidebar/constants
'
;
const
TIME_DATE_FORMAT
=
'
mmmm d, yyyy, HH:MM ("UTC:" o)
'
;
export
default
{
components
:
{
GlLoadingIcon
,
GlTable
,
},
inject
:
[
'
issuableId
'
,
'
issuableType
'
],
data
()
{
return
{
report
:
[],
isLoading
:
true
};
},
apollo
:
{
report
:
{
query
()
{
return
timelogQueries
[
this
.
issuableType
].
query
;
},
variables
()
{
return
{
id
:
convertToGraphQLId
(
this
.
getGraphQLEntityType
(),
this
.
issuableId
),
};
},
update
(
data
)
{
this
.
isLoading
=
false
;
return
this
.
extractTimelogs
(
data
);
},
error
()
{
createFlash
({
message
:
__
(
'
Something went wrong. Please try again.
'
)
});
},
},
},
methods
:
{
isIssue
()
{
return
this
.
issuableType
===
'
issue
'
;
},
getGraphQLEntityType
()
{
// eslint-disable-next-line @gitlab/require-i18n-strings
return
this
.
isIssue
()
?
'
Issue
'
:
'
MergeRequest
'
;
},
extractTimelogs
(
data
)
{
const
timelogs
=
data
?.
issuable
?.
timelogs
?.
nodes
||
[];
return
timelogs
.
slice
().
sort
((
a
,
b
)
=>
new
Date
(
a
.
spentAt
)
-
new
Date
(
b
.
spentAt
));
},
formatDate
(
date
)
{
return
formatDate
(
date
,
TIME_DATE_FORMAT
);
},
getNote
(
note
)
{
return
note
?.
body
;
},
getTotalTimeSpent
()
{
const
seconds
=
this
.
report
.
reduce
((
acc
,
item
)
=>
acc
+
item
.
timeSpent
,
0
);
return
this
.
formatTimeSpent
(
seconds
);
},
formatTimeSpent
(
seconds
)
{
const
negative
=
seconds
<
0
;
return
(
negative
?
'
-
'
:
''
)
+
stringifyTime
(
parseSeconds
(
seconds
));
},
},
fields
:
[
{
key
:
'
spentAt
'
,
label
:
__
(
'
Spent At
'
),
sortable
:
true
},
{
key
:
'
user
'
,
label
:
__
(
'
User
'
),
sortable
:
true
},
{
key
:
'
timeSpent
'
,
label
:
__
(
'
Time Spent
'
),
sortable
:
true
},
{
key
:
'
note
'
,
label
:
__
(
'
Note
'
),
sortable
:
true
},
],
};
</
script
>
<
template
>
<div>
<div
v-if=
"isLoading"
><gl-loading-icon
size=
"md"
/></div>
<gl-table
v-else
:items=
"report"
:fields=
"$options.fields"
foot-clone
>
<template
#cell(spentAt)=
"
{ item: { spentAt } }">
<div>
{{
formatDate
(
spentAt
)
}}
</div>
</
template
>
<
template
#foot
(
spentAt
)
>
</
template
>
<
template
#cell(user)=
"{ item: { user } }"
>
<div>
{{
user
.
name
}}
</div>
</
template
>
<
template
#foot
(
user
)
>
</
template
>
<
template
#cell(timeSpent)=
"{ item: { timeSpent } }"
>
<div>
{{
formatTimeSpent
(
timeSpent
)
}}
</div>
</
template
>
<
template
#foot
(
timeSpent
)
>
<div>
{{
getTotalTimeSpent
()
}}
</div>
</
template
>
<
template
#cell(note)=
"{ item: { note } }"
>
<div>
{{
getNote
(
note
)
}}
</div>
</
template
>
<
template
#foot
(
note
)
>
</
template
>
</gl-table>
</div>
</template>
app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
View file @
b8955f6d
<
script
>
import
{
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
GlIcon
,
GlLink
,
GlModal
,
GlModalDirective
}
from
'
@gitlab/ui
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
eventHub
from
'
../../event_hub
'
;
import
TimeTrackingCollapsedState
from
'
./collapsed_state.vue
'
;
import
TimeTrackingComparisonPane
from
'
./comparison_pane.vue
'
;
import
TimeTrackingHelpState
from
'
./help_state.vue
'
;
import
TimeTrackingReport
from
'
./report.vue
'
;
import
TimeTrackingSpentOnlyPane
from
'
./spent_only_pane.vue
'
;
export
default
{
...
...
@@ -15,10 +16,16 @@ export default {
},
components
:
{
GlIcon
,
GlLink
,
GlModal
,
TimeTrackingCollapsedState
,
TimeTrackingSpentOnlyPane
,
TimeTrackingComparisonPane
,
TimeTrackingHelpState
,
TimeTrackingReport
,
},
directives
:
{
GlModal
:
GlModalDirective
,
},
props
:
{
timeEstimate
:
{
...
...
@@ -160,6 +167,21 @@ export default {
:time-estimate-human-readable=
"humanTimeEstimate"
:limit-to-hours=
"limitToHours"
/>
<gl-link
v-if=
"hasTimeSpent"
v-gl-modal=
"'time-tracking-report'"
data-testid=
"reportLink"
href=
"#"
class=
"btn-link"
>
{{
__
(
'
Time tracking report
'
)
}}
</gl-link
>
<gl-modal
modal-id=
"time-tracking-report"
:title=
"__('Time tracking report')"
:hide-footer=
"true"
>
<time-tracking-report
/>
</gl-modal>
<transition
name=
"help-state-toggle"
>
<time-tracking-help-state
v-if=
"showHelpState"
/>
</transition>
...
...
app/assets/javascripts/sidebar/constants.js
View file @
b8955f6d
...
...
@@ -21,8 +21,10 @@ import updateIssueSubscriptionMutation from '~/sidebar/queries/update_issue_subs
import
updateMergeRequestSubscriptionMutation
from
'
~/sidebar/queries/update_merge_request_subscription.mutation.graphql
'
;
import
getIssueAssignees
from
'
~/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql
'
;
import
issueParticipantsQuery
from
'
~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql
'
;
import
getIssueTimelogsQuery
from
'
~/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql
'
;
import
getMergeRequestAssignees
from
'
~/vue_shared/components/sidebar/queries/get_mr_assignees.query.graphql
'
;
import
getMergeRequestParticipants
from
'
~/vue_shared/components/sidebar/queries/get_mr_participants.query.graphql
'
;
import
getMrTimelogsQuery
from
'
~/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql
'
;
import
updateIssueAssigneesMutation
from
'
~/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql
'
;
import
updateMergeRequestAssigneesMutation
from
'
~/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql
'
;
...
...
@@ -122,3 +124,12 @@ export const startDateQueries = {
mutation
:
updateEpicStartDateMutation
,
},
};
export
const
timelogQueries
=
{
[
IssuableType
.
Issue
]:
{
query
:
getIssueTimelogsQuery
,
},
[
IssuableType
.
MergeRequest
]:
{
query
:
getMrTimelogsQuery
,
},
};
app/assets/javascripts/sidebar/mount_sidebar.js
View file @
b8955f6d
...
...
@@ -367,16 +367,16 @@ function mountSubscriptionsComponent() {
function
mountTimeTrackingComponent
()
{
const
el
=
document
.
getElementById
(
'
issuable-time-tracker
'
);
const
{
id
,
issuableType
}
=
getSidebarOptions
();
if
(
!
el
)
return
;
// eslint-disable-next-line no-new
new
Vue
({
el
,
components
:
{
SidebarTimeTracking
,
},
render
:
(
createElement
)
=>
createElement
(
'
sidebar-time-tracking
'
,
{}),
apolloProvider
,
provide
:
{
issuableId
:
id
,
issuableType
},
render
:
(
createElement
)
=>
createElement
(
SidebarTimeTracking
,
{}),
});
}
...
...
app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql
0 → 100644
View file @
b8955f6d
#import "~/graphql_shared/fragments/issuable_timelogs.fragment.graphql"
query
timeTrackingReport
(
$id
:
IssueID
!)
{
issuable
:
issue
(
id
:
$id
)
{
__typename
id
title
timelogs
{
nodes
{
...
TimelogFragment
}
}
}
}
app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql
0 → 100644
View file @
b8955f6d
#import "~/graphql_shared/fragments/issuable_timelogs.fragment.graphql"
query
timeTrackingReport
(
$id
:
MergeRequestID
!)
{
issuable
:
mergeRequest
(
id
:
$id
)
{
__typename
id
title
timelogs
{
nodes
{
...
TimelogFragment
}
}
}
}
app/graphql/types/timelog_type.rb
View file @
b8955f6d
...
...
@@ -43,5 +43,9 @@ module Types
def
issue
Gitlab
::
Graphql
::
Loaders
::
BatchModelLoader
.
new
(
Issue
,
object
.
issue_id
).
find
end
def
spent_at
object
.
spent_at
||
object
.
created_at
end
end
end
changelogs/unreleased/271409-time-tracking-reports.yml
0 → 100644
View file @
b8955f6d
---
title
:
Add isuable time tracking report
merge_request
:
60161
author
:
Lee Tickett @leetickett
type
:
added
doc/user/project/img/time_tracking_report_v13_12.png
0 → 100644
View file @
b8955f6d
12.8 KB
doc/user/project/img/time_tracking_sidebar_v13_12.png
0 → 100644
View file @
b8955f6d
5.67 KB
doc/user/project/img/time_tracking_sidebar_v8_16.png
deleted
100644 → 0
View file @
a309f510
8.86 KB
doc/user/project/time_tracking.md
View file @
b8955f6d
...
...
@@ -20,13 +20,14 @@ Time Tracking allows you to:
-
Record the time spent working on an issue or a merge request.
-
Add an estimate of the amount of time needed to complete an issue or a merge
request.
-
View a breakdown of time spent working on an issue or a merge request.
You don't have to indicate an estimate to enter the time spent, and vice versa.
Data about time tracking is shown on the issue/merge request sidebar, as shown
below.
![
Time tracking in the sidebar
](
img/time_tracking_sidebar_v
8_16
.png
)
![
Time tracking in the sidebar
](
img/time_tracking_sidebar_v
13_12
.png
)
## How to enter data
...
...
@@ -75,6 +76,19 @@ command fails and no time is logged.
To remove all the time spent at once, use
`/remove_time_spent`
.
## View a time tracking report
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/271409) in GitLab 13.12.
You can view a breakdown of time spent on an issue or merge request.
To view a time tracking report, go to an issue or a merge request and select
**Time tracking report**
in the right sidebar.
![
Time tracking report
](
img/time_tracking_report_v13_12.png
)
The breakdown of spent time is limited to a maximum of 100 entries.
## Configuration
The following time units are available:
...
...
locale/gitlab.pot
View file @
b8955f6d
...
...
@@ -30488,6 +30488,9 @@ msgstr ""
msgid "Speed up your pipelines with Needs relationships"
msgstr ""
msgid "Spent At"
msgstr ""
msgid "Squash commit message"
msgstr ""
...
...
@@ -33362,6 +33365,9 @@ msgstr ""
msgid "Time"
msgstr ""
msgid "Time Spent"
msgstr ""
msgid "Time based: Yes"
msgstr ""
...
...
@@ -33413,6 +33419,9 @@ msgstr ""
msgid "Time tracking"
msgstr ""
msgid "Time tracking report"
msgstr ""
msgid "Time until first merge request"
msgstr ""
...
...
spec/frontend/sidebar/components/time_tracking/mock_data.js
0 → 100644
View file @
b8955f6d
export
const
getIssueTimelogsQueryResponse
=
{
data
:
{
issuable
:
{
__typename
:
'
Issue
'
,
id
:
'
gid://gitlab/Issue/148
'
,
title
:
'
Est perferendis dicta expedita ipsum adipisci laudantium omnis consequatur consequatur et.
'
,
timelogs
:
{
nodes
:
[
{
__typename
:
'
Timelog
'
,
timeSpent
:
14400
,
user
:
{
name
:
'
John Doe18
'
,
__typename
:
'
UserCore
'
,
},
spentAt
:
'
2020-05-01T00:00:00Z
'
,
note
:
{
body
:
'
I paired with @root on this last week.
'
,
__typename
:
'
Note
'
,
},
},
{
__typename
:
'
Timelog
'
,
timeSpent
:
1800
,
user
:
{
name
:
'
Administrator
'
,
__typename
:
'
UserCore
'
,
},
spentAt
:
'
2021-05-07T13:19:01Z
'
,
note
:
null
,
},
{
__typename
:
'
Timelog
'
,
timeSpent
:
14400
,
user
:
{
name
:
'
Administrator
'
,
__typename
:
'
UserCore
'
,
},
spentAt
:
'
2021-05-01T00:00:00Z
'
,
note
:
{
body
:
'
I did some work on this last week.
'
,
__typename
:
'
Note
'
,
},
},
],
__typename
:
'
TimelogConnection
'
,
},
},
},
};
export
const
getMrTimelogsQueryResponse
=
{
data
:
{
issuable
:
{
__typename
:
'
MergeRequest
'
,
id
:
'
gid://gitlab/MergeRequest/29
'
,
title
:
'
Esse amet perspiciatis voluptas et sed praesentium debitis repellat.
'
,
timelogs
:
{
nodes
:
[
{
__typename
:
'
Timelog
'
,
timeSpent
:
1800
,
user
:
{
name
:
'
Administrator
'
,
__typename
:
'
UserCore
'
,
},
spentAt
:
'
2021-05-07T14:44:55Z
'
,
note
:
{
body
:
'
Thirty minutes!
'
,
__typename
:
'
Note
'
,
},
},
{
__typename
:
'
Timelog
'
,
timeSpent
:
3600
,
user
:
{
name
:
'
Administrator
'
,
__typename
:
'
UserCore
'
,
},
spentAt
:
'
2021-05-07T14:44:39Z
'
,
note
:
null
,
},
{
__typename
:
'
Timelog
'
,
timeSpent
:
300
,
user
:
{
name
:
'
Administrator
'
,
__typename
:
'
UserCore
'
,
},
spentAt
:
'
2021-03-10T00:00:00Z
'
,
note
:
{
body
:
'
A note with some time
'
,
__typename
:
'
Note
'
,
},
},
],
__typename
:
'
TimelogConnection
'
,
},
},
},
};
spec/frontend/sidebar/components/time_tracking/report_spec.js
0 → 100644
View file @
b8955f6d
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
getAllByRole
}
from
'
@testing-library/dom
'
;
import
{
shallowMount
,
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
createFlash
from
'
~/flash
'
;
import
Report
from
'
~/sidebar/components/time_tracking/report.vue
'
;
import
getIssueTimelogsQuery
from
'
~/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql
'
;
import
getMrTimelogsQuery
from
'
~/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql
'
;
import
{
getIssueTimelogsQueryResponse
,
getMrTimelogsQueryResponse
}
from
'
./mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
describe
(
'
Issuable Time Tracking Report
'
,
()
=>
{
const
localVue
=
createLocalVue
();
localVue
.
use
(
VueApollo
);
let
wrapper
;
let
fakeApollo
;
const
findLoadingIcon
=
()
=>
wrapper
.
findComponent
(
GlLoadingIcon
);
const
successIssueQueryHandler
=
jest
.
fn
().
mockResolvedValue
(
getIssueTimelogsQueryResponse
);
const
successMrQueryHandler
=
jest
.
fn
().
mockResolvedValue
(
getMrTimelogsQueryResponse
);
const
mountComponent
=
({
queryHandler
=
successIssueQueryHandler
,
issuableType
=
'
issue
'
,
mountFunction
=
shallowMount
,
}
=
{})
=>
{
fakeApollo
=
createMockApollo
([
[
getIssueTimelogsQuery
,
queryHandler
],
[
getMrTimelogsQuery
,
queryHandler
],
]);
wrapper
=
mountFunction
(
Report
,
{
provide
:
{
issuableId
:
1
,
issuableType
,
},
localVue
,
apolloProvider
:
fakeApollo
,
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
fakeApollo
=
null
;
});
it
(
'
should render loading spinner
'
,
()
=>
{
mountComponent
();
expect
(
findLoadingIcon
()).
toExist
();
});
it
(
'
should render error message on reject
'
,
async
()
=>
{
mountComponent
({
queryHandler
:
jest
.
fn
().
mockRejectedValue
(
'
ERROR
'
)
});
await
waitForPromises
();
expect
(
createFlash
).
toHaveBeenCalled
();
});
describe
(
'
for issue
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
({
mountFunction
:
mount
});
});
it
(
'
calls correct query
'
,
()
=>
{
expect
(
successIssueQueryHandler
).
toHaveBeenCalled
();
});
it
(
'
renders correct results
'
,
async
()
=>
{
await
waitForPromises
();
expect
(
getAllByRole
(
wrapper
.
element
,
'
row
'
,
{
name
:
/John Doe18/i
})).
toHaveLength
(
1
);
expect
(
getAllByRole
(
wrapper
.
element
,
'
row
'
,
{
name
:
/Administrator/i
})).
toHaveLength
(
2
);
});
});
describe
(
'
for merge request
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
({
queryHandler
:
successMrQueryHandler
,
issuableType
:
'
merge_request
'
,
mountFunction
:
mount
,
});
});
it
(
'
calls correct query
'
,
()
=>
{
expect
(
successMrQueryHandler
).
toHaveBeenCalled
();
});
it
(
'
renders correct results
'
,
async
()
=>
{
await
waitForPromises
();
expect
(
getAllByRole
(
wrapper
.
element
,
'
row
'
,
{
name
:
/Administrator/i
})).
toHaveLength
(
3
);
});
});
});
spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
View file @
b8955f6d
...
...
@@ -10,6 +10,7 @@ describe('Issuable Time Tracker', () => {
const
findComparisonMeter
=
()
=>
findByTestId
(
'
compareMeter
'
).
attributes
(
'
title
'
);
const
findCollapsedState
=
()
=>
findByTestId
(
'
collapsedState
'
);
const
findTimeRemainingProgress
=
()
=>
findByTestId
(
'
timeRemainingProgress
'
);
const
findReportLink
=
()
=>
findByTestId
(
'
reportLink
'
);
const
defaultProps
=
{
timeEstimate
:
10
_000
,
// 2h 46m
...
...
@@ -192,6 +193,33 @@ describe('Issuable Time Tracker', () => {
});
});
describe
(
'
Time tracking report
'
,
()
=>
{
describe
(
'
When no time spent
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mountComponent
({
props
:
{
timeSpent
:
0
,
timeSpentHumanReadable
:
''
,
},
});
});
it
(
'
link should not appear
'
,
()
=>
{
expect
(
findReportLink
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
When time spent
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mountComponent
();
});
it
(
'
link should appear
'
,
()
=>
{
expect
(
findReportLink
().
exists
()).
toBe
(
true
);
});
});
});
describe
(
'
Help pane
'
,
()
=>
{
const
findHelpButton
=
()
=>
findByTestId
(
'
helpButton
'
);
const
findCloseHelpButton
=
()
=>
findByTestId
(
'
closeHelpButton
'
);
...
...
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