Commit 5e11c9b7 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 97d4d926
.notify:
image: alpine
image: ruby:2.6-alpine
stage: notification
dependencies: []
cache: {}
before_script:
- apk update && apk add git curl bash
- source scripts/utils.sh
- source scripts/notifications.sh
- install_gitlab_gem
variables:
COMMIT_NOTES_URL: "https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
COMMIT_NOTES_URL: "https://${CI_SERVER_HOST}/${CI_PROJECT_PATH}/commit/${CI_COMMIT_SHA}#notes-list"
schedule:package-and-qa:notify-failure:
extends:
- .only:variables_refs-canonical-dot-com-schedules
- .notify
script:
- 'scripts/notify-slack qa-master ":skull_and_crossbones: Scheduled QA against master failed! :skull_and_crossbones: See $CI_PIPELINE_URL. For downstream pipelines, see $COMMIT_NOTES_URL" ci_failing'
- 'export NOTIFICATION_MESSAGE=":skull_and_crossbones: Scheduled QA against master failed! :skull_and_crossbones: See ${CI_PIPELINE_URL}. For downstream pipelines, see ${COMMIT_NOTES_URL}"'
- 'notify_on_job_failure schedule:package-and-qa qa-master "${NOTIFICATION_MESSAGE}" ci_failing'
needs: ["schedule:package-and-qa"]
when: on_failure
allow_failure: true
when: always
## What is the productivity problem to solve?
<!--
Please describe the productivity problem that needs to be solved backed by charts from
https://about.gitlab.com/handbook/engineering/quality/engineering-productivity-team/#engineering-productivity-team-metrics.
-->
### Problem identification checklist
- [ ] The root cause of the problem is identified.
- [ ] The surface of the problem is as small as possible.
## What are the potential solutions?
<!--
Please provide potential solutions here. Example solutions could be:
- Dogfood a feature.
- Refactor/improve some workflow code.
- Throw more money at the problem.
Please provide pros/cons and a weight estimate for each solution.
-->
- [ ] All potential solutions are listed.
- [ ] A solution has been chosen for the first iteration: `PUT THE CHOSEN SOLUTION HERE`
## Who and when will the solution be implemented?
<!--
For history reason, please list the person that will implement the solution and
the planned milestone/date.
-->
## Verify that the solution has improved the situation
<!--
Ideally, looking at the charts from the first part, we should see an improvement
after the implementation is merged/deployed/released.
-->
- [ ] The solution improved the situation.
- If yes, check this box and close the issue. Well done! :tada:
- Otherwise, create a new "Productivity Improvement" issue. You can re-use the description from this issue, but obviously another solution should be chosen this time.
/label ~"Engineering Productivity" ~meta
/cc @gl-quality/eng-prod
......@@ -127,9 +127,11 @@ export default {
<input name="issue[description]" :value="issueDescription" type="hidden" />
<gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" />
<loading-button
v-if="!error.gitlab_issue"
class="btn-success"
:label="__('Create issue')"
:loading="issueCreationInProgress"
data-qa-selector="create_issue_button"
@click="createIssue"
/>
</form>
......@@ -140,6 +142,12 @@ export default {
</tooltip-on-truncate>
<h3>{{ __('Error details') }}</h3>
<ul>
<li v-if="error.gitlab_issue">
<span class="bold">{{ __('GitLab Issue') }}:</span>
<gl-link :href="error.gitlab_issue">
<span>{{ error.gitlab_issue }}</span>
</gl-link>
</li>
<li>
<span class="bold">{{ __('Sentry event') }}:</span>
<gl-link
......
......@@ -42,6 +42,7 @@ class Issue < ApplicationRecord
has_many :issue_assignees
has_many :assignees, class_name: "User", through: :issue_assignees
has_many :zoom_meetings
has_one :sentry_issue
validates :project, presence: true
......
# frozen_string_literal: true
class SentryIssue < ApplicationRecord
belongs_to :issue
validates :issue, uniqueness: true, presence: true
validates :sentry_issue_identifier, presence: true
end
......@@ -8,6 +8,18 @@ class Timelog < ApplicationRecord
belongs_to :merge_request, touch: true
belongs_to :user
scope :for_issues_in_group, -> (group) do
joins(:issue).where(
'EXISTS (?)',
Project.select(1).where(namespace: group.self_and_descendants)
.where('issues.project_id = projects.id')
)
end
scope :between_dates, -> (start_date, end_date) do
where('spent_at BETWEEN ? AND ?', start_date, end_date)
end
def issuable
issue || merge_request
end
......
......@@ -10,6 +10,7 @@ module ErrorTracking
:first_release_short_version,
:first_seen,
:frequency,
:gitlab_issue,
:id,
:last_release_last_commit,
:last_release_short_version,
......
---
title: Add SentryIssue table to store a link between issue and sentry issue
merge_request: 37026
author:
type: added
---
title: Increase lower DAG `needs` limit from five to ten
merge_request: 21237
author:
type: changed
---
title: Fix misaligned approval tr
merge_request: 21368
author: Lee Tickett
type: fixed
---
title: Surface GitLab issue in error detail page
merge_request: 21019
author:
type: added
......@@ -29,8 +29,8 @@ def ce_port_changelog?(changelog_path)
helper.ee? && !ee_changelog?(changelog_path)
end
def docs_only_change?
helper.changes_by_category.keys == [:docs]
def categories_need_changelog?
(helper.changes_by_category.keys - %i[docs none]).any?
end
def check_changelog(path)
......@@ -59,7 +59,7 @@ def sanitized_mr_title
gitlab.mr_json["title"].gsub(/^WIP: */, '').gsub(/`/, '\\\`')
end
changelog_needed = !docs_only_change? && (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty?
changelog_needed = categories_need_changelog? && (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty?
changelog_found = git.added_files.find { |path| path =~ %r{\A(ee/)?(changelogs/unreleased)(-ee)?/} }
if git.modified_files.include?("CHANGELOG.md")
......
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddTimelogSpentAtIndex < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :timelogs, :spent_at, where: 'spent_at IS NOT NULL'
end
def down
remove_concurrent_index :timelogs, :spent_at, where: 'spent_at IS NOT NULL'
end
end
# frozen_string_literal: true
class CreateSentryIssuesTable < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
create_table :sentry_issues do |t|
t.references :issue,
foreign_key: { on_delete: :cascade },
index: { unique: true },
null: false
t.bigint :sentry_issue_identifier, null: false
end
end
end
......@@ -3607,6 +3607,12 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
t.index ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true
end
create_table "sentry_issues", force: :cascade do |t|
t.bigint "issue_id", null: false
t.bigint "sentry_issue_identifier", null: false
t.index ["issue_id"], name: "index_sentry_issues_on_issue_id", unique: true
end
create_table "service_desk_settings", primary_key: "project_id", id: :bigint, default: nil, force: :cascade do |t|
t.string "issue_template_key", limit: 255
end
......@@ -3812,6 +3818,7 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
t.datetime "spent_at"
t.index ["issue_id"], name: "index_timelogs_on_issue_id"
t.index ["merge_request_id"], name: "index_timelogs_on_merge_request_id"
t.index ["spent_at"], name: "index_timelogs_on_spent_at", where: "(spent_at IS NOT NULL)"
t.index ["user_id"], name: "index_timelogs_on_user_id"
end
......@@ -4660,6 +4667,7 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
add_foreign_key "scim_oauth_access_tokens", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "self_managed_prometheus_alert_events", "environments", on_delete: :cascade
add_foreign_key "self_managed_prometheus_alert_events", "projects", on_delete: :cascade
add_foreign_key "sentry_issues", "issues", on_delete: :cascade
add_foreign_key "service_desk_settings", "projects", on_delete: :cascade
add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade
add_foreign_key "slack_integrations", "services", on_delete: :cascade
......
......@@ -2156,6 +2156,10 @@ type Group {
"""
state: EpicState
): EpicConnection
"""
Indicates if Epics are enabled for namespace
"""
epicsEnabled: Boolean
"""
......@@ -2168,6 +2172,11 @@ type Group {
"""
fullPath: ID!
"""
Indicates if Group timelogs are enabled for namespace
"""
groupTimelogsEnabled: Boolean
"""
ID of the namespace
"""
......@@ -2233,6 +2242,41 @@ type Group {
"""
rootStorageStatistics: RootStorageStatistics
"""
Time logged in issues by group members
"""
timelogs(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
List time logs within a time range where the logged date is before end_date parameter.
"""
endDate: Time!
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
List time logs within a time range where the logged date is after start_date parameter.
"""
startDate: Time!
): TimelogConnection!
"""
Permissions for the current user on the resource
"""
......@@ -5484,6 +5528,63 @@ Time represented in ISO 8601
"""
scalar Time
type Timelog {
"""
The date when the time tracked was spent at
"""
date: Time!
"""
The issue that logged time was added to
"""
issue: Issue
"""
The time spent displayed in seconds
"""
timeSpent: Int!
"""
The user that logged the time
"""
user: User!
}
"""
The connection type for Timelog.
"""
type TimelogConnection {
"""
A list of edges.
"""
edges: [TimelogEdge]
"""
A list of nodes.
"""
nodes: [Timelog]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type TimelogEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: Timelog
}
"""
Representing a todo entry
"""
......
......@@ -3223,7 +3223,7 @@
},
{
"name": "epicsEnabled",
"description": null,
"description": "Indicates if Epics are enabled for namespace",
"args": [
],
......@@ -3271,6 +3271,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "groupTimelogsEnabled",
"description": "Indicates if Group timelogs are enabled for namespace",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID of the namespace",
......@@ -3448,6 +3462,91 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "timelogs",
"description": "Time logged in issues by group members",
"args": [
{
"name": "startDate",
"description": "List time logs within a time range where the logged date is after start_date parameter.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "endDate",
"description": "List time logs within a time range where the logged date is before end_date parameter.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "TimelogConnection",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "userPermissions",
"description": "Permissions for the current user on the resource",
......@@ -10813,6 +10912,199 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TimelogConnection",
"description": "The connection type for Timelog.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "TimelogEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Timelog",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TimelogEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Timelog",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Timelog",
"description": null,
"fields": [
{
"name": "date",
"description": "The date when the time tracked was spent at",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issue",
"description": "The issue that logged time was added to",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "timeSpent",
"description": "The time spent displayed in seconds",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "user",
"description": "The user that logged the time",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "User",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ProjectStatistics",
......
......@@ -320,7 +320,8 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `webUrl` | String! | Web URL of the group |
| `avatarUrl` | String | Avatar URL of the group |
| `parent` | Group | Parent group |
| `epicsEnabled` | Boolean | |
| `epicsEnabled` | Boolean | Indicates if Epics are enabled for namespace |
| `groupTimelogsEnabled` | Boolean | Indicates if Group timelogs are enabled for namespace |
| `epic` | Epic | |
### GroupPermissions
......@@ -836,6 +837,15 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `count` | Int! | Number of total tasks |
| `completedCount` | Int! | Number of completed tasks |
### Timelog
| Name | Type | Description |
| --- | ---- | ---------- |
| `date` | Time! | The date when the time tracked was spent at |
| `timeSpent` | Int! | The time spent displayed in seconds |
| `user` | User! | The user that logged the time |
| `issue` | Issue | The issue that logged time was added to |
### Todo
| Name | Type | Description |
......
......@@ -2256,11 +2256,11 @@ This example creates three paths of execution:
pipeline will be created with YAML error.
- We are temporarily limiting the maximum number of jobs that a single job can
need in the `needs:` array:
- For GitLab.com, the limit is five. For more information, see our
- For GitLab.com, the limit is ten. For more information, see our
[infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/7541).
- For self-managed instances, the limit is:
- Five by default (`ci_dag_limit_needs` feature flag is enabled).
- 50 if the `ci_dag_limit_needs` feature flag is disabled.
- 10, if the `ci_dag_limit_needs` feature flag is enabled (default).
- 50, if the `ci_dag_limit_needs` feature flag is disabled.
- It is impossible for now to have `needs: []` (empty needs), the job always needs to
depend on something, unless this is the job in the first stage. However, support for
an empty needs array [is planned](https://gitlab.com/gitlab-org/gitlab/issues/30631).
......
......@@ -38,7 +38,7 @@ Feature.enabled?(:feature_flag, project, default_enabled: true)
The [`Project#feature_available?`][project-fa],
[`Namespace#feature_available?`][namespace-fa] (EE), and
[`License.feature_available?`][license-fa] (EE) methods all implicitly check for
a feature flag by the same name as the provided argument.
a by default enabled feature flag with the same name as the provided argument.
For example if a feature is license-gated, there's no need to add an additional
explicit feature flag check since the flag will be checked as part of the
......@@ -56,12 +56,19 @@ isn't gated by a License or Plan.
unless the feature is explicitly disabled or limited to a percentage of users,
the feature flag check will default to `true`.**
As an example, if you were to ship the backend half of a feature behind a flag,
you'd want to explicitly disable that flag until the frontend half is also ready
to be shipped. To make sure this feature is disabled for both GitLab.com and
self-managed instances you'd need to explicitly call `Feature.enabled?` method
before the `feature_available` method. This ensures the feature_flag is defaulting
to `false`.
This is relevant when developing the feature using
[several smaller merge requests](https://about.gitlab.com/handbook/values/#make-small-merge-requests), or when the feature is considered to be an
[alpha or beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga), and
should not be available by default.
As an example, if you were to ship the frontend half of a feature without the
backend, you'd want to disable the feature entirely until the backend half is
also ready to be shipped. To make sure this feature is disabled for both
GitLab.com and self-managed instances, you should use the
[`Namespace#alpha_feature_available?`](https://gitlab.com/gitlab-org/gitlab/blob/458749872f4a8f27abe8add930dbb958044cb926/ee/app/models/ee/namespace.rb#L113) or
[`Namespace#beta_feature_available?`](https://gitlab.com/gitlab-org/gitlab/blob/458749872f4a8f27abe8add930dbb958044cb926/ee/app/models/ee/namespace.rb#L100-112)
method, according to our [definitions](https://about.gitlab.com/handbook/product/#alpha-beta-ga). This ensures the feature is disabled unless the feature flag is
_explicitly_ enabled.
## Feature groups
......
......@@ -208,7 +208,7 @@ and it is useful for knowing which versions won't be compatible between them.
### When to bump the version up
We will have to bump the verision if we rename model/columns or perform any format
We will have to bump the version if we rename model/columns or perform any format
modifications in the JSON structure or the file structure of the archive file.
We do not need to bump the version up in any of the following cases:
......
......@@ -133,6 +133,8 @@ CHROME_HEADLESS=0 bundle exec rspec some_spec.rb
```
The test will go by quickly, but this will give you an idea of what's happening.
Using `live_debug` with `CHROME_HEADLESS=0` pauses the open browser, and does not
open the page again. This can be used to debug and inspect elements.
You can also add `byebug` or `binding.pry` to pause execution and [step through](../pry_debugging.md#stepping)
the test.
......
......@@ -128,9 +128,16 @@ automatically. If you are using [Auto DevOps](../../../topics/autodevops/index.m
need to explicitly provide the `KUBE_NAMESPACE` [deployment variable](#deployment-variables)
that will be used by your deployment jobs, otherwise a namespace will be created for you.
NOTE: **Note:**
If you [install applications](#installing-applications) on your cluster, GitLab will create
the resources required to run these even if you have chosen to manage your own cluster.
#### Important notes
Note the following with GitLab and clusters:
- If you [install applications](#installing-applications) on your cluster, GitLab will
create the resources required to run these even if you have chosen to manage your own
cluster.
- Be aware that manually managing resources that have been created by GitLab, like
namespaces and service accounts, can cause unexpected errors. If this occurs, try
[clearing the cluster cache](#clearing-the-cluster-cache).
#### Clearing the cluster cache
......
......@@ -92,17 +92,17 @@ project.
## Editing a release
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/26016) in GitLab 12.5.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/26016) in GitLab 12.6.
To edit the details of a release, navigate to **Project overview > Releases** and click
the edit button (pencil icon) in the top-right corner of the release you want to modify.
![A release with an edit button](img/release_edit_button_v12_5.png)
![A release with an edit button](img/release_edit_button_v12_6.png)
This will bring you to the **Edit Release** page, from which you can
change some of the release's details.
![Edit release page](img/edit_release_page_v12_5.png)
![Edit release page](img/edit_release_page_v12_6.png)
Currently, it is only possible to edit the release title and notes.
To change other release information, such as its tag, associated
......
......@@ -40,6 +40,8 @@ const moduleNameMapper = {
'^spec/test_constants$': '<rootDir>/spec/frontend/helpers/test_constants',
};
const collectCoverageFrom = ['<rootDir>/app/assets/javascripts/**/*.{js,vue}'];
if (IS_EE) {
const rootDirEE = '<rootDir>/ee/app/assets/javascripts$1';
Object.assign(moduleNameMapper, {
......@@ -47,6 +49,8 @@ if (IS_EE) {
'^ee_component(/.*)$': rootDirEE,
'^ee_else_ce(/.*)$': rootDirEE,
});
collectCoverageFrom.push(rootDirEE.replace('$1', '/**/*.{js,vue}'));
}
// eslint-disable-next-line import/no-commonjs
......@@ -54,7 +58,7 @@ module.exports = {
testMatch,
moduleFileExtensions: ['js', 'json', 'vue'],
moduleNameMapper,
collectCoverageFrom: ['<rootDir>/app/assets/javascripts/**/*.{js,vue}'],
collectCoverageFrom,
coverageDirectory: '<rootDir>/coverage-frontend/',
coverageReporters: ['json', 'lcov', 'text-summary', 'clover'],
cacheDirectory: '<rootDir>/tmp/cache/jest',
......
......@@ -10,7 +10,7 @@ module Gitlab
delegate :dig, to: :@seed_attributes
# When the `ci_dag_limit_needs` is enabled it uses the lower limit
LOW_NEEDS_LIMIT = 5
LOW_NEEDS_LIMIT = 10
HARD_NEEDS_LIMIT = 50
def initialize(pipeline, attributes, previous_stages)
......
......@@ -15,6 +15,7 @@ module Gitlab
:first_seen,
:frequency,
:gitlab_project,
:gitlab_issue,
:id,
:last_release_last_commit,
:last_release_short_version,
......
......@@ -29,6 +29,7 @@ tree:
- :priorities
- :issue_assignees
- :zoom_meetings
- :sentry_issue
- snippets:
- :award_emoji
- notes:
......
......@@ -94,7 +94,7 @@ module Gitlab
relation_index: relation_index,
exception_class: exception.class.to_s,
exception_message: exception.message.truncate(255),
correlation_id_value: Labkit::Correlation::CorrelationId.current_id
correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id
)
end
......
......@@ -233,6 +233,15 @@ module Sentry
stack_trace_entry.dig('stacktrace', 'frames')
end
def parse_gitlab_issue(plugin_issues)
return unless plugin_issues
gitlab_plugin = plugin_issues.detect { |item| item['id'] == 'gitlab' }
return unless gitlab_plugin
gitlab_plugin.dig('issue', 'url')
end
def map_to_detailed_error(issue)
Gitlab::ErrorTracking::DetailedError.new(
id: issue.fetch('id'),
......@@ -252,6 +261,7 @@ module Sentry
project_id: issue.dig('project', 'id'),
project_name: issue.dig('project', 'name'),
project_slug: issue.dig('project', 'slug'),
gitlab_issue: parse_gitlab_issue(issue.fetch('pluginIssues', nil)),
first_release_last_commit: issue.dig('firstRelease', 'lastCommit'),
last_release_last_commit: issue.dig('lastRelease', 'lastCommit'),
first_release_short_version: issue.dig('firstRelease', 'shortVersion'),
......
......@@ -8360,6 +8360,9 @@ msgstr ""
msgid "GitLab Import"
msgstr ""
msgid "GitLab Issue"
msgstr ""
msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
msgstr ""
......
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'gitlab'
require 'optparse'
#
# Configure credentials to be used with gitlab gem
#
Gitlab.configure do |config|
config.endpoint = 'https://gitlab.com/api/v4'
config.private_token = ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
end
options = {}
OptionParser.new do |opts|
opts.on("-s", "--scope=SCOPE", "Find job with matching scope") do |scope|
options[:scope] = scope
end
end.parse!
class PipelineJobFinder
def initialize(project_id, pipeline_id, job_name, options)
@project_id = project_id
@pipeline_id = pipeline_id
@job_name = job_name
@options = options
end
def execute
Gitlab.pipeline_jobs(@project_id, @pipeline_id, @options).auto_paginate do |job|
break job if job.name == @job_name
end
end
end
project_id, pipeline_id, job_name = ARGV
job = PipelineJobFinder.new(project_id, pipeline_id, job_name, options).execute
return if job.nil?
puts job.id
# Sends Slack notification MSG to CI_SLACK_WEBHOOK_URL (which needs to be set).
# ICON_EMOJI needs to be set to an icon emoji name (without the `:` around it).
function notify_slack() {
CHANNEL=$1
MSG=$2
ICON_EMOJI=$3
if [ -z "$CHANNEL" ] || [ -z "$CI_SLACK_WEBHOOK_URL" ] || [ -z "$MSG" ] || [ -z "$ICON_EMOJI" ]; then
echo "Missing argument(s) - Use: $0 channel message icon_emoji"
echo "and set CI_SLACK_WEBHOOK_URL environment variable."
else
curl -X POST --data-urlencode 'payload={"channel": "#'"${CHANNEL}"'", "username": "GitLab QA Bot", "text": "'"${MSG}"'", "icon_emoji": "'":${ICON_EMOJI}:"'"}' "${CI_SLACK_WEBHOOK_URL}"
fi
}
function notify_on_job_failure() {
JOB_NAME=$1
CHANNEL=$2
MSG=$3
ICON_EMOJI=$4
local job_id
job_id=$(scripts/get-job-id "$CI_PROJECT_ID" "$CI_PIPELINE_ID" "$JOB_NAME" -s failed)
if [ -n "${job_id}" ]; then
notify_slack "${CHANNEL}" "${MSG}" "${ICON_EMOJI}"
fi
}
#!/bin/bash
# Sends Slack notification MSG to CI_SLACK_WEBHOOK_URL (which needs to be set).
# ICON_EMOJI needs to be set to an icon emoji name (without the `:` around it).
CHANNEL=$1
MSG=$2
ICON_EMOJI=$3
if [ -z "$CHANNEL" ] || [ -z "$CI_SLACK_WEBHOOK_URL" ] || [ -z "$MSG" ] || [ -z "$ICON_EMOJI" ]; then
echo "Missing argument(s) - Use: $0 channel message icon_emoji"
echo "and set CI_SLACK_WEBHOOK_URL environment variable."
else
curl -X POST --data-urlencode 'payload={"channel": "#'"$CHANNEL"'", "username": "GitLab QA Bot", "text": "'"$MSG"'", "icon_emoji": "'":$ICON_EMOJI:"'"}' "$CI_SLACK_WEBHOOK_URL"
fi
......@@ -8,6 +8,8 @@ global:
configureCertmanager: false
tls:
secretName: tls-cert
initialRootPassword:
secret: shared-gitlab-initial-root-password
certmanager:
install: false
gitlab:
......@@ -26,8 +28,6 @@ gitlab:
mailroom:
enabled: false
migrations:
initialRootPassword:
secret: shared-gitlab-initial-root-password
resources:
requests:
cpu: 350m
......
......@@ -23,6 +23,7 @@ FactoryBot.define do
[Time.now.to_i, 10]
]
end
gitlab_issue { 'http://gitlab.example.com/issues/1' }
first_release_last_commit { '68c914da9' }
last_release_last_commit { '9ad419c86' }
first_release_short_version { 'abc123' }
......
# frozen_string_literal: true
FactoryBot.define do
factory :sentry_issue, class: SentryIssue do
issue
sentry_issue_identifier { 1234567891 }
end
end
......@@ -13,6 +13,7 @@
"short_id",
"status",
"frequency",
"gitlab_issue",
"first_release_last_commit",
"last_release_last_commit",
"first_release_short_version",
......@@ -36,6 +37,7 @@
"short_id": { "type": "string"},
"status": { "type": "string"},
"frequency": { "type": "array"},
"gitlab_issue": { "type": ["string", "null"] },
"first_release_last_commit": { "type": ["string", "null"] },
"last_release_last_commit": { "type": ["string", "null"] },
"first_release_short_version": { "type": ["string", "null"] },
......
......@@ -366,7 +366,12 @@
"type": "ProjectLabel"
}
}
]
],
"sentry_issue": {
"id": 1,
"issue_id": 40,
"sentry_issue_identifier": 1234567891
}
},
{
"id": 39,
......
......@@ -138,5 +138,48 @@ describe('ErrorDetails', () => {
submitSpy.mockRestore();
});
});
describe('GitLab issue link', () => {
const gitlabIssue = 'https://gitlab.example.com/issues/1';
const findGitLabLink = () => wrapper.find(`[href="${gitlabIssue}"]`);
const findCreateIssueButton = () => wrapper.find('[data-qa-selector="create_issue_button"]');
describe('is present', () => {
beforeEach(() => {
store.state.details.loading = false;
store.state.details.error = {
id: 1,
gitlab_issue: gitlabIssue,
};
mountComponent();
});
it('should display the issue link', () => {
expect(findGitLabLink().exists()).toBe(true);
});
it('should not display a create issue button', () => {
expect(findCreateIssueButton().exists()).toBe(false);
});
});
describe('is not present', () => {
beforeEach(() => {
store.state.details.loading = false;
store.state.details.error = {
id: 1,
gitlab_issue: null,
};
mountComponent();
});
it('should not display an issue link', () => {
expect(findGitLabLink().exists()).toBe(false);
});
it('should display the create issue button', () => {
expect(findCreateIssueButton().exists()).toBe(true);
});
});
});
});
});
......@@ -852,7 +852,7 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
it "returns an error" do
expect(subject.errors).to contain_exactly(
"rspec: one job can only need 5 others, but you have listed 6. See needs keyword documentation for more details")
"rspec: one job can only need 10 others, but you have listed 11. See needs keyword documentation for more details")
end
end
......
......@@ -8,6 +8,7 @@ issues:
- milestone
- notes
- resource_label_events
- sentry_issue
- label_links
- labels
- last_edited_by
......@@ -548,4 +549,6 @@ versions: &version
- actions
zoom_meetings:
- issue
sentry_issue:
- issue
design_versions: *version
......@@ -234,6 +234,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(meetings.first.url).to eq('https://zoom.us/j/123456789')
end
it 'restores sentry issues' do
sentry_issue = @project.issues.first.sentry_issue
expect(sentry_issue.sentry_issue_identifier).to eq(1234567891)
end
context 'Merge requests' do
it 'always has the new project as a target' do
expect(MergeRequest.find_by_title('MR1').target_project).to eq(@project)
......@@ -643,7 +649,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
before do
setup_import_export_config('with_invalid_records')
Labkit::Correlation::CorrelationId.use_id(correlation_id) { subject }
# Import is running from the rake task, `correlation_id` is not assigned
expect(Labkit::Correlation::CorrelationId).to receive(:new_id).and_return(correlation_id)
subject
end
context 'when failures occur because a relation fails to be processed' do
......
......@@ -689,6 +689,10 @@ ErrorTracking::ProjectErrorTrackingSetting:
- project_id
- project_name
- organization_name
SentryIssue:
- id
- issue_id
- sentry_issue_identifier
Suggestion:
- id
- note_id
......
......@@ -12,6 +12,7 @@ describe Issue do
it { is_expected.to belong_to(:duplicated_to).class_name('Issue') }
it { is_expected.to belong_to(:closed_by).class_name('User') }
it { is_expected.to have_many(:assignees) }
it { is_expected.to have_one(:sentry_issue) }
end
describe 'modules' do
......
# frozen_string_literal: true
require 'spec_helper'
describe SentryIssue do
describe 'associations' do
it { is_expected.to belong_to(:issue) }
end
describe 'validations' do
let!(:sentry_issue) { create(:sentry_issue) }
it { is_expected.to validate_presence_of(:issue) }
it { is_expected.to validate_uniqueness_of(:issue) }
it { is_expected.to validate_presence_of(:sentry_issue_identifier) }
end
end
......@@ -41,4 +41,30 @@ RSpec.describe Timelog do
expect(subject).to be_valid
end
end
describe 'scopes' do
describe 'for_issues_in_group' do
it 'return timelogs created for group issues' do
group = create(:group)
subgroup = create(:group, parent: group)
create(:timelog, issue: create(:issue, project: create(:project)))
timelog1 = create(:timelog, issue: create(:issue, project: create(:project, group: group)))
timelog2 = create(:timelog, issue: create(:issue, project: create(:project, group: subgroup)))
expect(described_class.for_issues_in_group(group)).to contain_exactly(timelog1, timelog2)
end
end
describe 'between_dates' do
it 'returns collection of timelogs within given dates' do
create(:timelog, spent_at: 65.days.ago)
timelog1 = create(:timelog, spent_at: 15.days.ago)
timelog2 = create(:timelog, spent_at: 5.days.ago)
timelogs = described_class.between_dates(20.days.ago, 1.day.ago)
expect(timelogs).to contain_exactly(timelog1, timelog2)
end
end
end
end
......@@ -6,11 +6,17 @@ module LiveDebugger
def live_debug
puts
puts "Current example is paused for live debugging."
puts "Opening #{current_url} in your default browser..."
if ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
puts "Switch to the Chrome window that was automatically opened to run the test in order to view current page"
else
puts "Opening #{current_url} in your default browser..."
end
puts "The current user credentials are: #{@current_user.username} / #{@current_user.password}" if @current_user
puts "Press any key to resume the execution of the example!!"
`open #{current_url}`
`open #{current_url}` if ENV['CHROME_HEADLESS'] !~ /^(false|no|0)$/i
loop until $stdin.getch
......
......@@ -64,6 +64,12 @@ RSpec::Matchers.define :have_graphql_type do |expected|
end
end
RSpec::Matchers.define :have_non_null_graphql_type do |expected|
match do |field|
expect(field.type).to eq(!expected.to_graphql)
end
end
RSpec::Matchers.define :have_graphql_resolver do |expected|
match do |field|
case expected
......
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