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
d36ce37d
Commit
d36ce37d
authored
Jul 06, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
aa2bedca
fb9f861a
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
509 additions
and
270 deletions
+509
-270
app/helpers/issuables_helper.rb
app/helpers/issuables_helper.rb
+0
-9
app/models/integrations/jenkins.rb
app/models/integrations/jenkins.rb
+0
-1
app/views/shared/issuable/_sidebar.html.haml
app/views/shared/issuable/_sidebar.html.haml
+1
-1
config/feature_flags/development/database_reindexing_pg12.yml
...ig/feature_flags/development/database_reindexing_pg12.yml
+8
-0
ee/app/assets/javascripts/sidebar/components/status/sidebar_status.vue
.../javascripts/sidebar/components/status/sidebar_status.vue
+16
-124
ee/app/assets/javascripts/sidebar/constants.js
ee/app/assets/javascripts/sidebar/constants.js
+0
-13
ee/app/assets/javascripts/sidebar/mount_sidebar.js
ee/app/assets/javascripts/sidebar/mount_sidebar.js
+2
-8
ee/app/assets/javascripts/sidebar/queries/issue_health_status.query.graphql
...scripts/sidebar/queries/issue_health_status.query.graphql
+0
-11
ee/app/assets/javascripts/sidebar/sidebar_mediator.js
ee/app/assets/javascripts/sidebar/sidebar_mediator.js
+17
-0
ee/spec/frontend/sidebar/components/status/sidebar_status_spec.js
...frontend/sidebar/components/status/sidebar_status_spec.js
+35
-50
ee/spec/frontend/sidebar/ee_sidebar_mediator_spec.js
ee/spec/frontend/sidebar/ee_sidebar_mediator_spec.js
+25
-0
lib/gitlab/database/postgres_index.rb
lib/gitlab/database/postgres_index.rb
+10
-1
lib/gitlab/database/reindexing.rb
lib/gitlab/database/reindexing.rb
+8
-3
lib/gitlab/database/reindexing/coordinator.rb
lib/gitlab/database/reindexing/coordinator.rb
+7
-1
lib/gitlab/database/reindexing/reindex_concurrently.rb
lib/gitlab/database/reindexing/reindex_concurrently.rb
+136
-0
locale/gitlab.pot
locale/gitlab.pot
+0
-6
spec/db/schema_spec.rb
spec/db/schema_spec.rb
+12
-0
spec/lib/gitlab/database/postgres_index_spec.rb
spec/lib/gitlab/database/postgres_index_spec.rb
+18
-0
spec/lib/gitlab/database/reindexing/coordinator_spec.rb
spec/lib/gitlab/database/reindexing/coordinator_spec.rb
+57
-38
spec/lib/gitlab/database/reindexing/reindex_concurrently_spec.rb
...b/gitlab/database/reindexing/reindex_concurrently_spec.rb
+134
-0
spec/lib/gitlab/database/reindexing_spec.rb
spec/lib/gitlab/database/reindexing_spec.rb
+23
-4
No files found.
app/helpers/issuables_helper.rb
View file @
d36ce37d
...
@@ -425,15 +425,6 @@ module IssuablesHelper
...
@@ -425,15 +425,6 @@ module IssuablesHelper
}
}
end
end
def
sidebar_status_data
(
issuable_sidebar
,
project
)
{
iid:
issuable_sidebar
[
:iid
],
issuable_type:
issuable_sidebar
[
:type
],
full_path:
project
.
full_path
,
can_edit:
issuable_sidebar
.
dig
(
:current_user
,
:can_edit
).
to_s
}
end
def
parent
def
parent
@project
||
@group
@project
||
@group
end
end
...
...
app/models/integrations/jenkins.rb
View file @
d36ce37d
...
@@ -97,7 +97,6 @@ module Integrations
...
@@ -97,7 +97,6 @@ module Integrations
{
{
type:
'text'
,
type:
'text'
,
name:
'username'
,
name:
'username'
,
required:
true
,
help:
s_
(
'The username for the Jenkins server.'
)
help:
s_
(
'The username for the Jenkins server.'
)
},
},
{
{
...
...
app/views/shared/issuable/_sidebar.html.haml
View file @
d36ce37d
...
@@ -81,7 +81,7 @@
...
@@ -81,7 +81,7 @@
#js-severity
#js-severity
-
if
issuable_sidebar
.
dig
(
:features_available
,
:health_status
)
-
if
issuable_sidebar
.
dig
(
:features_available
,
:health_status
)
.js-sidebar-status-entry-point
{
data:
sidebar_status_data
(
issuable_sidebar
,
@project
)
}
.js-sidebar-status-entry-point
-
if
issuable_sidebar
.
has_key?
(
:confidential
)
-
if
issuable_sidebar
.
has_key?
(
:confidential
)
%script
#js-confidential-issue-data
{
type:
"application/json"
}=
{
is_confidential:
issuable_sidebar
[
:confidential
],
is_editable:
can_edit_issuable
}.
to_json
.
html_safe
%script
#js-confidential-issue-data
{
type:
"application/json"
}=
{
is_confidential:
issuable_sidebar
[
:confidential
],
is_editable:
can_edit_issuable
}.
to_json
.
html_safe
...
...
config/feature_flags/development/database_reindexing_pg12.yml
0 → 100644
View file @
d36ce37d
---
name
:
database_reindexing_pg12
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64695
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/334372
milestone
:
'
14.1'
type
:
development
group
:
group::database
default_enabled
:
false
ee/app/assets/javascripts/sidebar/components/status/sidebar_status.vue
View file @
d36ce37d
<
script
>
<
script
>
import
produce
from
'
immer
'
;
import
{
mapGetters
}
from
'
vuex
'
;
import
createFlash
from
'
~/flash
'
;
import
createFlash
from
'
~/flash
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
OPENED
,
REOPENED
}
from
'
~/notes/constants
'
;
import
{
OPENED
,
REOPENED
}
from
'
~/notes/constants
'
;
import
{
healthStatusQueries
}
from
'
../../constants
'
;
import
Status
from
'
./status.vue
'
;
import
Status
from
'
./status.vue
'
;
export
default
{
export
default
{
...
@@ -11,134 +10,27 @@ export default {
...
@@ -11,134 +10,27 @@ export default {
Status
,
Status
,
},
},
props
:
{
props
:
{
issuableType
:
{
mediator
:
{
type
:
String
,
required
:
true
,
required
:
true
,
},
type
:
Object
,
iid
:
{
validator
(
mediatorObject
)
{
type
:
String
,
return
Boolean
(
mediatorObject
.
store
);
required
:
true
,
},
},
fullPath
:
{
type
:
String
,
required
:
true
,
},
canUpdate
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
},
},
computed
:
{
computed
:
{
...
mapGetters
([
'
getNoteableData
'
]),
isOpen
()
{
isOpen
()
{
return
this
.
issuableData
?.
state
===
OPENED
||
this
.
issuableData
?.
state
===
REOPENED
;
return
this
.
getNoteableData
.
state
===
OPENED
||
this
.
getNoteableData
.
state
===
REOPENED
;
},
isLoading
()
{
return
this
.
$apollo
.
queries
.
issuableData
.
loading
;
},
healthStatus
()
{
return
this
.
issuableData
?.
healthStatus
;
},
},
apollo
:
{
issuableData
:
{
query
()
{
return
healthStatusQueries
[
this
.
issuableType
].
query
;
},
variables
()
{
return
{
fullPath
:
this
.
fullPath
,
iid
:
this
.
iid
,
};
},
update
(
data
)
{
return
{
healthStatus
:
data
.
workspace
?.
issuable
?.
healthStatus
,
state
:
data
.
workspace
?.
issuable
?.
state
,
};
},
result
({
data
})
{
this
.
$emit
(
'
issuableData
'
,
{
healthStatus
:
data
.
workspace
?.
issuable
?.
healthStatus
,
state
:
data
.
workspace
?.
issuable
?.
state
,
});
},
error
()
{
createFlash
({
message
:
sprintf
(
__
(
'
Something went wrong while setting %{issuableType} health status.
'
),
{
issuableType
:
this
.
issuableType
,
},
),
});
},
},
},
},
},
methods
:
{
methods
:
{
handleDropdownClick
(
status
)
{
handleDropdownClick
(
status
)
{
return
this
.
$apollo
this
.
mediator
.
updateStatus
(
status
).
catch
(()
=>
{
.
mutate
({
createFlash
({
mutation
:
healthStatusQueries
[
this
.
issuableType
].
mutation
,
message
:
__
(
'
Error occurred while updating the issue status
'
),
variables
:
{
projectPath
:
this
.
fullPath
,
iid
:
this
.
iid
,
healthStatus
:
status
,
},
update
:
(
_
,
{
data
:
{
updateIssue
:
{
issue
:
{
healthStatus
},
},
},
},
)
=>
{
const
{
defaultClient
:
client
}
=
this
.
$apollo
.
provider
.
clients
;
const
queryVariables
=
{
fullPath
:
this
.
fullPath
,
iid
:
this
.
iid
};
const
sourceData
=
client
.
readQuery
({
query
:
healthStatusQueries
[
this
.
issuableType
].
query
,
variables
:
queryVariables
,
});
const
data
=
produce
(
sourceData
,
(
draftData
)
=>
{
draftData
.
workspace
.
issuable
.
healthStatus
=
healthStatus
;
});
client
.
writeQuery
({
query
:
healthStatusQueries
[
this
.
issuableType
].
query
,
variables
:
queryVariables
,
data
,
});
},
optimisticResponse
:
{
__typename
:
'
Mutation
'
,
// eslint-disable-line @gitlab/require-i18n-strings
updateIssue
:
{
issue
:
{
healthStatus
:
status
,
__typename
:
'
Issue
'
,
// eslint-disable-line @gitlab/require-i18n-strings
},
errors
:
[],
__typename
:
'
UpdateIssuePayload
'
,
},
},
})
.
then
(({
data
:
{
updateIssue
}
=
{}
}
=
{})
=>
{
const
error
=
updateIssue
?.
errors
[
0
];
if
(
error
)
{
createFlash
({
message
:
error
});
}
})
.
catch
(()
=>
{
createFlash
({
message
:
sprintf
(
__
(
'
Error occurred while updating the %{issuableType} status
'
),
{
issuableType
:
this
.
issuableType
,
}),
});
});
});
});
},
},
},
},
};
};
...
@@ -147,9 +39,9 @@ export default {
...
@@ -147,9 +39,9 @@ export default {
<
template
>
<
template
>
<status
<status
:is-open=
"isOpen"
:is-open=
"isOpen"
:is-editable=
"
canUpdat
e"
:is-editable=
"
mediator.store.editabl
e"
:is-fetching=
"
isLoading
"
:is-fetching=
"
mediator.store.isFetching.status
"
:status=
"
healthS
tatus"
:status=
"
mediator.store.s
tatus"
@
onDropdownClick=
"handleDropdownClick"
@
onDropdownClick=
"handleDropdownClick"
/>
/>
</
template
>
</
template
>
ee/app/assets/javascripts/sidebar/constants.js
View file @
d36ce37d
...
@@ -5,11 +5,9 @@ import {
...
@@ -5,11 +5,9 @@ import {
IssuableAttributeState
as
IssuableAttributeStateFoss
,
IssuableAttributeState
as
IssuableAttributeStateFoss
,
issuableAttributesQueries
as
issuableAttributesQueriesFoss
,
issuableAttributesQueries
as
issuableAttributesQueriesFoss
,
}
from
'
~/sidebar/constants
'
;
}
from
'
~/sidebar/constants
'
;
import
updateStatusMutation
from
'
~/sidebar/queries/updateStatus.mutation.graphql
'
;
import
epicAncestorsQuery
from
'
./queries/epic_ancestors.query.graphql
'
;
import
epicAncestorsQuery
from
'
./queries/epic_ancestors.query.graphql
'
;
import
groupEpicsQuery
from
'
./queries/group_epics.query.graphql
'
;
import
groupEpicsQuery
from
'
./queries/group_epics.query.graphql
'
;
import
groupIterationsQuery
from
'
./queries/group_iterations.query.graphql
'
;
import
groupIterationsQuery
from
'
./queries/group_iterations.query.graphql
'
;
import
issueHealthStatusQuery
from
'
./queries/issue_health_status.query.graphql
'
;
import
projectIssueEpicMutation
from
'
./queries/project_issue_epic.mutation.graphql
'
;
import
projectIssueEpicMutation
from
'
./queries/project_issue_epic.mutation.graphql
'
;
import
projectIssueEpicQuery
from
'
./queries/project_issue_epic.query.graphql
'
;
import
projectIssueEpicQuery
from
'
./queries/project_issue_epic.query.graphql
'
;
import
projectIssueIterationMutation
from
'
./queries/project_issue_iteration.mutation.graphql
'
;
import
projectIssueIterationMutation
from
'
./queries/project_issue_iteration.mutation.graphql
'
;
...
@@ -130,14 +128,3 @@ export const ancestorsQueries = {
...
@@ -130,14 +128,3 @@ export const ancestorsQueries = {
query
:
epicAncestorsQuery
,
query
:
epicAncestorsQuery
,
},
},
};
};
export
const
healthStatusQueries
=
{
[
IssuableType
.
Issue
]:
{
mutation
:
updateStatusMutation
,
query
:
issueHealthStatusQuery
,
},
[
IssuableType
.
Epic
]:
{
mutation
:
updateStatusMutation
,
query
:
issueHealthStatusQuery
,
},
};
ee/app/assets/javascripts/sidebar/mount_sidebar.js
View file @
d36ce37d
...
@@ -28,18 +28,15 @@ const mountWeightComponent = () => {
...
@@ -28,18 +28,15 @@ const mountWeightComponent = () => {
});
});
};
};
const
mountStatusComponent
=
()
=>
{
const
mountStatusComponent
=
(
mediator
)
=>
{
const
el
=
document
.
querySelector
(
'
.js-sidebar-status-entry-point
'
);
const
el
=
document
.
querySelector
(
'
.js-sidebar-status-entry-point
'
);
if
(
!
el
)
{
if
(
!
el
)
{
return
false
;
return
false
;
}
}
const
{
iid
,
fullPath
,
issuableType
,
canEdit
}
=
el
.
dataset
;
return
new
Vue
({
return
new
Vue
({
el
,
el
,
apolloProvider
,
store
,
store
,
components
:
{
components
:
{
SidebarStatus
,
SidebarStatus
,
...
@@ -47,10 +44,7 @@ const mountStatusComponent = () => {
...
@@ -47,10 +44,7 @@ const mountStatusComponent = () => {
render
:
(
createElement
)
=>
render
:
(
createElement
)
=>
createElement
(
'
sidebar-status
'
,
{
createElement
(
'
sidebar-status
'
,
{
props
:
{
props
:
{
issuableType
,
mediator
,
iid
,
fullPath
,
canUpdate
:
parseBoolean
(
canEdit
),
},
},
}),
}),
});
});
...
...
ee/app/assets/javascripts/sidebar/queries/issue_health_status.query.graphql
deleted
100644 → 0
View file @
aa2bedca
query
issueHealthStatus
(
$fullPath
:
ID
!,
$iid
:
String
)
{
workspace
:
project
(
fullPath
:
$fullPath
)
{
__typename
issuable
:
issue
(
iid
:
$iid
)
{
__typename
id
healthStatus
state
}
}
}
ee/app/assets/javascripts/sidebar/sidebar_mediator.js
View file @
d36ce37d
import
Store
from
'
ee/sidebar/stores/sidebar_store
'
;
import
Store
from
'
ee/sidebar/stores/sidebar_store
'
;
import
updateStatusMutation
from
'
~/sidebar/queries/updateStatus.mutation.graphql
'
;
import
CESidebarMediator
from
'
~/sidebar/sidebar_mediator
'
;
import
CESidebarMediator
from
'
~/sidebar/sidebar_mediator
'
;
export
default
class
SidebarMediator
extends
CESidebarMediator
{
export
default
class
SidebarMediator
extends
CESidebarMediator
{
...
@@ -27,4 +28,20 @@ export default class SidebarMediator extends CESidebarMediator {
...
@@ -27,4 +28,20 @@ export default class SidebarMediator extends CESidebarMediator {
throw
err
;
throw
err
;
});
});
}
}
updateStatus
(
healthStatus
)
{
this
.
store
.
setFetchingState
(
'
status
'
,
true
);
return
this
.
service
.
updateWithGraphQl
(
updateStatusMutation
,
{
healthStatus
})
.
then
(({
data
})
=>
{
if
(
data
?.
updateIssue
?.
errors
?.
length
>
0
)
{
throw
data
.
updateIssue
.
errors
[
0
];
}
this
.
store
.
setStatus
(
data
?.
updateIssue
?.
issue
?.
healthStatus
);
})
.
catch
((
error
)
=>
{
throw
error
;
})
.
finally
(()
=>
this
.
store
.
setFetchingState
(
'
status
'
,
false
));
}
}
}
ee/spec/frontend/sidebar/components/status/sidebar_status_spec.js
View file @
d36ce37d
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
ApolloMutation
,
ApolloQuery
}
from
'
vue-apollo
'
;
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
SidebarStatus
from
'
ee/sidebar/components/status/sidebar_status.vue
'
;
import
SidebarStatus
from
'
ee/sidebar/components/status/sidebar_status.vue
'
;
import
Status
from
'
ee/sidebar/components/status/status.vue
'
;
import
Status
from
'
ee/sidebar/components/status/status.vue
'
;
import
{
healthStatusQueries
}
from
'
ee/sidebar/constants
'
;
const
mutate
=
jest
.
fn
().
mockResolvedValue
(
);
Vue
.
use
(
Vuex
);
describe
(
'
SidebarStatus
'
,
()
=>
{
describe
(
'
SidebarStatus
'
,
()
=>
{
let
mediator
;
let
wrapper
;
let
wrapper
;
const
createWrapper
=
({
const
createMediator
=
(
states
)
=>
{
issuableType
=
'
issue
'
,
mediator
=
{
state
=
'
opened
'
,
updateStatus
:
jest
.
fn
().
mockResolvedValue
(),
healthStatus
=
'
onTrack
'
,
store
:
{
loading
=
false
,
isFetching
:
{
}
=
{})
=>
{
status
:
true
,
const
$apollo
=
{
queries
:
{
issuableData
:
{
loading
,
},
},
status
:
''
,
...
states
,
},
},
mutate
,
};
};
};
const
createWrapper
=
({
noteableState
}
=
{})
=>
{
const
store
=
new
Vuex
.
Store
({
getters
:
{
getNoteableData
:
()
=>
({
state
:
noteableState
}),
},
});
wrapper
=
shallowMount
(
SidebarStatus
,
{
wrapper
=
shallowMount
(
SidebarStatus
,
{
propsData
:
{
propsData
:
{
issuableType
,
mediator
,
iid
:
'
1
'
,
fullPath
:
'
foo/bar
'
,
canUpdate
:
true
,
},
data
()
{
return
{
issuableData
:
{
state
,
healthStatus
,
},
};
},
sync
:
false
,
mocks
:
{
$apollo
},
stubs
:
{
ApolloMutation
,
ApolloQuery
,
},
},
store
,
});
});
};
};
beforeEach
(()
=>
{
beforeEach
(()
=>
{
createWrapper
();
createMediator
();
createWrapper
({
getters
:
{
getNoteableData
:
{},
},
});
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
...
@@ -58,16 +53,17 @@ describe('SidebarStatus', () => {
...
@@ -58,16 +53,17 @@ describe('SidebarStatus', () => {
describe
(
'
computed
'
,
()
=>
{
describe
(
'
computed
'
,
()
=>
{
describe
.
each
`
describe
.
each
`
state
| isOpen
noteableState
| isOpen
${
'
opened
'
}
|
${
true
}
${
'
opened
'
}
|
${
true
}
${
'
reopened
'
}
|
${
true
}
${
'
reopened
'
}
|
${
true
}
${
'
closed
'
}
|
${
false
}
${
'
closed
'
}
|
${
false
}
`
(
'
isOpen
'
,
({
s
tate
,
isOpen
})
=>
{
`
(
'
isOpen
'
,
({
noteableS
tate
,
isOpen
})
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
createWrapper
({
state
});
createMediator
({
editable
:
true
});
createWrapper
({
noteableState
});
});
});
it
(
`returns
${
isOpen
}
when issue is
${
s
tate
}
`
,
()
=>
{
it
(
`returns
${
isOpen
}
when issue is
${
noteableS
tate
}
`
,
()
=>
{
expect
(
wrapper
.
vm
.
isOpen
).
toBe
(
isOpen
);
expect
(
wrapper
.
vm
.
isOpen
).
toBe
(
isOpen
);
});
});
});
});
...
@@ -80,21 +76,10 @@ describe('SidebarStatus', () => {
...
@@ -80,21 +76,10 @@ describe('SidebarStatus', () => {
expect
(
wrapper
.
find
(
Status
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
Status
).
exists
()).
toBe
(
true
);
});
});
it
(
'
calls
apollo mut
ate when receiving an onDropdownClick event from Status component
'
,
()
=>
{
it
(
'
calls
mediator status upd
ate when receiving an onDropdownClick event from Status component
'
,
()
=>
{
wrapper
.
find
(
Status
).
vm
.
$emit
(
'
onDropdownClick
'
,
'
onTrack
'
);
wrapper
.
find
(
Status
).
vm
.
$emit
(
'
onDropdownClick
'
,
'
onTrack
'
);
const
mutationVariables
=
{
expect
(
mediator
.
updateStatus
).
toHaveBeenCalledWith
(
'
onTrack
'
);
mutation
:
healthStatusQueries
.
issue
.
mutation
,
update
:
expect
.
anything
(),
optimisticResponse
:
expect
.
anything
(),
variables
:
{
projectPath
:
'
foo/bar
'
,
iid
:
'
1
'
,
healthStatus
:
'
onTrack
'
,
},
};
expect
(
mutate
).
toHaveBeenCalledWith
(
mutationVariables
);
});
});
});
});
});
});
ee/spec/frontend/sidebar/ee_sidebar_mediator_spec.js
View file @
d36ce37d
import
SidebarMediator
from
'
ee/sidebar/sidebar_mediator
'
;
import
SidebarMediator
from
'
ee/sidebar/sidebar_mediator
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
SidebarService
from
'
~/sidebar/services/sidebar_service
'
;
import
SidebarService
from
'
~/sidebar/services/sidebar_service
'
;
import
CESidebarMediator
from
'
~/sidebar/sidebar_mediator
'
;
import
CESidebarMediator
from
'
~/sidebar/sidebar_mediator
'
;
import
CESidebarStore
from
'
~/sidebar/stores/sidebar_store
'
;
import
CESidebarStore
from
'
~/sidebar/stores/sidebar_store
'
;
...
@@ -26,4 +27,28 @@ describe('EE Sidebar mediator', () => {
...
@@ -26,4 +27,28 @@ describe('EE Sidebar mediator', () => {
expect
(
mediator
.
store
.
weight
).
toBe
(
mockData
.
weight
);
expect
(
mediator
.
store
.
weight
).
toBe
(
mockData
.
weight
);
expect
(
mediator
.
store
.
status
).
toBe
(
mockGraphQlData
.
project
.
issue
.
healthStatus
);
expect
(
mediator
.
store
.
status
).
toBe
(
mockGraphQlData
.
project
.
issue
.
healthStatus
);
});
});
it
(
'
updates status when updateStatus is called
'
,
()
=>
{
const
healthStatus
=
'
onTrack
'
;
jest
.
spyOn
(
mediator
.
service
,
'
updateWithGraphQl
'
).
mockReturnValue
(
Promise
.
resolve
({
data
:
{
updateIssue
:
{
issue
:
{
healthStatus
,
},
},
},
}),
);
expect
(
mediator
.
store
.
status
).
toBe
(
''
);
mediator
.
updateStatus
(
healthStatus
);
return
waitForPromises
().
then
(()
=>
{
expect
(
mediator
.
store
.
status
).
toBe
(
healthStatus
);
});
});
});
});
lib/gitlab/database/postgres_index.rb
View file @
d36ce37d
...
@@ -20,16 +20,25 @@ module Gitlab
...
@@ -20,16 +20,25 @@ module Gitlab
# A 'regular' index is a non-unique index,
# A 'regular' index is a non-unique index,
# that does not serve an exclusion constraint and
# that does not serve an exclusion constraint and
# is defined on a table that is not partitioned.
# is defined on a table that is not partitioned.
scope
:regular
,
->
{
where
(
unique:
false
,
partitioned:
false
,
exclusion:
false
)}
#
# Deprecated: Switch to scope .reindexing_support
scope
:regular
,
->
{
where
(
unique:
false
,
partitioned:
false
,
exclusion:
false
,
expression:
false
)}
# Indexes for reindexing with PG12
scope
:reindexing_support
,
->
{
where
(
partitioned:
false
,
exclusion:
false
,
expression:
false
)
}
scope
:not_match
,
->
(
regex
)
{
where
(
"name !~ ?"
,
regex
)}
scope
:not_match
,
->
(
regex
)
{
where
(
"name !~ ?"
,
regex
)}
scope
:match
,
->
(
regex
)
{
where
(
"name ~* ?"
,
regex
)}
scope
:not_recently_reindexed
,
->
do
scope
:not_recently_reindexed
,
->
do
recent_actions
=
Reindexing
::
ReindexAction
.
recent
.
where
(
'index_identifier = identifier'
)
recent_actions
=
Reindexing
::
ReindexAction
.
recent
.
where
(
'index_identifier = identifier'
)
where
(
'NOT EXISTS (?)'
,
recent_actions
)
where
(
'NOT EXISTS (?)'
,
recent_actions
)
end
end
alias_method
:reset
,
:reload
def
bloat_size
def
bloat_size
strong_memoize
(
:bloat_size
)
{
bloat_estimate
&
.
bloat_size
||
0
}
strong_memoize
(
:bloat_size
)
{
bloat_estimate
&
.
bloat_size
||
0
}
end
end
...
...
lib/gitlab/database/reindexing.rb
View file @
d36ce37d
...
@@ -14,11 +14,16 @@ module Gitlab
...
@@ -14,11 +14,16 @@ module Gitlab
end
end
def
self
.
candidate_indexes
def
self
.
candidate_indexes
Gitlab
::
Database
::
PostgresIndex
indexes
=
Gitlab
::
Database
::
PostgresIndex
.
regular
.
where
(
'NOT expression'
)
.
not_match
(
"^
#{
ConcurrentReindex
::
TEMPORARY_INDEX_PREFIX
}
"
)
.
not_match
(
"^
#{
ConcurrentReindex
::
TEMPORARY_INDEX_PREFIX
}
"
)
.
not_match
(
"^
#{
ConcurrentReindex
::
REPLACED_INDEX_PREFIX
}
"
)
.
not_match
(
"^
#{
ConcurrentReindex
::
REPLACED_INDEX_PREFIX
}
"
)
.
not_match
(
"
#{
ReindexConcurrently
::
TEMPORARY_INDEX_PATTERN
}
$"
)
if
Feature
.
enabled?
(
:database_reindexing_pg12
,
type: :development
)
indexes
.
reindexing_support
else
indexes
.
regular
end
end
end
end
end
end
end
...
...
lib/gitlab/database/reindexing/coordinator.rb
View file @
d36ce37d
...
@@ -41,7 +41,13 @@ module Gitlab
...
@@ -41,7 +41,13 @@ module Gitlab
end
end
def
perform_for
(
index
,
action
)
def
perform_for
(
index
,
action
)
ConcurrentReindex
.
new
(
index
).
perform
strategy
=
if
Feature
.
enabled?
(
:database_reindexing_pg12
,
type: :development
)
ReindexConcurrently
else
ConcurrentReindex
end
strategy
.
new
(
index
).
perform
rescue
StandardError
rescue
StandardError
action
.
state
=
:failed
action
.
state
=
:failed
...
...
lib/gitlab/database/reindexing/reindex_concurrently.rb
0 → 100644
View file @
d36ce37d
# frozen_string_literal: true
module
Gitlab
module
Database
module
Reindexing
# This is a >= PG12 reindexing strategy based on `REINDEX CONCURRENTLY`
class
ReindexConcurrently
ReindexError
=
Class
.
new
(
StandardError
)
TEMPORARY_INDEX_PATTERN
=
'\_ccnew[0-9]*'
STATEMENT_TIMEOUT
=
9
.
hours
PG_MAX_INDEX_NAME_LENGTH
=
63
# When dropping an index, we acquire a SHARE UPDATE EXCLUSIVE lock,
# which only conflicts with DDL and vacuum. We therefore execute this with a rather
# high lock timeout and a long pause in between retries. This is an alternative to
# setting a high statement timeout, which would lead to a long running query with effects
# on e.g. vacuum.
REMOVE_INDEX_RETRY_CONFIG
=
[[
1
.
minute
,
9
.
minutes
]]
*
30
attr_reader
:index
,
:logger
def
initialize
(
index
,
logger:
Gitlab
::
AppLogger
)
@index
=
index
@logger
=
logger
end
def
perform
raise
ReindexError
,
'indexes serving an exclusion constraint are currently not supported'
if
index
.
exclusion?
raise
ReindexError
,
'index is a left-over temporary index from a previous reindexing run'
if
index
.
name
=~
/
#{
TEMPORARY_INDEX_PATTERN
}
/
# Expression indexes require additional statistics in `pg_statistic`:
# select * from pg_statistic where starelid = (select oid from pg_class where relname = 'some_index');
#
# In PG12, this has been fixed in https://gitlab.com/postgres/postgres/-/commit/b17ff07aa3eb142d2cde2ea00e4a4e8f63686f96.
# Discussion happened in https://www.postgresql.org/message-id/flat/CAFcNs%2BqpFPmiHd1oTXvcPdvAHicJDA9qBUSujgAhUMJyUMb%2BSA%40mail.gmail.com
# following a GitLab.com incident that surfaced this (https://gitlab.com/gitlab-com/gl-infra/production/-/issues/2885).
#
# While this has been backpatched, we continue to disable expression indexes until further review.
raise
ReindexError
,
'expression indexes are currently not supported'
if
index
.
expression?
begin
with_logging
do
set_statement_timeout
do
execute
(
"REINDEX INDEX CONCURRENTLY
#{
quote_table_name
(
index
.
schema
)
}
.
#{
quote_table_name
(
index
.
name
)
}
"
)
end
end
ensure
cleanup_dangling_indexes
end
end
private
def
with_logging
bloat_size
=
index
.
bloat_size
ondisk_size_before
=
index
.
ondisk_size_bytes
logger
.
info
(
message:
"Starting reindex of
#{
index
}
"
,
index:
index
.
identifier
,
table:
index
.
tablename
,
estimated_bloat_bytes:
bloat_size
,
index_size_before_bytes:
ondisk_size_before
)
duration
=
Benchmark
.
realtime
do
yield
end
index
.
reset
logger
.
info
(
message:
"Finished reindex of
#{
index
}
"
,
index:
index
.
identifier
,
table:
index
.
tablename
,
estimated_bloat_bytes:
bloat_size
,
index_size_before_bytes:
ondisk_size_before
,
index_size_after_bytes:
index
.
ondisk_size_bytes
,
duration_s:
duration
.
round
(
2
)
)
end
def
cleanup_dangling_indexes
Gitlab
::
Database
::
PostgresIndex
.
match
(
"
#{
TEMPORARY_INDEX_PATTERN
}
$"
).
each
do
|
lingering_index
|
# Example lingering index name: some_index_ccnew1
# Example prefix: 'some_index'
prefix
=
lingering_index
.
name
.
gsub
(
/
#{
TEMPORARY_INDEX_PATTERN
}
/
,
''
)
# Example suffix: '_ccnew1'
suffix
=
lingering_index
.
name
.
match
(
/
#{
TEMPORARY_INDEX_PATTERN
}
/
)[
0
]
# Only remove if the lingering index name could have been chosen
# as a result of a REINDEX operation (considering that PostgreSQL
# truncates index names to 63 chars and adds a suffix).
if
index
.
name
[
0
...
PG_MAX_INDEX_NAME_LENGTH
-
suffix
.
length
]
==
prefix
remove_index
(
lingering_index
)
end
end
end
def
remove_index
(
index
)
logger
.
info
(
"Removing dangling index
#{
index
.
identifier
}
"
)
retries
=
Gitlab
::
Database
::
WithLockRetriesOutsideTransaction
.
new
(
timing_configuration:
REMOVE_INDEX_RETRY_CONFIG
,
klass:
self
.
class
,
logger:
logger
)
retries
.
run
(
raise_on_exhaustion:
false
)
do
execute
(
"DROP INDEX CONCURRENTLY IF EXISTS
#{
quote_table_name
(
index
.
schema
)
}
.
#{
quote_table_name
(
index
.
name
)
}
"
)
end
end
def
with_lock_retries
(
&
block
)
arguments
=
{
klass:
self
.
class
,
logger:
logger
}
Gitlab
::
Database
::
WithLockRetries
.
new
(
**
arguments
).
run
(
raise_on_exhaustion:
true
,
&
block
)
end
def
set_statement_timeout
execute
(
"SET statement_timeout TO '%ds'"
%
STATEMENT_TIMEOUT
)
yield
ensure
execute
(
'RESET statement_timeout'
)
end
delegate
:execute
,
:quote_table_name
,
to: :connection
def
connection
@connection
||=
ActiveRecord
::
Base
.
connection
end
end
end
end
end
locale/gitlab.pot
View file @
d36ce37d
...
@@ -12832,9 +12832,6 @@ msgstr ""
...
@@ -12832,9 +12832,6 @@ msgstr ""
msgid "Error occurred when saving reviewers"
msgid "Error occurred when saving reviewers"
msgstr ""
msgstr ""
msgid "Error occurred while updating the %{issuableType} status"
msgstr ""
msgid "Error occurred while updating the issue status"
msgid "Error occurred while updating the issue status"
msgstr ""
msgstr ""
...
@@ -30474,9 +30471,6 @@ msgstr ""
...
@@ -30474,9 +30471,6 @@ msgstr ""
msgid "Something went wrong while setting %{issuableType} confidentiality."
msgid "Something went wrong while setting %{issuableType} confidentiality."
msgstr ""
msgstr ""
msgid "Something went wrong while setting %{issuableType} health status."
msgstr ""
msgid "Something went wrong while setting %{issuableType} notifications."
msgid "Something went wrong while setting %{issuableType} notifications."
msgstr ""
msgstr ""
...
...
spec/db/schema_spec.rb
View file @
d36ce37d
...
@@ -264,6 +264,18 @@ RSpec.describe 'Database schema' do
...
@@ -264,6 +264,18 @@ RSpec.describe 'Database schema' do
end
end
end
end
context
'index names'
do
it
'disallows index names with a _ccnew[0-9]* suffix'
do
# During REINDEX operations, Postgres generates a temporary index with a _ccnew[0-9]* suffix
# Since indexes are being considered temporary and subject to removal if they stick around for longer. See Gitlab::Database::Reindexing.
#
# Hence we disallow adding permanent indexes with this suffix.
problematic_indexes
=
Gitlab
::
Database
::
PostgresIndex
.
match
(
"
#{
Gitlab
::
Database
::
Reindexing
::
ReindexConcurrently
::
TEMPORARY_INDEX_PATTERN
}
$"
).
all
expect
(
problematic_indexes
).
to
be_empty
end
end
private
private
def
retrieve_columns_name_with_jsonb
def
retrieve_columns_name_with_jsonb
...
...
spec/lib/gitlab/database/postgres_index_spec.rb
View file @
d36ce37d
...
@@ -34,6 +34,24 @@ RSpec.describe Gitlab::Database::PostgresIndex do
...
@@ -34,6 +34,24 @@ RSpec.describe Gitlab::Database::PostgresIndex do
it
'only indexes that dont serve an exclusion constraint'
do
it
'only indexes that dont serve an exclusion constraint'
do
expect
(
described_class
.
regular
).
to
all
(
have_attributes
(
exclusion:
false
))
expect
(
described_class
.
regular
).
to
all
(
have_attributes
(
exclusion:
false
))
end
end
it
'only non-expression indexes'
do
expect
(
described_class
.
regular
).
to
all
(
have_attributes
(
expression:
false
))
end
end
describe
'.reindexing_support'
do
it
'only non partitioned indexes'
do
expect
(
described_class
.
reindexing_support
).
to
all
(
have_attributes
(
partitioned:
false
))
end
it
'only indexes that dont serve an exclusion constraint'
do
expect
(
described_class
.
reindexing_support
).
to
all
(
have_attributes
(
exclusion:
false
))
end
it
'only non-expression indexes'
do
expect
(
described_class
.
reindexing_support
).
to
all
(
have_attributes
(
expression:
false
))
end
end
end
describe
'.not_match'
do
describe
'.not_match'
do
...
...
spec/lib/gitlab/database/reindexing/coordinator_spec.rb
View file @
d36ce37d
...
@@ -9,13 +9,6 @@ RSpec.describe Gitlab::Database::Reindexing::Coordinator do
...
@@ -9,13 +9,6 @@ RSpec.describe Gitlab::Database::Reindexing::Coordinator do
describe
'.perform'
do
describe
'.perform'
do
subject
{
described_class
.
new
(
index
,
notifier
).
perform
}
subject
{
described_class
.
new
(
index
,
notifier
).
perform
}
before
do
swapout_view_for_table
(
:postgres_indexes
)
allow
(
Gitlab
::
Database
::
Reindexing
::
ConcurrentReindex
).
to
receive
(
:new
).
with
(
index
).
and_return
(
reindexer
)
allow
(
Gitlab
::
Database
::
Reindexing
::
ReindexAction
).
to
receive
(
:create_for
).
with
(
index
).
and_return
(
action
)
end
let
(
:index
)
{
create
(
:postgres_index
)
}
let
(
:index
)
{
create
(
:postgres_index
)
}
let
(
:notifier
)
{
instance_double
(
Gitlab
::
Database
::
Reindexing
::
GrafanaNotifier
,
notify_start:
nil
,
notify_end:
nil
)
}
let
(
:notifier
)
{
instance_double
(
Gitlab
::
Database
::
Reindexing
::
GrafanaNotifier
,
notify_start:
nil
,
notify_end:
nil
)
}
let
(
:reindexer
)
{
instance_double
(
Gitlab
::
Database
::
Reindexing
::
ConcurrentReindex
,
perform:
nil
)
}
let
(
:reindexer
)
{
instance_double
(
Gitlab
::
Database
::
Reindexing
::
ConcurrentReindex
,
perform:
nil
)
}
...
@@ -26,57 +19,83 @@ RSpec.describe Gitlab::Database::Reindexing::Coordinator do
...
@@ -26,57 +19,83 @@ RSpec.describe Gitlab::Database::Reindexing::Coordinator do
let
(
:lease_timeout
)
{
1
.
day
}
let
(
:lease_timeout
)
{
1
.
day
}
let
(
:uuid
)
{
'uuid'
}
let
(
:uuid
)
{
'uuid'
}
context
'locking'
do
shared_examples_for
'reindexing coordination'
do
it
'acquires a lock while reindexing'
do
context
'locking'
do
expect
(
lease
).
to
receive
(
:try_obtain
).
ordered
.
and_return
(
uuid
)
it
'acquires a lock while reindexing'
do
expect
(
lease
).
to
receive
(
:try_obtain
).
ordered
.
and_return
(
uuid
)
expect
(
reindexer
).
to
receive
(
:perform
).
ordered
expect
(
reindexer
).
to
receive
(
:perform
).
ordered
expect
(
Gitlab
::
ExclusiveLease
).
to
receive
(
:cancel
).
ordered
.
with
(
lease_key
,
uuid
)
expect
(
Gitlab
::
ExclusiveLease
).
to
receive
(
:cancel
).
ordered
.
with
(
lease_key
,
uuid
)
subject
subject
end
end
it
'does not perform reindexing actions if lease is not granted'
do
it
'does not perform reindexing actions if lease is not granted'
do
expect
(
lease
).
to
receive
(
:try_obtain
).
ordered
.
and_return
(
false
)
expect
(
lease
).
to
receive
(
:try_obtain
).
ordered
.
and_return
(
false
)
expect
(
Gitlab
::
Database
::
Reindexing
::
ConcurrentReindex
).
not_to
receive
(
:new
)
expect
(
Gitlab
::
Database
::
Reindexing
::
ConcurrentReindex
).
not_to
receive
(
:new
)
subject
subject
end
end
end
end
context
'notifications'
do
context
'notifications'
do
it
'sends #notify_start before reindexing'
do
it
'sends #notify_start before reindexing'
do
expect
(
notifier
).
to
receive
(
:notify_start
).
with
(
action
).
ordered
expect
(
notifier
).
to
receive
(
:notify_start
).
with
(
action
).
ordered
expect
(
reindexer
).
to
receive
(
:perform
).
ordered
expect
(
reindexer
).
to
receive
(
:perform
).
ordered
subject
end
subject
it
'sends #notify_end after reindexing and updating the action is done'
do
expect
(
action
).
to
receive
(
:finish
).
ordered
expect
(
notifier
).
to
receive
(
:notify_end
).
with
(
action
).
ordered
subject
end
end
end
it
'sends #notify_end after reindexing and updating the action is done'
do
context
'action tracking'
do
expect
(
action
).
to
receive
(
:finish
).
ordered
it
'calls #finish on the action'
do
expect
(
notifier
).
to
receive
(
:notify_end
).
with
(
action
).
ordered
expect
(
reindexer
).
to
receive
(
:perform
).
ordered
expect
(
action
).
to
receive
(
:finish
).
ordered
subject
end
it
'upon error, it still calls finish and raises the error'
do
expect
(
reindexer
).
to
receive
(
:perform
).
ordered
.
and_raise
(
'something went wrong'
)
expect
(
action
).
to
receive
(
:finish
).
ordered
subject
expect
{
subject
}.
to
raise_error
(
/something went wrong/
)
expect
(
action
).
to
be_failed
end
end
end
end
end
context
'
action tracking
'
do
context
'
legacy reindexing method (< PG12) - to be removed
'
do
it
'calls #finish on the action'
do
before
do
expect
(
reindexer
).
to
receive
(
:perform
).
ordered
stub_feature_flags
(
database_reindexing_pg12:
false
)
expect
(
action
).
to
receive
(
:finish
).
ordered
swapout_view_for_table
(
:postgres_indexes
)
subject
allow
(
Gitlab
::
Database
::
Reindexing
::
ConcurrentReindex
).
to
receive
(
:new
).
with
(
index
).
and_return
(
reindexer
)
allow
(
Gitlab
::
Database
::
Reindexing
::
ReindexAction
).
to
receive
(
:create_for
).
with
(
index
).
and_return
(
action
)
end
end
it
'upon error, it still calls finish and raises the error'
do
it_behaves_like
'reindexing coordination'
expect
(
reindexer
).
to
receive
(
:perform
).
ordered
.
and_raise
(
'something went wrong'
)
end
expect
(
action
).
to
receive
(
:finish
).
ordered
expect
{
subject
}.
to
raise_error
(
/something went wrong/
)
context
'PG12 reindexing method'
do
before
do
stub_feature_flags
(
database_reindexing_pg12:
true
)
swapout_view_for_table
(
:postgres_indexes
)
expect
(
action
).
to
be_failed
allow
(
Gitlab
::
Database
::
Reindexing
::
ReindexConcurrently
).
to
receive
(
:new
).
with
(
index
).
and_return
(
reindexer
)
allow
(
Gitlab
::
Database
::
Reindexing
::
ReindexAction
).
to
receive
(
:create_for
).
with
(
index
).
and_return
(
action
)
end
end
it_behaves_like
'reindexing coordination'
end
end
end
end
end
end
spec/lib/gitlab/database/reindexing/reindex_concurrently_spec.rb
0 → 100644
View file @
d36ce37d
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Gitlab
::
Database
::
Reindexing
::
ReindexConcurrently
,
'#perform'
do
subject
{
described_class
.
new
(
index
,
logger:
logger
).
perform
}
let
(
:table_name
)
{
'_test_reindex_table'
}
let
(
:column_name
)
{
'_test_column'
}
let
(
:index_name
)
{
'_test_reindex_index'
}
let
(
:index
)
{
Gitlab
::
Database
::
PostgresIndex
.
by_identifier
(
"public.
#{
iname
(
index_name
)
}
"
)
}
let
(
:logger
)
{
double
(
'logger'
,
debug:
nil
,
info:
nil
,
error:
nil
)
}
let
(
:connection
)
{
ActiveRecord
::
Base
.
connection
}
before
do
connection
.
execute
(
<<~
SQL
)
CREATE TABLE
#{
table_name
}
(
id serial NOT NULL PRIMARY KEY,
#{
column_name
}
integer NOT NULL);
CREATE INDEX
#{
index_name
}
ON
#{
table_name
}
(
#{
column_name
}
);
SQL
end
context
'when the index serves an exclusion constraint'
do
before
do
allow
(
index
).
to
receive
(
:exclusion?
).
and_return
(
true
)
end
it
'raises an error'
do
expect
{
subject
}.
to
raise_error
(
described_class
::
ReindexError
,
/indexes serving an exclusion constraint are currently not supported/
)
end
end
context
'when attempting to reindex an expression index'
do
before
do
allow
(
index
).
to
receive
(
:expression?
).
and_return
(
true
)
end
it
'raises an error'
do
expect
{
subject
}.
to
raise_error
(
described_class
::
ReindexError
,
/expression indexes are currently not supported/
)
end
end
context
'when the index is a dangling temporary index from a previous reindexing run'
do
context
'with the temporary index prefix'
do
let
(
:index_name
)
{
'_test_reindex_index_ccnew'
}
it
'raises an error'
do
expect
{
subject
}.
to
raise_error
(
described_class
::
ReindexError
,
/left-over temporary index/
)
end
end
context
'with the temporary index prefix with a counter'
do
let
(
:index_name
)
{
'_test_reindex_index_ccnew1'
}
it
'raises an error'
do
expect
{
subject
}.
to
raise_error
(
described_class
::
ReindexError
,
/left-over temporary index/
)
end
end
end
it
'recreates the index using REINDEX with a long statement timeout'
do
expect_to_execute_in_order
(
"SET statement_timeout TO '32400s'"
,
"REINDEX INDEX CONCURRENTLY
\"
public
\"
.
\"
#{
index
.
name
}
\"
"
,
"RESET statement_timeout"
)
subject
end
context
'with dangling indexes matching TEMPORARY_INDEX_PATTERN, i.e. /some\_index\_ccnew(\d)*/'
do
before
do
# dangling indexes
connection
.
execute
(
"CREATE INDEX
#{
iname
(
index_name
,
'_ccnew'
)
}
ON
#{
table_name
}
(
#{
column_name
}
)"
)
connection
.
execute
(
"CREATE INDEX
#{
iname
(
index_name
,
'_ccnew2'
)
}
ON
#{
table_name
}
(
#{
column_name
}
)"
)
# Unrelated index - don't drop
connection
.
execute
(
"CREATE INDEX some_other_index_ccnew ON
#{
table_name
}
(
#{
column_name
}
)"
)
end
shared_examples_for
'dropping the dangling index'
do
it
'drops the dangling indexes while controlling lock_timeout'
do
expect_to_execute_in_order
(
# Regular index rebuild
"SET statement_timeout TO '32400s'"
,
"REINDEX INDEX CONCURRENTLY
\"
public
\"
.
\"
#{
index_name
}
\"
"
,
"RESET statement_timeout"
,
# Drop _ccnew index
"SET lock_timeout TO '60000ms'"
,
"DROP INDEX CONCURRENTLY IF EXISTS
\"
public
\"
.
\"
#{
iname
(
index_name
,
'_ccnew'
)
}
\"
"
,
"RESET idle_in_transaction_session_timeout; RESET lock_timeout"
,
# Drop _ccnew2 index
"SET lock_timeout TO '60000ms'"
,
"DROP INDEX CONCURRENTLY IF EXISTS
\"
public
\"
.
\"
#{
iname
(
index_name
,
'_ccnew2'
)
}
\"
"
,
"RESET idle_in_transaction_session_timeout; RESET lock_timeout"
)
subject
end
end
context
'with normal index names'
do
it_behaves_like
'dropping the dangling index'
end
context
'with index name at 63 character limit'
do
let
(
:index_name
)
{
'a'
*
63
}
before
do
# Another unrelated index - don't drop
extra_index
=
index_name
[
0
...
55
]
connection
.
execute
(
"CREATE INDEX
#{
extra_index
}
_ccnew ON
#{
table_name
}
(
#{
column_name
}
)"
)
end
it_behaves_like
'dropping the dangling index'
end
end
def
iname
(
name
,
suffix
=
''
)
"
#{
name
[
0
...
63
-
suffix
.
size
]
}#{
suffix
}
"
end
def
expect_to_execute_in_order
(
*
queries
)
# Indexes cannot be created CONCURRENTLY in a transaction. Since the tests are wrapped in transactions,
# verify the original call but pass through the non-concurrent form.
queries
.
each
do
|
query
|
expect
(
connection
).
to
receive
(
:execute
).
with
(
query
).
ordered
.
and_wrap_original
do
|
method
,
sql
|
method
.
call
(
sql
.
sub
(
/CONCURRENTLY/
,
''
))
end
end
end
end
spec/lib/gitlab/database/reindexing_spec.rb
View file @
d36ce37d
...
@@ -29,11 +29,30 @@ RSpec.describe Gitlab::Database::Reindexing do
...
@@ -29,11 +29,30 @@ RSpec.describe Gitlab::Database::Reindexing do
describe
'.candidate_indexes'
do
describe
'.candidate_indexes'
do
subject
{
described_class
.
candidate_indexes
}
subject
{
described_class
.
candidate_indexes
}
it
'retrieves regular indexes that are no left-overs from previous runs'
do
context
'with deprecated method for < PG12'
do
result
=
double
before
do
expect
(
Gitlab
::
Database
::
PostgresIndex
).
to
receive_message_chain
(
'regular.where.not_match.not_match'
).
with
(
no_args
).
with
(
'NOT expression'
).
with
(
'^tmp_reindex_'
).
with
(
'^old_reindex_'
).
and_return
(
result
)
stub_feature_flags
(
database_reindexing_pg12:
false
)
end
it
'retrieves regular indexes that are no left-overs from previous runs'
do
result
=
double
expect
(
Gitlab
::
Database
::
PostgresIndex
).
to
receive_message_chain
(
'not_match.not_match.not_match.regular'
).
with
(
'^tmp_reindex_'
).
with
(
'^old_reindex_'
).
with
(
'\_ccnew[0-9]*$'
).
with
(
no_args
).
and_return
(
result
)
expect
(
subject
).
to
eq
(
result
)
end
end
expect
(
subject
).
to
eq
(
result
)
context
'with deprecated method for >= PG12'
do
before
do
stub_feature_flags
(
database_reindexing_pg12:
true
)
end
it
'retrieves regular indexes that are no left-overs from previous runs'
do
result
=
double
expect
(
Gitlab
::
Database
::
PostgresIndex
).
to
receive_message_chain
(
'not_match.not_match.not_match.reindexing_support'
).
with
(
'^tmp_reindex_'
).
with
(
'^old_reindex_'
).
with
(
'\_ccnew[0-9]*$'
).
with
(
no_args
).
and_return
(
result
)
expect
(
subject
).
to
eq
(
result
)
end
end
end
end
end
end
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