diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-10 12:07:55 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-10 12:07:55 +0000 |
commit | 5e11c9b77cb1b2b77ee29359047b55807afe255d (patch) | |
tree | 40b02dead6acdcaab9cc15efc9ae4710c2ed78a8 | |
parent | 97d4d926630822d0e1a638206909679c962d2f0a (diff) | |
download | gitlab-ce-5e11c9b77cb1b2b77ee29359047b55807afe255d.tar.gz |
Add latest changes from gitlab-org/gitlab@master
51 files changed, 819 insertions, 50 deletions
diff --git a/.gitlab/ci/notifications.gitlab-ci.yml b/.gitlab/ci/notifications.gitlab-ci.yml index 8e2574fad4c..4271e709f45 100644 --- a/.gitlab/ci/notifications.gitlab-ci.yml +++ b/.gitlab/ci/notifications.gitlab-ci.yml @@ -1,18 +1,23 @@ .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 diff --git a/.gitlab/issue_templates/Productivity Improvement.md b/.gitlab/issue_templates/Productivity Improvement.md new file mode 100644 index 00000000000..89505cd85b4 --- /dev/null +++ b/.gitlab/issue_templates/Productivity Improvement.md @@ -0,0 +1,47 @@ +## 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 diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue index 21f487f09f7..64e698a0bc9 100644 --- a/app/assets/javascripts/error_tracking/components/error_details.vue +++ b/app/assets/javascripts/error_tracking/components/error_details.vue @@ -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 diff --git a/app/models/issue.rb b/app/models/issue.rb index 4a17c93e7a2..2f8d6cb0c06 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -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 diff --git a/app/models/sentry_issue.rb b/app/models/sentry_issue.rb new file mode 100644 index 00000000000..678e09b5370 --- /dev/null +++ b/app/models/sentry_issue.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class SentryIssue < ApplicationRecord + belongs_to :issue + + validates :issue, uniqueness: true, presence: true + validates :sentry_issue_identifier, presence: true +end diff --git a/app/models/timelog.rb b/app/models/timelog.rb index 048134fbf04..4ddaf6bcb86 100644 --- a/app/models/timelog.rb +++ b/app/models/timelog.rb @@ -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 diff --git a/app/serializers/error_tracking/detailed_error_entity.rb b/app/serializers/error_tracking/detailed_error_entity.rb index 8f08f84aa41..dd0cac8e4cd 100644 --- a/app/serializers/error_tracking/detailed_error_entity.rb +++ b/app/serializers/error_tracking/detailed_error_entity.rb @@ -10,6 +10,7 @@ module ErrorTracking :first_release_short_version, :first_seen, :frequency, + :gitlab_issue, :id, :last_release_last_commit, :last_release_short_version, diff --git a/changelogs/unreleased/37026-backend-create-a-table-for-sentry-error-related-issues.yml b/changelogs/unreleased/37026-backend-create-a-table-for-sentry-error-related-issues.yml new file mode 100644 index 00000000000..ca6a7a0d698 --- /dev/null +++ b/changelogs/unreleased/37026-backend-create-a-table-for-sentry-error-related-issues.yml @@ -0,0 +1,5 @@ +--- +title: Add SentryIssue table to store a link between issue and sentry issue +merge_request: 37026 +author: +type: added diff --git a/changelogs/unreleased/leipert-increase-dag-limit.yml b/changelogs/unreleased/leipert-increase-dag-limit.yml new file mode 100644 index 00000000000..1da61d951b4 --- /dev/null +++ b/changelogs/unreleased/leipert-increase-dag-limit.yml @@ -0,0 +1,5 @@ +--- +title: Increase lower DAG `needs` limit from five to ten +merge_request: 21237 +author: +type: changed diff --git a/changelogs/unreleased/misaligned_approval_tr.yml b/changelogs/unreleased/misaligned_approval_tr.yml new file mode 100644 index 00000000000..8f6b6df33ee --- /dev/null +++ b/changelogs/unreleased/misaligned_approval_tr.yml @@ -0,0 +1,5 @@ +--- +title: Fix misaligned approval tr +merge_request: 21368 +author: Lee Tickett +type: fixed diff --git a/changelogs/unreleased/tr-link-error-to-issue-frontend.yml b/changelogs/unreleased/tr-link-error-to-issue-frontend.yml new file mode 100644 index 00000000000..8e57e5c3fa5 --- /dev/null +++ b/changelogs/unreleased/tr-link-error-to-issue-frontend.yml @@ -0,0 +1,5 @@ +--- +title: Surface GitLab issue in error detail page +merge_request: 21019 +author: +type: added diff --git a/danger/changelog/Dangerfile b/danger/changelog/Dangerfile index c5d02e1d320..f8e31323f41 100644 --- a/danger/changelog/Dangerfile +++ b/danger/changelog/Dangerfile @@ -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") diff --git a/db/migrate/20191106150931_add_timelog_spent_at_index.rb b/db/migrate/20191106150931_add_timelog_spent_at_index.rb new file mode 100644 index 00000000000..2412b77d0bf --- /dev/null +++ b/db/migrate/20191106150931_add_timelog_spent_at_index.rb @@ -0,0 +1,20 @@ +# 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 diff --git a/db/migrate/20191122161519_create_sentry_issues_table.rb b/db/migrate/20191122161519_create_sentry_issues_table.rb new file mode 100644 index 00000000000..753286b6025 --- /dev/null +++ b/db/migrate/20191122161519_create_sentry_issues_table.rb @@ -0,0 +1,15 @@ +# 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 diff --git a/db/schema.rb b/db/schema.rb index da8dba2e83d..e3df8af0ea0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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 diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 90ec122aaa2..b9af49bd6da 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -2156,6 +2156,10 @@ type Group { """ state: EpicState ): EpicConnection + + """ + Indicates if Epics are enabled for namespace + """ epicsEnabled: Boolean """ @@ -2169,6 +2173,11 @@ type Group { fullPath: ID! """ + Indicates if Group timelogs are enabled for namespace + """ + groupTimelogsEnabled: Boolean + + """ ID of the namespace """ id: ID! @@ -2234,6 +2243,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 """ userPermissions: GroupPermissions! @@ -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 """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index f6019971621..6a41b454610 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -3223,7 +3223,7 @@ }, { "name": "epicsEnabled", - "description": null, + "description": "Indicates if Epics are enabled for namespace", "args": [ ], @@ -3272,6 +3272,20 @@ "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", "args": [ @@ -3449,6 +3463,91 @@ "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", "args": [ @@ -10815,6 +10914,199 @@ }, { "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", "description": null, "fields": [ diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 9c6a873b0e7..8b973c5d3b1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -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 | diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 83efea50a01..ff1a6180f00 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -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). diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md index 77795b8f1d7..cb5d5fe9ab4 100644 --- a/doc/development/feature_flags/development.md +++ b/doc/development/feature_flags/development.md @@ -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 diff --git a/doc/development/import_export.md b/doc/development/import_export.md index 38119d9bbd0..3ee723bc897 100644 --- a/doc/development/import_export.md +++ b/doc/development/import_export.md @@ -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: diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index fe3989474e6..356e3f7a227 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -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. diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 2aa746fc596..6d863a8b888 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -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 diff --git a/doc/user/project/releases/img/edit_release_page_v12_5.png b/doc/user/project/releases/img/edit_release_page_v12_6.png Binary files differindex 8b9c502a2ef..8b9c502a2ef 100644 --- a/doc/user/project/releases/img/edit_release_page_v12_5.png +++ b/doc/user/project/releases/img/edit_release_page_v12_6.png diff --git a/doc/user/project/releases/img/release_edit_button_v12_5.png b/doc/user/project/releases/img/release_edit_button_v12_6.png Binary files differindex f60b0ecb1be..f60b0ecb1be 100644 --- a/doc/user/project/releases/img/release_edit_button_v12_5.png +++ b/doc/user/project/releases/img/release_edit_button_v12_6.png diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md index 54e59a318c3..58e028c89be 100644 --- a/doc/user/project/releases/index.md +++ b/doc/user/project/releases/index.md @@ -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 diff --git a/jest.config.js b/jest.config.js index 3f9dc3fe213..79522552c6d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -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', diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb index dce56b22666..590c7f4d1dd 100644 --- a/lib/gitlab/ci/pipeline/seed/build.rb +++ b/lib/gitlab/ci/pipeline/seed/build.rb @@ -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) diff --git a/lib/gitlab/error_tracking/detailed_error.rb b/lib/gitlab/error_tracking/detailed_error.rb index 8438df2bbf8..169d6c03f12 100644 --- a/lib/gitlab/error_tracking/detailed_error.rb +++ b/lib/gitlab/error_tracking/detailed_error.rb @@ -15,6 +15,7 @@ module Gitlab :first_seen, :frequency, :gitlab_project, + :gitlab_issue, :id, :last_release_last_commit, :last_release_short_version, diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 64bf40b50c2..b3a1d472e73 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -29,6 +29,7 @@ tree: - :priorities - :issue_assignees - :zoom_meetings + - :sentry_issue - snippets: - :award_emoji - notes: diff --git a/lib/gitlab/import_export/relation_tree_restorer.rb b/lib/gitlab/import_export/relation_tree_restorer.rb index 15d1f8a8148..8a2b0cab915 100644 --- a/lib/gitlab/import_export/relation_tree_restorer.rb +++ b/lib/gitlab/import_export/relation_tree_restorer.rb @@ -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 diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb index 450695aa545..6cbee830b17 100644 --- a/lib/sentry/client.rb +++ b/lib/sentry/client.rb @@ -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'), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c3047ffd361..33774dd26ab 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/scripts/get-job-id b/scripts/get-job-id new file mode 100755 index 00000000000..a5d34dc545b --- /dev/null +++ b/scripts/get-job-id @@ -0,0 +1,43 @@ +#!/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 diff --git a/scripts/notifications.sh b/scripts/notifications.sh new file mode 100755 index 00000000000..d1b11d44e88 --- /dev/null +++ b/scripts/notifications.sh @@ -0,0 +1,27 @@ +# 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 +} diff --git a/scripts/notify-slack b/scripts/notify-slack deleted file mode 100755 index 5907fd8b986..00000000000 --- a/scripts/notify-slack +++ /dev/null @@ -1,14 +0,0 @@ -#!/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 diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml index 1014bd9a89f..8465f32b741 100644 --- a/scripts/review_apps/base-config.yaml +++ b/scripts/review_apps/base-config.yaml @@ -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 diff --git a/spec/factories/error_tracking/detailed_error.rb b/spec/factories/error_tracking/detailed_error.rb index 0fee329b808..f12c327d403 100644 --- a/spec/factories/error_tracking/detailed_error.rb +++ b/spec/factories/error_tracking/detailed_error.rb @@ -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' } diff --git a/spec/factories/sentry_issue.rb b/spec/factories/sentry_issue.rb new file mode 100644 index 00000000000..c9886f1673a --- /dev/null +++ b/spec/factories/sentry_issue.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :sentry_issue, class: SentryIssue do + issue + sentry_issue_identifier { 1234567891 } + end +end diff --git a/spec/fixtures/api/schemas/error_tracking/error_detailed.json b/spec/fixtures/api/schemas/error_tracking/error_detailed.json index 40d6773f0e6..2a1cd2c03e0 100644 --- a/spec/fixtures/api/schemas/error_tracking/error_detailed.json +++ b/spec/fixtures/api/schemas/error_tracking/error_detailed.json @@ -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"] }, diff --git a/spec/fixtures/lib/gitlab/import_export/complex/project.json b/spec/fixtures/lib/gitlab/import_export/complex/project.json index 8dc91b05f4d..acfd6a6924a 100644 --- a/spec/fixtures/lib/gitlab/import_export/complex/project.json +++ b/spec/fixtures/lib/gitlab/import_export/complex/project.json @@ -366,7 +366,12 @@ "type": "ProjectLabel" } } - ] + ], + "sentry_issue": { + "id": 1, + "issue_id": 40, + "sentry_issue_identifier": 1234567891 + } }, { "id": 39, diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js index 72f3577c530..632ed373545 100644 --- a/spec/frontend/error_tracking/components/error_details_spec.js +++ b/spec/frontend/error_tracking/components/error_details_spec.js @@ -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); + }); + }); + }); }); }); diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 53dcb6359fe..2ae513aea1b 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -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 diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 24f0eb9a30c..ab17d9993f6 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -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 diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 67614eb509d..f549216ccb0 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -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 diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 05cae858450..fa6bf14bf64 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -689,6 +689,10 @@ ErrorTracking::ProjectErrorTrackingSetting: - project_id - project_name - organization_name +SentryIssue: +- id +- issue_id +- sentry_issue_identifier Suggestion: - id - note_id diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 0f78cb4d9b1..4d115be9524 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -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 diff --git a/spec/models/sentry_issue_spec.rb b/spec/models/sentry_issue_spec.rb new file mode 100644 index 00000000000..b3f7d2628db --- /dev/null +++ b/spec/models/sentry_issue_spec.rb @@ -0,0 +1,17 @@ +# 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 diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb index 7321a458817..33c1afad59f 100644 --- a/spec/models/timelog_spec.rb +++ b/spec/models/timelog_spec.rb @@ -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 diff --git a/spec/support/helpers/live_debugger.rb b/spec/support/helpers/live_debugger.rb index d6091035b59..cdb068760f4 100644 --- a/spec/support/helpers/live_debugger.rb +++ b/spec/support/helpers/live_debugger.rb @@ -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 diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index d735c10f698..ebba5d8a73c 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -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 |