Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
heptapod
heptapod
Commits
1bf89426c718
Commit
1c783007
authored
Apr 06, 2017
by
Regis Boudinot
Committed by
Jacob Schatz
Apr 06, 2017
Browse files
Issue title realtime
parent
4c9555470029
Changes
18
Hide whitespace changes
Inline
Side-by-side
app/assets/javascripts/issue_show/index.js
0 → 100644
View file @
1bf89426
import
Vue
from
'
vue
'
;
import
IssueTitle
from
'
./issue_title
'
;
import
'
../vue_shared/vue_resource_interceptor
'
;
const
vueOptions
=
()
=>
({
el
:
'
.issue-title-entrypoint
'
,
components
:
{
IssueTitle
,
},
data
()
{
const
issueTitleData
=
document
.
querySelector
(
'
.issue-title-data
'
).
dataset
;
return
{
initialTitle
:
issueTitleData
.
initialTitle
,
endpoint
:
issueTitleData
.
endpoint
,
};
},
template
:
`
<IssueTitle
:initialTitle="initialTitle"
:endpoint="endpoint"
/>
`
,
});
(()
=>
new
Vue
(
vueOptions
()))();
app/assets/javascripts/issue_show/issue_title.js
0 → 100644
View file @
1bf89426
import
Visibility
from
'
visibilityjs
'
;
import
Poll
from
'
./../lib/utils/poll
'
;
import
Service
from
'
./services/index
'
;
export
default
{
props
:
{
initialTitle
:
{
required
:
true
,
type
:
String
},
endpoint
:
{
required
:
true
,
type
:
String
},
},
data
()
{
const
resource
=
new
Service
(
this
.
$http
,
this
.
endpoint
);
const
poll
=
new
Poll
({
resource
,
method
:
'
getTitle
'
,
successCallback
:
(
res
)
=>
{
this
.
renderResponse
(
res
);
},
errorCallback
:
(
err
)
=>
{
if
(
process
.
env
.
NODE_ENV
!==
'
production
'
)
{
// eslint-disable-next-line no-console
console
.
error
(
'
ISSUE SHOW TITLE REALTIME ERROR
'
,
err
);
}
else
{
throw
new
Error
(
err
);
}
},
});
return
{
poll
,
timeoutId
:
null
,
title
:
this
.
initialTitle
,
};
},
methods
:
{
fetch
()
{
this
.
poll
.
makeRequest
();
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
this
.
poll
.
restart
();
}
else
{
this
.
poll
.
stop
();
}
});
},
renderResponse
(
res
)
{
const
body
=
JSON
.
parse
(
res
.
body
);
this
.
triggerAnimation
(
body
);
},
triggerAnimation
(
body
)
{
const
{
title
}
=
body
;
/**
* since opacity is changed, even if there is no diff for Vue to update
* we must check the title even on a 304 to ensure no visual change
*/
if
(
this
.
title
===
title
)
return
;
this
.
$el
.
style
.
opacity
=
0
;
this
.
timeoutId
=
setTimeout
(()
=>
{
this
.
title
=
title
;
this
.
$el
.
style
.
transition
=
'
opacity 0.2s ease
'
;
this
.
$el
.
style
.
opacity
=
1
;
clearTimeout
(
this
.
timeoutId
);
},
100
);
},
},
created
()
{
this
.
fetch
();
},
template
:
`
<h2 class='title' v-html='title'></h2>
`
,
};
app/assets/javascripts/issue_show/services/index.js
0 → 100644
View file @
1bf89426
export
default
class
Service
{
constructor
(
resource
,
endpoint
)
{
this
.
resource
=
resource
;
this
.
endpoint
=
endpoint
;
}
getTitle
()
{
return
this
.
resource
.
get
(
this
.
endpoint
);
}
}
app/assets/javascripts/vue_pipelines_index/stores/pipelines_store.js
View file @
1bf89426
/* eslint-disable no-underscore-dangle*/
import
'
../../vue_realtime_listener
'
;
import
VueRealtimeListener
from
'
../../vue_realtime_listener
'
;
export
default
class
PipelinesStore
{
constructor
()
{
...
...
@@ -56,6 +56,6 @@ export default class PipelinesStore {
const
removeIntervals
=
()
=>
clearInterval
(
this
.
timeLoopInterval
);
const
startIntervals
=
()
=>
startTimeLoops
();
gl
.
VueRealtimeListener
(
removeIntervals
,
startIntervals
);
VueRealtimeListener
(
removeIntervals
,
startIntervals
);
}
}
app/assets/javascripts/vue_realtime_listener/index.js
View file @
1bf89426
/* eslint-disable no-param-reassign */
((
gl
)
=>
{
gl
.
VueRealtimeListener
=
(
removeIntervals
,
startIntervals
)
=>
{
const
removeAll
=
()
=>
{
removeIntervals
();
window
.
removeEventListener
(
'
beforeunload
'
,
removeIntervals
);
window
.
removeEventListener
(
'
focus
'
,
startIntervals
);
window
.
removeEventListener
(
'
blur
'
,
removeIntervals
);
document
.
removeEventListener
(
'
beforeunload
'
,
removeAll
);
};
window
.
addEventListener
(
'
beforeunload
'
,
removeIntervals
);
window
.
addEventListener
(
'
focus
'
,
startIntervals
);
window
.
addEventListener
(
'
blur
'
,
removeIntervals
);
document
.
addEventListener
(
'
beforeunload
'
,
removeAll
);
// add removeAll methods to stack
const
stack
=
gl
.
VueRealtimeListener
.
reset
;
gl
.
VueRealtimeListener
.
reset
=
()
=>
{
gl
.
VueRealtimeListener
.
reset
=
stack
;
removeAll
();
stack
();
};
};
// remove all event listeners and intervals
gl
.
VueRealtimeListener
.
reset
=
()
=>
undefined
;
// noop
})(
window
.
gl
||
(
window
.
gl
=
{}));
export
default
(
removeIntervals
,
startIntervals
)
=>
{
window
.
removeEventListener
(
'
focus
'
,
startIntervals
);
window
.
removeEventListener
(
'
blur
'
,
removeIntervals
);
window
.
removeEventListener
(
'
onbeforeload
'
,
removeIntervals
);
window
.
addEventListener
(
'
focus
'
,
startIntervals
);
window
.
addEventListener
(
'
blur
'
,
removeIntervals
);
window
.
addEventListener
(
'
onbeforeload
'
,
removeIntervals
);
};
app/controllers/projects/issues_controller.rb
View file @
1bf89426
...
...
@@ -11,10 +11,10 @@ class Projects::IssuesController < Projects::ApplicationController
before_action
:redirect_to_external_issue_tracker
,
only:
[
:index
,
:new
]
before_action
:module_enabled
before_action
:issue
,
only:
[
:edit
,
:update
,
:show
,
:referenced_merge_requests
,
:related_branches
,
:can_create_branch
]
:related_branches
,
:can_create_branch
,
:rendered_title
]
# Allow read any issue
before_action
:authorize_read_issue!
,
only:
[
:show
]
before_action
:authorize_read_issue!
,
only:
[
:show
,
:rendered_title
]
# Allow write(create) issue
before_action
:authorize_create_issue!
,
only:
[
:new
,
:create
]
...
...
@@ -200,6 +200,11 @@ def can_create_branch
end
end
def
rendered_title
Gitlab
::
PollingInterval
.
set_header
(
response
,
interval:
3_000
)
render
json:
{
title:
view_context
.
markdown_field
(
@issue
,
:title
)
}
end
protected
def
issue
...
...
app/models/issue.rb
View file @
1bf89426
...
...
@@ -40,6 +40,8 @@ class Issue < ActiveRecord::Base
scope
:include_associations
,
->
{
includes
(
:assignee
,
:labels
,
project: :namespace
)
}
after_save
:expire_etag_cache
attr_spammable
:title
,
spam_title:
true
attr_spammable
:description
,
spam_description:
true
...
...
@@ -252,4 +254,13 @@ def readable_by?(user)
def
publicly_visible?
project
.
public?
&&
!
confidential?
end
def
expire_etag_cache
key
=
Gitlab
::
Routing
.
url_helpers
.
rendered_title_namespace_project_issue_path
(
project
.
namespace
,
project
,
self
)
Gitlab
::
EtagCaching
::
Store
.
new
.
touch
(
key
)
end
end
app/views/projects/issues/show.html.haml
View file @
1bf89426
...
...
@@ -49,11 +49,12 @@
=
link_to
'Submit as spam'
,
mark_as_spam_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
method: :post
,
class:
'hidden-xs hidden-sm btn btn-grouped btn-spam'
,
title:
'Submit as spam'
=
link_to
'Edit'
,
edit_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
class:
'hidden-xs hidden-sm btn btn-grouped issuable-edit'
.issue-details.issuable-details
.detail-page-description.content-block
{
class:
(
'hide-bottom-border'
unless
@issue
.
description
.
present?
)
}
%h2
.title
=
markdown_field
(
@issue
,
:title
)
.issue-title-data.hidden
{
"data"
=>
{
"initial-title"
=>
markdown_field
(
@issue
,
:title
),
"endpoint"
=>
rendered_title_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
}
}
.issue-title-entrypoint
-
if
@issue
.
description
.
present?
.description
{
class:
can?
(
current_user
,
:update_issue
,
@issue
)
?
'js-task-list-container'
:
''
}
.wiki
...
...
@@ -77,3 +78,5 @@
=
render
'projects/issues/discussion'
=
render
'shared/issuable/sidebar'
,
issuable:
@issue
=
page_specific_javascript_bundle_tag
(
'issue_show'
)
config/routes/project.rb
View file @
1bf89426
...
...
@@ -250,6 +250,7 @@
get
:referenced_merge_requests
get
:related_branches
get
:can_create_branch
get
:rendered_title
end
collection
do
post
:bulk_update
...
...
config/webpack.config.js
View file @
1bf89426
...
...
@@ -46,6 +46,7 @@ var config = {
u2f
:
[
'
vendor/u2f
'
],
users
:
'
./users/users_bundle.js
'
,
vue_pipelines
:
'
./vue_pipelines_index/index.js
'
,
issue_show
:
'
./issue_show/index.js
'
,
},
output
:
{
...
...
lib/gitlab/etag_caching/middleware.rb
View file @
1bf89426
...
...
@@ -3,7 +3,8 @@ module EtagCaching
class
Middleware
RESERVED_WORDS
=
NamespaceValidator
::
WILDCARD_ROUTES
.
map
{
|
word
|
"/
#{
word
}
/"
}.
join
(
'|'
)
ROUTE_REGEXP
=
Regexp
.
union
(
%r(^(?!.*(
#{
RESERVED_WORDS
}
)).*/noteable/issue/
\d
+/notes
\z
)
%r(^(?!.*(
#{
RESERVED_WORDS
}
)).*/noteable/issue/
\d
+/notes
\z
)
,
%r(^(?!.*(
#{
RESERVED_WORDS
}
)).*/issues/
\d
+/rendered_title
\z
)
)
def
initialize
(
app
)
...
...
spec/features/gitlab_flavored_markdown_spec.rb
View file @
1bf89426
...
...
@@ -48,7 +48,9 @@
end
end
describe
"for issues"
do
describe
"for issues"
,
feature:
true
,
js:
true
do
include
WaitForVueResource
before
do
@other_issue
=
create
(
:issue
,
author:
@user
,
...
...
@@ -79,6 +81,14 @@
expect
(
page
).
to
have_link
(
fred
.
to_reference
)
end
it
"renders updated subject once edited somewhere else in issues#show"
do
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
@issue
)
@issue
.
update
(
title:
"fix
#{
@other_issue
.
to_reference
}
and update"
)
wait_for_vue_resource
expect
(
page
).
to
have_text
(
"fix
#{
@other_issue
.
to_reference
}
and update"
)
end
end
describe
"for merge requests"
do
...
...
spec/features/issues/award_emoji_spec.rb
View file @
1bf89426
...
...
@@ -2,6 +2,7 @@
describe
'Awards Emoji'
,
feature:
true
do
include
WaitForAjax
include
WaitForVueResource
let!
(
:project
)
{
create
(
:project
,
:public
)
}
let!
(
:user
)
{
create
(
:user
)
}
...
...
@@ -22,10 +23,11 @@
# The `heart_tip` emoji is not valid anymore so we need to skip validation
issue
.
award_emoji
.
build
(
user:
user
,
name:
'heart_tip'
).
save!
(
validate:
false
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
wait_for_vue_resource
end
# Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
it
'does not shows a 500 page'
do
it
'does not shows a 500 page'
,
js:
true
do
expect
(
page
).
to
have_text
(
issue
.
title
)
end
end
...
...
@@ -35,6 +37,7 @@
before
do
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
wait_for_vue_resource
end
it
'increments the thumbsdown emoji'
,
js:
true
do
...
...
spec/features/issues/move_spec.rb
View file @
1bf89426
...
...
@@ -37,8 +37,8 @@
edit_issue
(
issue
)
end
scenario
'moving issue to another project'
do
fi
rst
(
'#move_to_project_id'
,
visible:
false
).
set
(
new_project
.
id
)
scenario
'moving issue to another project'
,
js:
true
do
fi
nd
(
'#move_to_project_id'
,
visible:
false
).
set
(
new_project
.
id
)
click_button
(
'Save changes'
)
expect
(
current_url
).
to
include
project_path
(
new_project
)
...
...
spec/features/issues/spam_issues_spec.rb
View file @
1bf89426
require
'rails_helper'
describe
'New issue'
,
feature:
true
do
describe
'New issue'
,
feature:
true
,
js:
true
do
include
StubENV
let
(
:project
)
{
create
(
:project
,
:public
)
}
...
...
spec/features/issues_spec.rb
View file @
1bf89426
...
...
@@ -695,4 +695,21 @@
end
end
end
describe
'title issue#show'
,
js:
true
do
include
WaitForVueResource
it
'updates the title'
,
js:
true
do
issue
=
create
(
:issue
,
author:
@user
,
assignee:
@user
,
project:
project
,
title:
'new title'
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
expect
(
page
).
to
have_text
(
"new title"
)
issue
.
update
(
title:
"updated title"
)
wait_for_vue_resource
expect
(
page
).
to
have_text
(
"updated title"
)
end
end
end
spec/javascripts/issue_show/issue_title_spec.js
0 → 100644
View file @
1bf89426
import
Vue
from
'
vue
'
;
import
issueTitle
from
'
~/issue_show/issue_title
'
;
describe
(
'
Issue Title
'
,
()
=>
{
let
IssueTitleComponent
;
beforeEach
(()
=>
{
IssueTitleComponent
=
Vue
.
extend
(
issueTitle
);
});
it
(
'
should render a title
'
,
()
=>
{
const
component
=
new
IssueTitleComponent
({
propsData
:
{
initialTitle
:
'
wow
'
,
endpoint
:
'
/gitlab-org/gitlab-shell/issues/9/rendered_title
'
,
},
}).
$mount
();
expect
(
component
.
$el
.
classList
).
toContain
(
'
title
'
);
expect
(
component
.
$el
.
innerHTML
).
toContain
(
'
wow
'
);
});
});
spec/javascripts/test_bundle.js
View file @
1bf89426
...
...
@@ -64,6 +64,7 @@ if (process.env.BABEL_ENV === 'coverage') {
'
./snippet/snippet_bundle.js
'
,
'
./terminal/terminal_bundle.js
'
,
'
./users/users_bundle.js
'
,
'
./issue_show/index.js
'
,
];
describe
(
'
Uncovered files
'
,
function
()
{
...
...
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