diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-07 00:09:01 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-07 00:09:01 +0300 |
commit | ca3ff7f842fb1e4bf124356a6faccd3e65b7aba3 (patch) | |
tree | befb345c84e172b0592378ca17298f7a0c955a06 | |
parent | 1287690a3678ad0ec21c9b2f3b21ae18257d5e22 (diff) |
Add latest changes from gitlab-org/gitlab@master
71 files changed, 1112 insertions, 285 deletions
diff --git a/.rubocop_todo/style/class_and_module_children.yml b/.rubocop_todo/style/class_and_module_children.yml index 2674902c7ee..bff827ba5d3 100644 --- a/.rubocop_todo/style/class_and_module_children.yml +++ b/.rubocop_todo/style/class_and_module_children.yml @@ -521,6 +521,7 @@ Style/ClassAndModuleChildren: - 'ee/db/fixtures/development/21_dast_profiles.rb' - 'ee/db/fixtures/development/30_customizable_cycle_analytics.rb' - 'ee/db/fixtures/development/32_compliance_report_violations.rb' + - 'ee/db/fixtures/development/35_merge_request_predictions.rb' - 'ee/db/fixtures/development/90_productivity_analytics.rb' - 'ee/lib/ee/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb' - 'ee/lib/ee/gitlab/analytics/cycle_analytics/base_query_builder.rb' diff --git a/app/assets/javascripts/alert_management/components/alert_management_table.vue b/app/assets/javascripts/alert_management/components/alert_management_table.vue index 37a6ea16018..c0cac958a42 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_table.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_table.vue @@ -216,8 +216,11 @@ export default { this.pagination = initialPaginationState; this.sort = sortObjectToString({ sortBy, sortDesc }); }, + showAlertLink({ iid }) { + return joinPaths(window.location.pathname, iid, 'details'); + }, navigateToAlertDetails({ iid }, index, { metaKey }) { - return visitUrl(joinPaths(window.location.pathname, iid, 'details'), metaKey); + return visitUrl(this.showAlertLink({ iid }), metaKey); }, hasAssignees(assignees) { return Boolean(assignees.nodes?.length); @@ -357,7 +360,7 @@ export default { :title="`${item.iid} - ${item.title}`" data-testid="idField" > - #{{ item.iid }} {{ item.title }} + <gl-link :href="showAlertLink(item)"> #{{ item.iid }} {{ item.title }} </gl-link> </div> </template> diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index afdf6b9eee5..52bf9d25e6b 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -8,34 +8,74 @@ "type": "string", "format": "uri" }, - "image": { "$ref": "#/definitions/image" }, - "services": { "$ref": "#/definitions/services" }, - "before_script": { "$ref": "#/definitions/before_script" }, - "after_script": { "$ref": "#/definitions/after_script" }, - "variables": { "$ref": "#/definitions/globalVariables" }, - "cache": { "$ref": "#/definitions/cache" }, - "!reference": {"$ref" : "#/definitions/!reference"}, + "image": { + "$ref": "#/definitions/image" + }, + "services": { + "$ref": "#/definitions/services" + }, + "before_script": { + "$ref": "#/definitions/before_script" + }, + "after_script": { + "$ref": "#/definitions/after_script" + }, + "variables": { + "$ref": "#/definitions/globalVariables" + }, + "cache": { + "$ref": "#/definitions/cache" + }, + "!reference": { + "$ref": "#/definitions/!reference" + }, "default": { "type": "object", "properties": { - "after_script": { "$ref": "#/definitions/after_script" }, - "artifacts": { "$ref": "#/definitions/artifacts" }, - "before_script": { "$ref": "#/definitions/before_script" }, - "cache": { "$ref": "#/definitions/cache" }, - "image": { "$ref": "#/definitions/image" }, - "interruptible": { "$ref": "#/definitions/interruptible" }, - "retry": { "$ref": "#/definitions/retry" }, - "services": { "$ref": "#/definitions/services" }, - "tags": { "$ref": "#/definitions/tags" }, - "timeout": { "$ref": "#/definitions/timeout" }, - "!reference": {"$ref" : "#/definitions/!reference"} + "after_script": { + "$ref": "#/definitions/after_script" + }, + "artifacts": { + "$ref": "#/definitions/artifacts" + }, + "before_script": { + "$ref": "#/definitions/before_script" + }, + "cache": { + "$ref": "#/definitions/cache" + }, + "image": { + "$ref": "#/definitions/image" + }, + "interruptible": { + "$ref": "#/definitions/interruptible" + }, + "retry": { + "$ref": "#/definitions/retry" + }, + "services": { + "$ref": "#/definitions/services" + }, + "tags": { + "$ref": "#/definitions/tags" + }, + "timeout": { + "$ref": "#/definitions/timeout" + }, + "!reference": { + "$ref": "#/definitions/!reference" + } }, "additionalProperties": false }, "stages": { "type": "array", "markdownDescription": "Groups jobs into stages. All jobs in one stage must complete before next stage is executed. Defaults to ['build', 'test', 'deploy']. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#stages).", - "default": ["build", "test", "deploy"], + "default": [ + "build", + "test", + "deploy" + ], "items": { "type": "string" }, @@ -45,10 +85,14 @@ "include": { "markdownDescription": "Can be `IncludeItem` or `IncludeItem[]`. Each `IncludeItem` will be a string, or an object with properties for the method if including external YAML file. The external content will be fetched, included and evaluated along the `.gitlab-ci.yml`. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#include).", "oneOf": [ - { "$ref": "#/definitions/include_item" }, + { + "$ref": "#/definitions/include_item" + }, { "type": "array", - "items": { "$ref": "#/definitions/include_item" } + "items": { + "$ref": "#/definitions/include_item" + } } ] }, @@ -63,17 +107,36 @@ "type": "array", "items": { "anyOf": [ - {"type": "object"}, - {"type": "array", "minLength": 1, "items": { "type": "string" }} + { + "type": "object" + }, + { + "type": "array", + "minLength": 1, + "items": { + "type": "string" + } + } ], "properties": { - "if": { "$ref": "#/definitions/if" }, - "changes": { "$ref": "#/definitions/changes" }, - "exists": { "$ref": "#/definitions/exists" }, - "variables": { "$ref": "#/definitions/variables" }, + "if": { + "$ref": "#/definitions/if" + }, + "changes": { + "$ref": "#/definitions/changes" + }, + "exists": { + "$ref": "#/definitions/exists" + }, + "variables": { + "$ref": "#/definitions/variables" + }, "when": { "type": "string", - "enum": ["always", "never"] + "enum": [ + "always", + "never" + ] } }, "additionalProperties": false @@ -86,8 +149,12 @@ "^[.]": { "description": "Hidden keys.", "anyOf": [ - { "$ref": "#/definitions/job_template" }, - { "description": "Arbitrary YAML anchor." } + { + "$ref": "#/definitions/job_template" + }, + { + "description": "Arbitrary YAML anchor." + } ] } }, @@ -134,15 +201,21 @@ "default": "on_success", "oneOf": [ { - "enum": ["on_success"], + "enum": [ + "on_success" + ], "description": "Upload artifacts only when the job succeeds (this is the default)." }, { - "enum": ["on_failure"], + "enum": [ + "on_failure" + ], "description": "Upload artifacts only when the job fails." }, { - "enum": ["always"], + "enum": [ + "always" + ], "description": "Upload artifacts regardless of job status." } ] @@ -180,7 +253,9 @@ "properties": { "coverage_format": { "description": "Code coverage format used by the test framework.", - "enum": ["cobertura"] + "enum": [ + "cobertura" + ] }, "path": { "description": "Path to the coverage report file that should be parsed.", @@ -284,9 +359,13 @@ "format": "uri-reference", "pattern": "\\.ya?ml$" }, - "rules": { "$ref": "#/definitions/rules" } + "rules": { + "$ref": "#/definitions/rules" + } }, - "required": ["local"] + "required": [ + "local" + ] }, { "type": "object", @@ -319,7 +398,10 @@ ] } }, - "required": ["project", "file"] + "required": [ + "project", + "file" + ] }, { "type": "object", @@ -332,7 +414,9 @@ "pattern": "\\.ya?ml$" } }, - "required": ["template"] + "required": [ + "template" + ] }, { "type": "object", @@ -345,7 +429,9 @@ "pattern": "^https?://.+\\.ya?ml$" } }, - "required": ["remote"] + "required": [ + "remote" + ] } ] }, @@ -406,7 +492,9 @@ ] } }, - "required": ["name"] + "required": [ + "name" + ] }, { "type": "array", @@ -487,7 +575,9 @@ "minLength": 1 } }, - "required": ["name"] + "required": [ + "name" + ] } ] } @@ -511,20 +601,37 @@ "engine": { "type": "object", "properties": { - "name": { "type": "string" }, - "path": { "type": "string" } + "name": { + "type": "string" + }, + "path": { + "type": "string" + } }, - "required": ["name", "path"] + "required": [ + "name", + "path" + ] }, - "path": { "type": "string" }, - "field": { "type": "string" } + "path": { + "type": "string" + }, + "field": { + "type": "string" + } }, - "required": ["engine", "path", "field"] + "required": [ + "engine", + "path", + "field" + ] } ] } }, - "required": ["vault"] + "required": [ + "vault" + ] } }, "before_script": { @@ -570,17 +677,40 @@ "type": "object", "additionalProperties": false, "properties": { - "if": { "$ref": "#/definitions/if" }, - "changes": { "$ref": "#/definitions/changes" }, - "exists": { "$ref": "#/definitions/exists" }, - "variables": { "$ref": "#/definitions/variables" }, - "when": { "$ref": "#/definitions/when" }, - "start_in": { "$ref": "#/definitions/start_in" }, - "allow_failure": { "$ref": "#/definitions/allow_failure" } + "if": { + "$ref": "#/definitions/if" + }, + "changes": { + "$ref": "#/definitions/changes" + }, + "exists": { + "$ref": "#/definitions/exists" + }, + "variables": { + "$ref": "#/definitions/variables" + }, + "when": { + "$ref": "#/definitions/when" + }, + "start_in": { + "$ref": "#/definitions/start_in" + }, + "allow_failure": { + "$ref": "#/definitions/allow_failure" + } } }, - {"type": "string", "minLength": 1}, - {"type": "array", "minLength": 1, "items": { "type": "string" }} + { + "type": "string", + "minLength": 1 + }, + { + "type": "array", + "minLength": 1, + "items": { + "type": "string" + } + } ] } }, @@ -591,7 +721,10 @@ ".*": { "oneOf": [ { - "type": ["string", "number"] + "type": [ + "string", + "number" + ] }, { "type": "object", @@ -621,7 +754,9 @@ { "type": "object", "additionalProperties": false, - "required": ["paths"], + "required": [ + "paths" + ], "properties": { "paths": { "type": "array", @@ -656,7 +791,10 @@ "type": "object", "patternProperties": { ".*": { - "type": ["string", "number"] + "type": [ + "string", + "number" + ] }, "additionalProperties": false } @@ -683,7 +821,9 @@ "description": "Exit code that are not considered failure. The job fails for any other exit code.", "type": "object", "additionalProperties": false, - "required": ["exit_codes"], + "required": [ + "exit_codes" + ], "properties": { "exit_codes": { "type": "integer" @@ -694,7 +834,9 @@ "description": "You can list which exit codes are not considered failures. The job fails for any other exit code.", "type": "object", "additionalProperties": false, - "required": ["exit_codes"], + "required": [ + "exit_codes" + ], "properties": { "exit_codes": { "type": "array", @@ -713,27 +855,39 @@ "default": "on_success", "oneOf": [ { - "enum": ["on_success"], + "enum": [ + "on_success" + ], "description": "Execute job only when all jobs from prior stages succeed." }, { - "enum": ["on_failure"], + "enum": [ + "on_failure" + ], "description": "Execute job when at least one job from prior stages fails." }, { - "enum": ["always"], + "enum": [ + "always" + ], "description": "Execute job regardless of the status from prior stages." }, { - "enum": ["manual"], + "enum": [ + "manual" + ], "markdownDescription": "Execute the job manually from Gitlab UI or API. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#when)." }, { - "enum": ["delayed"], + "enum": [ + "delayed" + ], "markdownDescription": "Execute a job after the time limit in 'start_in' expires. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#when)." }, { - "enum": ["never"], + "enum": [ + "never" + ], "description": "Never execute the job." } ] @@ -745,15 +899,21 @@ "default": "on_success", "oneOf": [ { - "enum": ["on_success"], + "enum": [ + "on_success" + ], "description": "Save the cache only when the job succeeds." }, { - "enum": ["on_failure"], + "enum": [ + "on_failure" + ], "description": "Save the cache only when the job fails. " }, { - "enum": ["always"], + "enum": [ + "always" + ], "description": "Always save the cache. " } ] @@ -805,15 +965,21 @@ "default": "pull-push", "oneOf": [ { - "enum": ["pull"], + "enum": [ + "pull" + ], "description": "Pull will download cache but skip uploading after job completes." }, { - "enum": ["push"], + "enum": [ + "push" + ], "description": "Push will skip downloading cache and always recreate cache after job completes." }, { - "enum": ["pull-push"], + "enum": [ + "pull-push" + ], "description": "Pull-push will both download cache at job start and upload cache on job success." } ] @@ -828,39 +994,57 @@ { "oneOf": [ { - "enum": ["branches"], + "enum": [ + "branches" + ], "description": "When a branch is pushed." }, { - "enum": ["tags"], + "enum": [ + "tags" + ], "description": "When a tag is pushed." }, { - "enum": ["api"], + "enum": [ + "api" + ], "description": "When a pipeline has been triggered by a second pipelines API (not triggers API)." }, { - "enum": ["external"], + "enum": [ + "external" + ], "description": "When using CI services other than Gitlab" }, { - "enum": ["pipelines"], + "enum": [ + "pipelines" + ], "description": "For multi-project triggers, created using the API with 'CI_JOB_TOKEN'." }, { - "enum": ["pushes"], + "enum": [ + "pushes" + ], "description": "Pipeline is triggered by a `git push` by the user" }, { - "enum": ["schedules"], + "enum": [ + "schedules" + ], "description": "For scheduled pipelines." }, { - "enum": ["triggers"], + "enum": [ + "triggers" + ], "description": "For pipelines created using a trigger token." }, { - "enum": ["web"], + "enum": [ + "web" + ], "description": "For pipelines created using *Run pipeline* button in Gitlab UI (under your project's *Pipelines*)." } ] @@ -888,7 +1072,9 @@ "$ref": "#/definitions/filter_refs" }, "kubernetes": { - "enum": ["active"], + "enum": [ + "active" + ], "description": "Filter job based on if Kubernetes integration is active." }, "variables": { @@ -912,16 +1098,22 @@ "retry": { "markdownDescription": "Retry a job if it fails. Can be a simple integer or object definition. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#retry).", "oneOf": [ - { "$ref": "#/definitions/retry_max" }, + { + "$ref": "#/definitions/retry_max" + }, { "type": "object", "additionalProperties": false, "properties": { - "max": { "$ref": "#/definitions/retry_max" }, + "max": { + "$ref": "#/definitions/retry_max" + }, "when": { "markdownDescription": "Either a single or array of error types to trigger job retry. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#retrywhen).", "oneOf": [ - { "$ref": "#/definitions/retry_errors" }, + { + "$ref": "#/definitions/retry_errors" + }, { "type": "array", "items": { @@ -1004,21 +1196,39 @@ }, "job": { "allOf": [ - { "$ref": "#/definitions/job_template" } + { + "$ref": "#/definitions/job_template" + } ] }, "job_template": { "type": "object", "additionalProperties": false, "properties": { - "image": { "$ref": "#/definitions/image" }, - "services": { "$ref": "#/definitions/services" }, - "before_script": { "$ref": "#/definitions/before_script" }, - "after_script": { "$ref": "#/definitions/after_script" }, - "rules": { "$ref": "#/definitions/rules" }, - "variables": { "$ref": "#/definitions/variables" }, - "cache": { "$ref": "#/definitions/cache" }, - "secrets": { "$ref": "#/definitions/secrets" }, + "image": { + "$ref": "#/definitions/image" + }, + "services": { + "$ref": "#/definitions/services" + }, + "before_script": { + "$ref": "#/definitions/before_script" + }, + "after_script": { + "$ref": "#/definitions/after_script" + }, + "rules": { + "$ref": "#/definitions/rules" + }, + "variables": { + "$ref": "#/definitions/variables" + }, + "cache": { + "$ref": "#/definitions/cache" + }, + "secrets": { + "$ref": "#/definitions/secrets" + }, "script": { "markdownDescription": "Shell scripts executed by the Runner. The only required property of jobs. Be careful with special characters (e.g. `:`, `{`, `}`, `&`) and use single or double quotes to avoid issues. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#script)", "oneOf": [ @@ -1060,7 +1270,7 @@ } } ] - }, + }, "only": { "$ref": "#/definitions/filter", "description": "Job will run *only* when these filtering options match." @@ -1102,7 +1312,9 @@ "type": "boolean" } }, - "required": ["job"] + "required": [ + "job" + ] }, { "type": "object", @@ -1118,7 +1330,10 @@ "type": "boolean" } }, - "required": ["job", "pipeline"] + "required": [ + "job", + "pipeline" + ] }, { "type": "object", @@ -1137,7 +1352,11 @@ "type": "boolean" } }, - "required": ["job", "project", "ref"] + "required": [ + "job", + "project", + "ref" + ] } ] } @@ -1174,7 +1393,9 @@ "environment": { "description": "Used to associate environment metadata with a deploy. Environment can have a name and URL attached to it, and will be displayed under /environments under the project.", "oneOf": [ - { "type": "string" }, + { + "type": "string" + }, { "type": "object", "additionalProperties": false, @@ -1195,7 +1416,13 @@ "description": "The name of a job to execute when the environment is about to be stopped." }, "action": { - "enum": ["start", "prepare", "stop", "verify", "access"], + "enum": [ + "start", + "prepare", + "stop", + "verify", + "access" + ], "description": "Specifies what this job will do. 'start' (default) indicates the job will start the deployment. 'prepare'/'verify'/'access' indicates this will not affect the deployment. 'stop' indicates this will stop the deployment.", "default": "start" }, @@ -1226,7 +1453,9 @@ ] } }, - "required": ["name"] + "required": [ + "name" + ] } ] }, @@ -1306,15 +1535,23 @@ ] } }, - "required": ["name", "url"] + "required": [ + "name", + "url" + ] }, "minItems": 1 } }, - "required": ["links"] + "required": [ + "links" + ] } }, - "required": ["tag_name", "description"] + "required": [ + "tag_name", + "description" + ] }, "coverage": { "type": "string", @@ -1345,14 +1582,20 @@ "type": "object", "description": "Defines environment variables for specific job.", "additionalProperties": { - "type": ["string", "number", "array"] + "type": [ + "string", + "number", + "array" + ] } }, "maxItems": 50 } }, "additionalProperties": false, - "required": ["matrix"] + "required": [ + "matrix" + ] } ] }, @@ -1383,7 +1626,9 @@ "strategy": { "description": "You can mirror the pipeline status from the triggered pipeline to the source bridge job by using strategy: depend", "type": "string", - "enum": ["depend"] + "enum": [ + "depend" + ] }, "forward": { "description": "Specify what to forward to the downstream pipeline.", @@ -1403,9 +1648,13 @@ } } }, - "required": ["project"], + "required": [ + "project" + ], "dependencies": { - "branch": ["project"] + "branch": [ + "project" + ] } }, { @@ -1466,7 +1715,10 @@ "type": "string" } }, - "required": ["artifact", "job"] + "required": [ + "artifact", + "job" + ] }, { "type": "object", @@ -1489,7 +1741,10 @@ "pattern": "\\.ya?ml$" } }, - "required": ["project", "file"] + "required": [ + "project", + "file" + ] } ] } @@ -1499,7 +1754,9 @@ "strategy": { "description": "You can mirror the pipeline status from the triggered pipeline to the source bridge job by using strategy: depend", "type": "string", - "enum": ["depend"] + "enum": [ + "depend" + ] }, "forward": { "description": "Specify what to forward to the downstream pipeline.", @@ -1560,7 +1817,9 @@ "variables": { "markdownDescription": "Whether to inherit all globally-defined variables or not. Or subset of inherited variables. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#inheritvariables).", "oneOf": [ - { "type": "boolean" }, + { + "type": "boolean" + }, { "type": "array", "items": { @@ -1576,15 +1835,24 @@ "oneOf": [ { "properties": { - "when": { "enum": ["delayed"] } + "when": { + "enum": [ + "delayed" + ] + } }, - "required": ["when", "start_in"] + "required": [ + "when", + "start_in" + ] }, { "properties": { "when": { "not": { - "enum": ["delayed"] + "enum": [ + "delayed" + ] } } } @@ -1612,4 +1880,4 @@ } } } -} +}
\ No newline at end of file diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue index 3c07dee025f..4b7d606b52c 100644 --- a/app/assets/javascripts/integrations/edit/components/integration_form.vue +++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue @@ -230,10 +230,8 @@ export default { @change="setOverride" /> - <div v-if="!hasSections" class="row"> - <div class="col-lg-4"></div> - - <div class="col-lg-8"> + <section v-if="!hasSections" class="gl-lg-display-flex gl-justify-content-end"> + <div class="gl-flex-basis-two-thirds"> <!-- helpHtml is trusted input --> <div v-if="helpHtml" v-safe-html:[$options.helpHtmlConfig]="helpHtml"></div> @@ -249,7 +247,7 @@ export default { :type="propsSource.type" /> </div> - </div> + </section> <template v-if="hasSections"> <div @@ -258,8 +256,8 @@ export default { :class="{ 'gl-border-b gl-pb-3 gl-mb-6': index !== customState.sections.length - 1 }" data-testid="integration-section" > - <div class="row"> - <div class="col-lg-4"> + <section class="gl-lg-display-flex"> + <div class="gl-flex-basis-third"> <h4 class="gl-mt-0"> {{ section.title }}<gl-badge @@ -277,7 +275,7 @@ export default { <p v-safe-html="section.description"></p> </div> - <div class="col-lg-8"> + <div class="gl-flex-basis-two-thirds"> <component :is="$options.integrationFormSectionComponents[section.type]" :fields="fieldsForSection(section)" @@ -286,14 +284,12 @@ export default { @request-jira-issue-types="onRequestJiraIssueTypes" /> </div> - </div> + </section> </div> </template> - <div v-if="hasFieldsWithoutSection" class="row"> - <div class="col-lg-4"></div> - - <div class="col-lg-8"> + <section v-if="hasFieldsWithoutSection" class="gl-lg-display-flex gl-justify-content-end"> + <div class="gl-flex-basis-two-thirds"> <dynamic-field v-for="field in fieldsWithoutSection" :key="`${currentKey}-${field.name}`" @@ -302,12 +298,12 @@ export default { :data-qa-selector="`${field.name}_div`" /> </div> - </div> + </section> - <div v-if="isEditable" class="row"> - <div :class="hasSections ? 'col' : 'col-lg-8 offset-lg-4'"> + <section v-if="isEditable" :class="!hasSections && 'gl-lg-display-flex gl-justify-content-end'"> + <div :class="!hasSections && 'gl-flex-basis-two-thirds'"> <div - class="footer-block row-content-block gl-display-flex gl-justify-content-space-between" + class="footer-block row-content-block gl-lg-display-flex gl-justify-content-space-between" > <div> <template v-if="isInstanceOrGroupLevel"> @@ -369,6 +365,6 @@ export default { </template> </div> </div> - </div> + </section> </gl-form> </template> diff --git a/app/assets/javascripts/notes/components/discussion_filter_note.vue b/app/assets/javascripts/notes/components/discussion_filter_note.vue index 61af0b06535..39b3df899a5 100644 --- a/app/assets/javascripts/notes/components/discussion_filter_note.vue +++ b/app/assets/javascripts/notes/components/discussion_filter_note.vue @@ -31,7 +31,7 @@ export default { <div class="timeline-icon d-none d-lg-flex"> <gl-icon name="comment" /> </div> - <div class="timeline-content"> + <div class="timeline-content gl-pl-8"> <div data-testid="discussion-filter-timeline-content"> <gl-sprintf :message="$options.i18n.information"> <template #bold="{ content }"> diff --git a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/constants.js b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/constants.js index b7768cfa5b9..df1188d365b 100644 --- a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/constants.js +++ b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/constants.js @@ -4,7 +4,7 @@ export const tdClass = 'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap'; export const thClass = 'gl-hover-bg-blue-50'; export const bodyTrClass = - 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-cursor-pointer gl-hover-bg-blue-50 gl-hover-border-b-solid gl-hover-border-blue-200'; + 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-cursor-pointer gl-hover-bg-gray-50 gl-hover-border-b-solid'; export const defaultPageSize = 20; diff --git a/app/assets/stylesheets/components/milestone_combobox.scss b/app/assets/stylesheets/components/milestone_combobox.scss deleted file mode 100644 index 94d295c324b..00000000000 --- a/app/assets/stylesheets/components/milestone_combobox.scss +++ /dev/null @@ -1,5 +0,0 @@ -.milestone-combobox { - .dropdown-menu.show { - overflow: hidden; - } -} diff --git a/app/assets/stylesheets/components/release_block.scss b/app/assets/stylesheets/components/release_block.scss deleted file mode 100644 index 7e82d0960d7..00000000000 --- a/app/assets/stylesheets/components/release_block.scss +++ /dev/null @@ -1,3 +0,0 @@ -.release-block { - transition: background-color 1s linear; -} diff --git a/app/assets/stylesheets/components/dashboard_skeleton.scss b/app/assets/stylesheets/page_bundles/operations.scss index 1dcaa47470b..497cb63033c 100644 --- a/app/assets/stylesheets/components/dashboard_skeleton.scss +++ b/app/assets/stylesheets/page_bundles/operations.scss @@ -1,3 +1,5 @@ +@import 'mixins_and_variables_and_functions'; + .dashboard-cards { margin-right: -$gl-padding-8; margin-left: -$gl-padding-8; @@ -8,7 +10,7 @@ &-header { &-warning { - background-color: $orange-100; + background-color: var(--orange-100, $orange-100); } } @@ -16,16 +18,16 @@ min-height: 120px; &-warning { - background-color: $orange-50; + background-color: var(--orange-50, $orange-50); } &-failed { - background-color: $red-50; + background-color: var(--red-50, $red-50); } } &-icon { - color: $gray-300; + color: var(--gray-300, $gray-300); } &-footer { @@ -33,7 +35,7 @@ height: $gl-padding-32; &-arrow { - color: $gray-200; + color: var(--gray-200, $gray-200); } &-downstream { @@ -41,7 +43,7 @@ } &-extra { - background-color: $gray-200; + background-color: var(--gray-200, $gray-200); font-size: 10px; line-height: $gl-line-height; width: $gl-padding; @@ -50,7 +52,7 @@ &-header { &-failed { - background-color: $red-100; + background-color: var(--red-100, $red-100); } } @@ -66,10 +68,10 @@ background-repeat: no-repeat; background-size: cover; background-image: linear-gradient(to right, - $gray-50 0%, - $gray-10 20%, - $gray-50 40%, - $gray-50 100%); + var(--gray-50, $gray-50) 0%, + var(--gray-10, $gray-10) 20%, + var(--gray-50, $gray-50) 40%, + var(--gray-50, $gray-50) 100%); border-radius: $gl-padding; height: $gl-padding; margin-top: -$gl-padding-8; diff --git a/app/assets/stylesheets/components/release_block_milestone_info.scss b/app/assets/stylesheets/page_bundles/releases.scss index b6a85ae965a..24ffbf9b90c 100644 --- a/app/assets/stylesheets/components/release_block_milestone_info.scss +++ b/app/assets/stylesheets/page_bundles/releases.scss @@ -1,3 +1,9 @@ +@import 'mixins_and_variables_and_functions'; + +.release-block { + transition: background-color 1s linear; +} + .release-block-milestone-info { .milestone-progress-bar-container { width: 300px; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index bd61e8df699..cfad34760ad 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -435,8 +435,8 @@ $system-note-svg-size: 1rem; .discussion-filter-note { .timeline-icon { - width: $system-note-icon-size + 6; - height: $system-note-icon-size + 6; + width: $system-note-icon-size; + height: $system-note-icon-size; margin-top: -8px; } } diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index ecd6270ed47..9f9d0da6efd 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -19,7 +19,7 @@ class LabelsFinder < UnionFinder items = with_title(items) items = by_subscription(items) items = by_search(items) - sort(items) + sort(items.with_preloaded_container) end private diff --git a/app/graphql/resolvers/base_issues_resolver.rb b/app/graphql/resolvers/base_issues_resolver.rb index 479b1df6876..6357132705e 100644 --- a/app/graphql/resolvers/base_issues_resolver.rb +++ b/app/graphql/resolvers/base_issues_resolver.rb @@ -47,7 +47,6 @@ module Resolvers def preloads { alert_management_alert: [:alert_management_alert], - labels: [:labels], assignees: [:assignees], participants: Issue.participant_includes, timelogs: [:timelogs], diff --git a/app/graphql/resolvers/bulk_labels_resolver.rb b/app/graphql/resolvers/bulk_labels_resolver.rb new file mode 100644 index 00000000000..a758ef70f93 --- /dev/null +++ b/app/graphql/resolvers/bulk_labels_resolver.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Resolvers + class BulkLabelsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type Types::LabelType.connection_type, null: true + + def resolve + authorize!(object) + + BatchLoader::GraphQL.for(object.id).batch(cache: false) do |ids, loader, args| + labels = Label.for_targets(ids, object.class.name).group_by(&:target_id) + + ids.each do |id| + loader.call(id, labels[id] || []) + end + end + end + + private + + def authorized_resource?(object) + Ability.allowed?(current_user, :read_label, object.issuing_parent) + end + end +end diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb index 697cc6f5b03..b6510e7c5fa 100644 --- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb +++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb @@ -42,7 +42,6 @@ module ResolvesMergeRequests assignees: [:assignees], reviewers: [:reviewers], participants: MergeRequest.participant_includes, - labels: [:labels], author: [:author], merged_at: [:metrics], commit_count: [:metrics], diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index ca4eb044bab..76fac831199 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -43,8 +43,10 @@ module Types field :updated_by, Types::UserType, null: true, description: 'User that last updated the issue.' - field :labels, Types::LabelType.connection_type, null: true, - description: 'Labels of the issue.' + field :labels, Types::LabelType.connection_type, + null: true, + description: 'Labels of the issue.', + resolver: Resolvers::BulkLabelsResolver field :milestone, Types::MilestoneType, null: true, description: 'Milestone of the issue.' diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 3a39236bafa..8cc600fc68e 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -158,8 +158,11 @@ module Types description: 'Human-readable time estimate of the merge request.' field :human_total_time_spent, GraphQL::Types::String, null: true, description: 'Human-readable total time reported as spent on the merge request.' - field :labels, Types::LabelType.connection_type, null: true, complexity: 5, - description: 'Labels of the merge request.' + field :labels, Types::LabelType.connection_type, + null: true, complexity: 5, + description: 'Labels of the merge request.', + resolver: Resolvers::BulkLabelsResolver + field :milestone, Types::MilestoneType, null: true, description: 'Milestone of the merge request.' field :participants, Types::MergeRequests::ParticipantType.connection_type, null: true, complexity: 15, diff --git a/app/models/ci/secure_file.rb b/app/models/ci/secure_file.rb index 0644c9f3235..3cc57e8f907 100644 --- a/app/models/ci/secure_file.rb +++ b/app/models/ci/secure_file.rb @@ -7,6 +7,7 @@ module Ci FILE_SIZE_LIMIT = 5.megabytes.freeze CHECKSUM_ALGORITHM = 'sha256' + PARSABLE_EXTENSIONS = ['cer'].freeze self.limit_scope = :project self.limit_name = 'project_ci_secure_files' @@ -34,6 +35,37 @@ module Ci CHECKSUM_ALGORITHM end + def file_extension + File.extname(name).delete_prefix('.') + end + + def metadata_parsable? + PARSABLE_EXTENSIONS.include?(file_extension) + end + + def metadata_parser + return unless metadata_parsable? + + case file_extension + when 'cer' + Gitlab::Ci::SecureFiles::Cer.new(file.read) + end + end + + def update_metadata! + return unless metadata_parser + + begin + parser = metadata_parser + self.metadata = parser.metadata + self.expires_at = parser.expires_at if parser.respond_to?(:expires_at) + save! + rescue StandardError => err + Gitlab::AppLogger.error("Secure File Parser Failure (#{id}): #{err.message} - #{parser.error}.") + nil + end + end + private def assign_checksum diff --git a/app/models/group_label.rb b/app/models/group_label.rb index ff14529c6e6..0d2eb524929 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -2,6 +2,7 @@ class GroupLabel < Label belongs_to :group + belongs_to :parent_container, foreign_key: :group_id, class_name: 'Group' validates :group, presence: true diff --git a/app/models/label.rb b/app/models/label.rb index 6608a0573cb..35daca92089 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -42,6 +42,7 @@ class Label < ApplicationRecord scope :order_name_asc, -> { reorder(title: :asc) } scope :order_name_desc, -> { reorder(title: :desc) } scope :subscribed_by, ->(user_id) { joins(:subscriptions).where(subscriptions: { user_id: user_id, subscribed: true }) } + scope :with_preloaded_container, -> { preload(parent_container: :route) } scope :top_labels_by_target, -> (target_relation) { label_id_column = arel_table[:id] @@ -59,6 +60,14 @@ class Label < ApplicationRecord .distinct } + scope :for_targets, ->(target_ids, targets_type) do + joins(:label_links) + .where(label_links: { target_id: target_ids }) + .where(label_links: { target_type: targets_type }) + .select("labels.*, target_id") + .with_preloaded_container + end + def self.prioritized(project) joins(:priorities) .where(label_priorities: { project_id: project }) diff --git a/app/models/preloaders/labels_preloader.rb b/app/models/preloaders/labels_preloader.rb index 722d588d8bc..b6e73c1cd02 100644 --- a/app/models/preloaders/labels_preloader.rb +++ b/app/models/preloaders/labels_preloader.rb @@ -21,8 +21,10 @@ module Preloaders def preload_all preloader = ActiveRecord::Associations::Preloader.new + preloader.preload(labels, parent_container: :route) preloader.preload(labels.select { |l| l.is_a? ProjectLabel }, { project: [:project_feature, namespace: :route] }) preloader.preload(labels.select { |l| l.is_a? GroupLabel }, { group: :route }) + labels.each do |label| label.lazy_subscription(user) label.lazy_subscription(user, project) if project.present? diff --git a/app/models/project_label.rb b/app/models/project_label.rb index d0b16cc98b4..dc647901b46 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -4,6 +4,7 @@ class ProjectLabel < Label MAX_NUMBER_OF_PRIORITIES = 1 belongs_to :project + belongs_to :parent_container, foreign_key: :project_id, class_name: 'Project' validates :project, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index d64a52ff7b9..be3bec0d142 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -935,6 +935,7 @@ class User < ApplicationRecord # that the password is the user's password def valid_password?(password) return false unless password_allowed?(password) + return false if password_automatically_set? return super if Feature.enabled?(:pbkdf2_password_encryption) Devise::Encryptor.compare(self.class, encrypted_password, password) diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb index 9f3acd44b23..4a848e44fec 100644 --- a/app/policies/group_label_policy.rb +++ b/app/policies/group_label_policy.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class GroupLabelPolicy < BasePolicy - delegate { @subject.group } + delegate { @subject.parent_container } end diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb index 5ce896ecaf2..6656d5990a5 100644 --- a/app/policies/project_label_policy.rb +++ b/app/policies/project_label_policy.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ProjectLabelPolicy < BasePolicy - delegate { @subject.project } + delegate { @subject.parent_container } end diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb index e3b110f8f26..2786a2e357e 100644 --- a/app/services/labels/promote_service.rb +++ b/app/services/labels/promote_service.rb @@ -9,7 +9,7 @@ module Labels return unless project.group && label.is_a?(ProjectLabel) - Label.transaction do + ProjectLabel.transaction do # use the existing group label if it exists group_label = find_or_create_group_label(label) @@ -50,7 +50,7 @@ module Labels .new(current_user, title: group_label.title, group_id: project.group.id) .execute(skip_authorization: true) .where.not(id: group_label) - .select(:id) # Can't use pluck() to avoid object-creation because of the batching + .select(:id, :project_id, :group_id, :type) # Can't use pluck() to avoid object-creation because of the batching end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 6d31a29f5a7..6b4f9dbe509 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -26,6 +26,7 @@ module MergeRequests @merge_request = merge_request @options = options + jid = merge_jid validate! @@ -37,7 +38,7 @@ module MergeRequests end end - log_info("Merge process finished on JID #{merge_jid} with state #{state}") + log_info("Merge process finished on JID #{jid} with state #{state}") rescue MergeError => e handle_merge_error(log_message: e.message, save_message_on_model: true) ensure @@ -159,17 +160,32 @@ module MergeRequests end def handle_merge_error(log_message:, save_message_on_model: false) - Gitlab::AppLogger.error("MergeService ERROR: #{merge_request_info} - #{log_message}") + log_error("MergeService ERROR: #{merge_request_info} - #{log_message}") @merge_request.update(merge_error: log_message) if save_message_on_model end def log_info(message) + payload = log_payload("#{merge_request_info} - #{message}") + logger.info(**payload) + end + + def log_error(message) + payload = log_payload(message) + logger.error(**payload) + end + + def logger @logger ||= Gitlab::AppLogger - @logger.info("#{merge_request_info} - #{message}") + end + + def log_payload(message) + Gitlab::ApplicationContext.current + .merge(merge_request_info: merge_request_info, + message: message) end def merge_request_info - merge_request.to_reference(full: true) + @merge_request_info ||= merge_request.to_reference(full: true) end def source_matches? diff --git a/app/services/pages_domains/create_acme_order_service.rb b/app/services/pages_domains/create_acme_order_service.rb index e289a78091b..c600f497fa5 100644 --- a/app/services/pages_domains/create_acme_order_service.rb +++ b/app/services/pages_domains/create_acme_order_service.rb @@ -2,9 +2,6 @@ module PagesDomains class CreateAcmeOrderService - # elliptic curve algorithm to generate the private key - ECDSA_CURVE = "prime256v1" - attr_reader :pages_domain def initialize(pages_domain) @@ -17,12 +14,7 @@ module PagesDomains challenge = order.new_challenge - private_key = if Feature.enabled?(:pages_lets_encrypt_ecdsa, pages_domain.project) - OpenSSL::PKey::EC.generate(ECDSA_CURVE) - else - OpenSSL::PKey::RSA.new(4096) - end - + private_key = OpenSSL::PKey::RSA.new(4096) saved_order = pages_domain.acme_orders.create!( url: order.url, expires_at: order.expires, diff --git a/app/views/projects/releases/index.html.haml b/app/views/projects/releases/index.html.haml index 9ddf2201fad..975abaefc6c 100644 --- a/app/views/projects/releases/index.html.haml +++ b/app/views/projects/releases/index.html.haml @@ -1,4 +1,5 @@ - page_title _('Releases') +- add_page_specific_style 'page_bundles/releases' - if use_startup_query_for_index_page? - add_page_startup_graphql_call('releases/all_releases', index_page_startup_query_variables) diff --git a/app/views/projects/releases/show.html.haml b/app/views/projects/releases/show.html.haml index 91ee9ad70a3..66b187c8c72 100644 --- a/app/views/projects/releases/show.html.haml +++ b/app/views/projects/releases/show.html.haml @@ -1,5 +1,6 @@ - add_to_breadcrumbs _("Releases"), project_releases_path(@project) - page_title @release.name - page_description @release.description_html +- add_page_specific_style 'page_bundles/releases' #js-show-release-page{ data: data_for_show_page } diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 26f9f45e4bd..e02285e7f34 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -2181,6 +2181,15 @@ :weight: 1 :idempotent: true :tags: [] +- :name: ci_parse_secure_file_metadata + :worker_name: Ci::ParseSecureFileMetadataWorker + :feature_category: :mobile_signing_deployment + :has_external_dependencies: false + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: ci_runners_process_runner_version_update :worker_name: Ci::Runners::ProcessRunnerVersionUpdateWorker :feature_category: :runner_fleet diff --git a/app/workers/ci/parse_secure_file_metadata_worker.rb b/app/workers/ci/parse_secure_file_metadata_worker.rb new file mode 100644 index 00000000000..0d2495d3155 --- /dev/null +++ b/app/workers/ci/parse_secure_file_metadata_worker.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Ci + class ParseSecureFileMetadataWorker + include ::ApplicationWorker + + feature_category :mobile_signing_deployment + urgency :low + idempotent! + + def perform(secure_file_id) + ::Ci::SecureFile.find_by_id(secure_file_id).try(&:update_metadata!) + end + end +end diff --git a/config/application.rb b/config/application.rb index 0ed3e6fbd2d..d45e0bab54d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -294,6 +294,7 @@ module Gitlab config.assets.precompile << "page_bundles/milestone.css" config.assets.precompile << "page_bundles/new_namespace.css" config.assets.precompile << "page_bundles/oncall_schedules.css" + config.assets.precompile << "page_bundles/operations.css" config.assets.precompile << "page_bundles/escalation_policies.css" config.assets.precompile << "page_bundles/pipeline.css" config.assets.precompile << "page_bundles/pipeline_schedules.css" @@ -306,6 +307,7 @@ module Gitlab config.assets.precompile << "page_bundles/project.css" config.assets.precompile << "page_bundles/projects_edit.css" config.assets.precompile << "page_bundles/prometheus.css" + config.assets.precompile << "page_bundles/releases.css" config.assets.precompile << "page_bundles/reports.css" config.assets.precompile << "page_bundles/roadmap.css" config.assets.precompile << "page_bundles/requirements.css" diff --git a/config/feature_flags/development/pages_lets_encrypt_ecdsa.yml b/config/feature_flags/development/approval_rules_eligible_filter.yml index 866c2438e9f..e8d925d08a7 100644 --- a/config/feature_flags/development/pages_lets_encrypt_ecdsa.yml +++ b/config/feature_flags/development/approval_rules_eligible_filter.yml @@ -1,8 +1,8 @@ --- -name: pages_lets_encrypt_ecdsa -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88125 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363026 -milestone: '15.1' +name: approval_rules_eligible_filter +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100192 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/376331 +milestone: '15.5' type: development -group: group::editor +group: group::source code default_enabled: false diff --git a/config/feature_flags/development/secure_files_metadata_parsers.yml b/config/feature_flags/development/secure_files_metadata_parsers.yml new file mode 100644 index 00000000000..2d6eed27f4b --- /dev/null +++ b/config/feature_flags/development/secure_files_metadata_parsers.yml @@ -0,0 +1,8 @@ +--- +name: secure_files_metadata_parsers +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/99046 +rollout_issue_url: +milestone: '15.5' +type: development +group: group::incubation +default_enabled: false diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 0e8f5d4a7d6..aff05cb62b9 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -93,6 +93,8 @@ - 1 - - ci_job_artifacts_expire_project_build_artifacts - 1 +- - ci_parse_secure_file_metadata + - 1 - - ci_runners_process_runner_version_update - 1 - - ci_upstream_projects_subscriptions_cleanup diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md index 1a13343d3e1..0b1d355aba2 100644 --- a/doc/development/documentation/styleguide/word_list.md +++ b/doc/development/documentation/styleguide/word_list.md @@ -218,7 +218,7 @@ Instead of: ## cannot, can not -Use **cannot** instead of **can not**. You can also use **can't**. +Use **cannot** instead of **can not**. See also [contractions](index.md#contractions). diff --git a/doc/development/gitlab_flavored_markdown/specification_guide/index.md b/doc/development/gitlab_flavored_markdown/specification_guide/index.md index 4a6a1d8f64f..b1ab39b0321 100644 --- a/doc/development/gitlab_flavored_markdown/specification_guide/index.md +++ b/doc/development/gitlab_flavored_markdown/specification_guide/index.md @@ -86,7 +86,7 @@ Some places in the code refer to both the GitLab and GitHub specifications simultaneous in the same areas of logic. In these situations, _GitHub_ Flavored Markdown may be referred to with variable or constant names like `ghfm_` to avoid confusion. For example, we use the `ghfm` acronym for the -[`ghfm_spec_v_0.29.txt` GitHub Flavored Markdown specification file](#github-flavored-markdown-specification) +[`ghfm_spec_v_0.29.md` GitHub Flavored Markdown specification file](#github-flavored-markdown-specification), which is committed to the `gitlab` repository and used as input to the [`update_specification.rb` script](#update-specificationrb-script). @@ -618,8 +618,8 @@ subgraph script: A --> B{Backend Markdown API} end subgraph input:<br/>input specification files - C[ghfm_spec_v_0.29.txt] --> A - D[glfm_intro.txt] --> A + C[ghfm_spec_v_0.29.md] --> A + D[glfm_intro.md] --> A E[glfm_official_specification_examples.md] --> A F[glfm_internal_extension_examples.md] --> A end @@ -749,7 +749,7 @@ subcategories based on their usage and purpose: These are the original input to drive all other automated GLFM specification scripts, processes, or tests. - `github_flavored_markdown`: Contains only the downloaded and committed - [`ghfm_spec_v_0.29.txt`](#github-flavored-markdown-specification) specification. + [`ghfm_spec_v_0.29.md`](#github-flavored-markdown-specification) specification. - `gitlab_flavored_markdown`: Contains all `glfm_*` files. - `*.md` [input specification files](#input-specification-files), which represent the GLFM specification itself. @@ -769,19 +769,20 @@ See the main [specification files](#specification-files) section for more contex ##### GitHub Flavored Markdown specification -[`glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.txt`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.txt) -is the official latest [GFM `spec.txt`](https://github.com/github/cmark-gfm/blob/master/test/spec.txt). +[`glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.md`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.md) +is a copy of the official latest [GFM `spec.txt`](https://github.com/github/cmark-gfm/blob/master/test/spec.txt). -- It is automatically downloaded and updated by `update-specification.rb` script. +- It is automatically downloaded and updated by the `update-specification.rb` script. - When it is downloaded, the version number is added to the filename. +- The extension is changed from `*.txt` to `*.md` so that it can be handled better by Markdown editors. NOTE: For extra clarity, this file uses the `ghfm` acronym in its name instead of `gfm`, as explained in the [Acronyms section](#acronyms-glfm-ghfm-gfm-commonmark). -##### `glfm_intro.txt` +##### `glfm_intro.md` -[`glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt) +[`glfm_specification/input/gitlab_flavored_markdown/glfm_intro.md`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.md) is the GitLab-specific version of the prose in the introduction section of the GLFM specification. - It is manually updated. diff --git a/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.txt b/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.md index 582131d700a..1702761563e 100644 --- a/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.txt +++ b/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.md @@ -2077,7 +2077,7 @@ followed by one of the strings (case-insensitive) `address`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`, `html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`, `nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, -`section`, `source`, `summary`, `table`, `tbody`, `td`, +`section`, `summary`, `table`, `tbody`, `td`, `tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed by [whitespace], the end of the line, the string `>`, or the string `/>`.\ @@ -10224,4 +10224,3 @@ closers: After we're done, we remove all delimiters above `stack_bottom` from the delimiter stack. - diff --git a/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt b/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.md index b5351bf37de..b5351bf37de 100644 --- a/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt +++ b/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.md diff --git a/glfm_specification/output/spec.txt b/glfm_specification/output/spec.txt index af4eba06758..9fa6c4c291e 100644 --- a/glfm_specification/output/spec.txt +++ b/glfm_specification/output/spec.txt @@ -1791,7 +1791,7 @@ followed by one of the strings (case-insensitive) `address`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`, `html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`, `nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, -`section`, `source`, `summary`, `table`, `tbody`, `td`, +`section`, `summary`, `table`, `tbody`, `td`, `tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed by [whitespace], the end of the line, the string `>`, or the string `/>`.\ @@ -10328,4 +10328,3 @@ closers: After we're done, we remove all delimiters above `stack_bottom` from the delimiter stack. - diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb index c1f47dd67ce..68431df203b 100644 --- a/lib/api/ci/secure_files.rb +++ b/lib/api/ci/secure_files.rb @@ -74,6 +74,10 @@ module API file_too_large! unless secure_file.file.size < ::Ci::SecureFile::FILE_SIZE_LIMIT.to_i if secure_file.save + if Feature.enabled?(:secure_files_metadata_parsers, user_project) + ::Ci::ParseSecureFileMetadataWorker.perform_async(secure_file.id) # rubocop:disable CodeReuse/Worker + end + present secure_file, with: Entities::Ci::SecureFile else render_validation_error!(secure_file) diff --git a/lib/gitlab/ci/secure_files/cer.rb b/lib/gitlab/ci/secure_files/cer.rb new file mode 100644 index 00000000000..7a55d9ca095 --- /dev/null +++ b/lib/gitlab/ci/secure_files/cer.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module SecureFiles + class Cer + attr_reader :error + + def initialize(filedata) + @filedata = filedata + @error = nil + end + + def certificate_data + @certificate_data ||= begin + OpenSSL::X509::Certificate.new(@filedata) + rescue StandardError => err + @error = err.to_s + nil + end + end + + def metadata + return {} unless certificate_data + + { + issuer: issuer, + subject: subject, + id: id, + expires_at: expires_at + } + end + + def expires_at + return unless certificate_data + + certificate_data.not_before + end + + private + + def id + certificate_data.serial.to_s + end + + def issuer + X509Name.parse(certificate_data.issuer) + end + + def subject + X509Name.parse(certificate_data.subject) + end + end + end + end +end diff --git a/lib/gitlab/ci/secure_files/x509_name.rb b/lib/gitlab/ci/secure_files/x509_name.rb new file mode 100644 index 00000000000..659959b8ae5 --- /dev/null +++ b/lib/gitlab/ci/secure_files/x509_name.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module SecureFiles + class X509Name + def self.parse(x509_name) + x509_name.to_utf8.split(',').to_h { |a| a.split('=') } + rescue StandardError + {} + end + end + end + end +end diff --git a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml index 12640d28d29..0604438e0aa 100644 --- a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml @@ -42,3 +42,8 @@ variables: test: script: - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never + +deploy: + stage: deploy + script: echo "Define your deployment script!" + environment: production diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml index 191d5b6b11c..febbb36d834 100644 --- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml @@ -55,3 +55,8 @@ pages: - public rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + +deploy: + stage: deploy + script: echo "Define your deployment script!" + environment: production diff --git a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml index a83f84da818..b9823444db2 100644 --- a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml @@ -40,3 +40,8 @@ test:cargo: # when: always # reports: # junit: $CI_PROJECT_DIR/tests/*.xml + +deploy: + stage: deploy + script: echo "Define your deployment script!" + environment: production diff --git a/lib/gitlab/ci/templates/Scala.gitlab-ci.yml b/lib/gitlab/ci/templates/Scala.gitlab-ci.yml index 26efe7a8908..ce5d5937896 100644 --- a/lib/gitlab/ci/templates/Scala.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Scala.gitlab-ci.yml @@ -30,3 +30,8 @@ test: script: # Execute your project's tests - sbt clean test + +deploy: + stage: deploy + script: echo "Define your deployment script!" + environment: production diff --git a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml index 3c4533d603e..2a5ac539a42 100644 --- a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml @@ -38,3 +38,8 @@ archive_project: - ios_11-3 - xcode_9-3 - macos_10-13 + +deploy: + stage: deploy + script: echo "Define your deployment script!" + environment: production diff --git a/lib/gitlab/jira_import/handle_labels_service.rb b/lib/gitlab/jira_import/handle_labels_service.rb index 1b00515cced..60d7f9e93d9 100644 --- a/lib/gitlab/jira_import/handle_labels_service.rb +++ b/lib/gitlab/jira_import/handle_labels_service.rb @@ -12,7 +12,7 @@ module Gitlab return if jira_labels.blank? existing_labels = LabelsFinder.new(nil, project: project, title: jira_labels) - .execute(skip_authorization: true).select(:id, :name) + .execute(skip_authorization: true).select(:id, :project_id, :group_id, :type, :name) new_labels = create_missing_labels(existing_labels) label_ids = existing_labels.map(&:id) diff --git a/scripts/lib/glfm/constants.rb b/scripts/lib/glfm/constants.rb index e5790bbdd88..27b959cd135 100644 --- a/scripts/lib/glfm/constants.rb +++ b/scripts/lib/glfm/constants.rb @@ -10,12 +10,12 @@ module Glfm # GitHub Flavored Markdown specification file GHFM_SPEC_TXT_URI = 'https://raw.githubusercontent.com/github/cmark-gfm/master/test/spec.txt' GHFM_SPEC_VERSION = '0.29' - GHFM_SPEC_TXT_FILENAME = "ghfm_spec_v_#{GHFM_SPEC_VERSION}.txt" - GHFM_SPEC_TXT_PATH = specification_path.join('input/github_flavored_markdown', GHFM_SPEC_TXT_FILENAME) + GHFM_SPEC_MD_FILENAME = "ghfm_spec_v_#{GHFM_SPEC_VERSION}.md" + GHFM_SPEC_MD_PATH = specification_path.join('input/github_flavored_markdown', GHFM_SPEC_MD_FILENAME) # GitLab Flavored Markdown specification files specification_input_glfm_path = specification_path.join('input/gitlab_flavored_markdown') - GLFM_INTRO_TXT_PATH = specification_input_glfm_path.join('glfm_intro.txt') + GLFM_INTRO_MD_PATH = specification_input_glfm_path.join('glfm_intro.md') GLFM_EXAMPLES_TXT_PATH = specification_input_glfm_path.join('glfm_canonical_examples.txt') GLFM_EXAMPLE_STATUS_YML_PATH = specification_input_glfm_path.join('glfm_example_status.yml') GLFM_EXAMPLE_METADATA_YML_PATH = diff --git a/scripts/lib/glfm/update_specification.rb b/scripts/lib/glfm/update_specification.rb index 73c23d40de5..7a89797123b 100644 --- a/scripts/lib/glfm/update_specification.rb +++ b/scripts/lib/glfm/update_specification.rb @@ -12,16 +12,16 @@ module Glfm def process output('Updating specification...') - ghfm_spec_txt_lines = load_ghfm_spec_txt - glfm_spec_txt_string = build_glfm_spec_txt(ghfm_spec_txt_lines) + ghfm_spec_lines = load_ghfm_spec + glfm_spec_txt_string = build_glfm_spec_txt(ghfm_spec_lines) write_glfm_spec_txt(glfm_spec_txt_string) end private - def load_ghfm_spec_txt + def load_ghfm_spec # We only re-download the GitHub Flavored Markdown specification if the - # UPDATE_GHFM_SPEC_TXT environment variable is set to true, which should only + # UPDATE_GHFM_SPEC_MD environment variable is set to true, which should only # ever be done manually and locally, never in CI. This provides some security # protection against a possible injection attack vector, if the GitHub-hosted # version of the spec is ever temporarily compromised with an injection attack. @@ -29,40 +29,40 @@ module Glfm # This also avoids doing external network access to download the file # in CI jobs, which can avoid potentially flaky builds if the GitHub-hosted # version of the file is temporarily unavailable. - if ENV['UPDATE_GHFM_SPEC_TXT'] == 'true' - download_and_write_ghfm_spec_txt + if ENV['UPDATE_GHFM_SPEC_MD'] == 'true' + update_ghfm_spec_md else - read_existing_ghfm_spec_txt + read_existing_ghfm_spec_md end end - def read_existing_ghfm_spec_txt - output("Reading existing #{GHFM_SPEC_TXT_PATH}...") - File.open(GHFM_SPEC_TXT_PATH).readlines + def read_existing_ghfm_spec_md + output("Reading existing #{GHFM_SPEC_MD_PATH}...") + File.open(GHFM_SPEC_MD_PATH).readlines end - def download_and_write_ghfm_spec_txt + def update_ghfm_spec_md output("Downloading #{GHFM_SPEC_TXT_URI}...") ghfm_spec_txt_uri_io = URI.open(GHFM_SPEC_TXT_URI) # Read IO stream into an array of lines for easy processing later - ghfm_spec_txt_lines = ghfm_spec_txt_uri_io.readlines - raise "Unable to read lines from #{GHFM_SPEC_TXT_URI}" if ghfm_spec_txt_lines.empty? + ghfm_spec_lines = ghfm_spec_txt_uri_io.readlines + raise "Unable to read lines from #{GHFM_SPEC_TXT_URI}" if ghfm_spec_lines.empty? # Make sure the GHFM spec version has not changed - validate_expected_spec_version!(ghfm_spec_txt_lines[2]) + validate_expected_spec_version!(ghfm_spec_lines[2]) # Reset IO stream and re-read into a single string for easy writing # noinspection RubyNilAnalysis ghfm_spec_txt_uri_io.seek(0) - ghfm_spec_txt_string = ghfm_spec_txt_uri_io.read - raise "Unable to read string from #{GHFM_SPEC_TXT_URI}" unless ghfm_spec_txt_string + ghfm_spec_string = ghfm_spec_txt_uri_io.read + raise "Unable to read string from #{GHFM_SPEC_TXT_URI}" unless ghfm_spec_string - output("Writing #{GHFM_SPEC_TXT_PATH}...") - GHFM_SPEC_TXT_PATH.dirname.mkpath - write_file(GHFM_SPEC_TXT_PATH, ghfm_spec_txt_string) + output("Writing #{GHFM_SPEC_MD_PATH}...") + GHFM_SPEC_MD_PATH.dirname.mkpath + write_file(GHFM_SPEC_MD_PATH, ghfm_spec_string) - ghfm_spec_txt_lines + ghfm_spec_lines end def validate_expected_spec_version!(version_line) @@ -85,13 +85,13 @@ module Glfm end def replace_intro_section(spec_txt_lines) - glfm_intro_txt_lines = File.open(GLFM_INTRO_TXT_PATH).readlines - raise "Unable to read lines from #{GLFM_INTRO_TXT_PATH}" if glfm_intro_txt_lines.empty? + glfm_intro_md_lines = File.open(GLFM_INTRO_MD_PATH).readlines + raise "Unable to read lines from #{GLFM_INTRO_MD_PATH}" if glfm_intro_md_lines.empty? ghfm_intro_header_begin_index = spec_txt_lines.index do |line| line =~ INTRODUCTION_HEADER_LINE_TEXT end - raise "Unable to locate introduction header line in #{GHFM_SPEC_TXT_PATH}" if ghfm_intro_header_begin_index.nil? + raise "Unable to locate introduction header line in #{GHFM_SPEC_MD_PATH}" if ghfm_intro_header_begin_index.nil? # Find the index of the next header after the introduction header, starting from the index # of the introduction header this is the length of the intro section @@ -100,7 +100,7 @@ module Glfm end # Replace the intro section with the GitLab flavored Markdown intro section - spec_txt_lines[ghfm_intro_header_begin_index, ghfm_intro_section_length] = glfm_intro_txt_lines + spec_txt_lines[ghfm_intro_header_begin_index, ghfm_intro_section_length] = glfm_intro_md_lines end def insert_examples_txt(spec_txt_lines) @@ -110,7 +110,7 @@ module Glfm ghfm_end_tests_comment_index = spec_txt_lines.index do |line| line =~ END_TESTS_COMMENT_LINE_TEXT end - raise "Unable to locate 'END TESTS' comment line in #{GHFM_SPEC_TXT_PATH}" if ghfm_end_tests_comment_index.nil? + raise "Unable to locate 'END TESTS' comment line in #{GHFM_SPEC_MD_PATH}" if ghfm_end_tests_comment_index.nil? # Insert the GLFM examples before the 'END TESTS' comment line spec_txt_lines[ghfm_end_tests_comment_index - 1] = ["\n", glfm_examples_txt_lines, "\n"].flatten diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb index 1d0db488751..6c860740354 100644 --- a/spec/features/profiles/password_spec.rb +++ b/spec/features/profiles/password_spec.rb @@ -51,11 +51,11 @@ RSpec.describe 'Profile > Password' do end context 'Password authentication unavailable' do - before do - gitlab_sign_in(user) - end - context 'Regular user' do + before do + gitlab_sign_in(user) + end + let(:user) { create(:user) } it 'renders 404 when password authentication is disabled for the web interface and Git' do @@ -69,7 +69,22 @@ RSpec.describe 'Profile > Password' do end context 'LDAP user' do + include LdapHelpers + + let(:ldap_settings) { { enabled: true } } let(:user) { create(:omniauth_user, provider: 'ldapmain') } + let(:provider) { 'ldapmain' } + let(:provider_label) { 'Main LDAP' } + + before do + stub_ldap_setting(ldap_settings) + stub_ldap_access(user, provider, provider_label) + sign_in_using_ldap!(user, provider_label, provider) + end + + after(:all) do + Rails.application.reload_routes! + end it 'renders 404' do visit edit_profile_password_path diff --git a/spec/fixtures/ci_secure_files/sample.cer b/spec/fixtures/ci_secure_files/sample.cer Binary files differnew file mode 100644 index 00000000000..9cca06d53c9 --- /dev/null +++ b/spec/fixtures/ci_secure_files/sample.cer diff --git a/spec/frontend/alert_management/components/alert_management_table_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js index 3e1438c37d6..7fb4f2d2463 100644 --- a/spec/frontend/alert_management/components/alert_management_table_spec.js +++ b/spec/frontend/alert_management/components/alert_management_table_spec.js @@ -1,4 +1,4 @@ -import { GlTable, GlAlert, GlLoadingIcon, GlDropdown, GlIcon, GlAvatar } from '@gitlab/ui'; +import { GlTable, GlAlert, GlLoadingIcon, GlDropdown, GlIcon, GlAvatar, GlLink } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; @@ -31,6 +31,7 @@ describe('AlertManagementTable', () => { const findSearch = () => wrapper.findComponent(FilteredSearchBar); const findSeverityColumnHeader = () => wrapper.findByTestId('alert-management-severity-sort'); const findFirstIDField = () => wrapper.findAllByTestId('idField').at(0); + const findFirstIDLink = () => wrapper.findAllByTestId('idField').at(0).findComponent(GlLink); const findAssignees = () => wrapper.findAllByTestId('assigneesField'); const findSeverityFields = () => wrapper.findAllByTestId('severityField'); const findIssueFields = () => wrapper.findAllByTestId('issueField'); @@ -135,10 +136,11 @@ describe('AlertManagementTable', () => { expect(findLoader().exists()).toBe(false); expect(findAlertsTable().exists()).toBe(true); expect(findAlerts()).toHaveLength(mockAlerts.length); - expect(findAlerts().at(0).classes()).toContain('gl-hover-bg-blue-50'); + expect(findAlerts().at(0).classes()).toContain('gl-hover-bg-gray-50'); + expect(findAlerts().at(0).classes()).not.toContain('gl-hover-border-blue-200'); }); - it('displays the alert ID and title formatted correctly', () => { + it('displays the alert ID and title as a link', () => { mountComponent({ data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, loading: false, @@ -146,6 +148,8 @@ describe('AlertManagementTable', () => { expect(findFirstIDField().exists()).toBe(true); expect(findFirstIDField().text()).toBe(`#${mockAlerts[0].iid} ${mockAlerts[0].title}`); + expect(findFirstIDLink().text()).toBe(`#${mockAlerts[0].iid} ${mockAlerts[0].title}`); + expect(findFirstIDLink().attributes('href')).toBe('/1527542/details'); }); it('displays status dropdown', () => { @@ -266,7 +270,8 @@ describe('AlertManagementTable', () => { alerts: { list: [ { - iid: 1, + iid: '1', + title: 'SyntaxError: Invalid or unexpected token', status: 'acknowledged', startedAt: '2020-03-17T23:18:14.996Z', severity: 'high', diff --git a/spec/lib/gitlab/ci/secure_files/cer_spec.rb b/spec/lib/gitlab/ci/secure_files/cer_spec.rb new file mode 100644 index 00000000000..50c8494524a --- /dev/null +++ b/spec/lib/gitlab/ci/secure_files/cer_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::SecureFiles::Cer do + context 'when the supplied certificate cannot be parsed' do + let(:invalid_certificate) { described_class.new('xyzabc') } + let(:subject) { described_class.new('xyzabc') } + + describe '#certificate_data' do + it 'assigns the error message and returns nil' do + expect(invalid_certificate.certificate_data).to be nil + expect(invalid_certificate.error).to eq('not enough data') + end + end + + describe '#metadata' do + it 'returns an empty hash' do + expect(invalid_certificate.metadata).to eq({}) + end + end + + describe '#expires_at' do + it 'returns nil' do + expect(invalid_certificate.expires_at).to be_nil + end + end + end + + context 'when the supplied certificate can be parsed' do + let(:sample_file) { fixture_file('ci_secure_files/sample.cer') } + let(:subject) { described_class.new(sample_file) } + + describe '#certificate_data' do + it 'returns an OpenSSL::X509::Certificate object' do + expect(subject.certificate_data.class).to be(OpenSSL::X509::Certificate) + end + end + + describe '#metadata' do + it 'returns a hash with the expected keys' do + expect(subject.metadata.keys).to match_array([:issuer, :subject, :id, :expires_at]) + end + end + + describe '#id' do + it 'returns the certificate serial number' do + expect(subject.metadata[:id]).to eq('33669367788748363528491290218354043267') + end + end + + describe '#expires_at' do + it 'returns the certificate expiration timestamp' do + expect(subject.expires_at).to eq('2022-04-26 19:20:40 UTC') + end + end + + describe '#issuer' do + it 'calls parse on X509Name' do + expect(subject.metadata[:issuer]["O"]).to eq('Apple Inc.') + end + end + + describe '#subject' do + it 'calls parse on X509Name' do + expect(subject.metadata[:subject]["OU"]).to eq('N7SYAN8PX8') + end + end + end +end diff --git a/spec/lib/gitlab/ci/secure_files/x509_name_spec.rb b/spec/lib/gitlab/ci/secure_files/x509_name_spec.rb new file mode 100644 index 00000000000..3a523924c5b --- /dev/null +++ b/spec/lib/gitlab/ci/secure_files/x509_name_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::SecureFiles::X509Name do + describe '.parse' do + it 'parses an X509Name object into a hash format' do + sample = OpenSSL::X509::Name.new([ + ['C', 'Test Country'], + ['O', 'Test Org Name'], + ['OU', 'Test Org Unit'], + ['CN', 'Test Common Name'], + ['UID', 'Test UID'] + ]) + + parsed_sample = described_class.parse(sample) + + expect(parsed_sample["C"]).to eq('Test Country') + expect(parsed_sample["O"]).to eq('Test Org Name') + expect(parsed_sample["OU"]).to eq('Test Org Unit') + expect(parsed_sample["CN"]).to eq('Test Common Name') + expect(parsed_sample["UID"]).to eq('Test UID') + end + + it 'returns an empty hash when an error occurs' do + parsed_sample = described_class.parse('unexpectedinput') + expect(parsed_sample).to eq({}) + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 34c062d3096..b3d5a0d57c1 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -95,6 +95,7 @@ label_links: label: - subscriptions - project +- parent_container - lists - label_links - issues diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb index e47efff5dfd..153912da4e8 100644 --- a/spec/models/ci/secure_file_spec.rb +++ b/spec/models/ci/secure_file_spec.rb @@ -81,4 +81,60 @@ RSpec.describe Ci::SecureFile do expect(Base64.encode64(subject.file.read)).to eq(Base64.encode64(sample_file)) end end + + describe '#file_extension' do + it 'returns the extension for the file name' do + file = build(:ci_secure_file, name: 'file1.cer') + expect(file.file_extension).to eq('cer') + end + + it 'returns only the last part of the extension for the file name' do + file = build(:ci_secure_file, name: 'file1.tar.gz') + expect(file.file_extension).to eq('gz') + end + end + + describe '#metadata_parsable?' do + it 'returns true when the file extension has a supported parser' do + file = build(:ci_secure_file, name: 'file1.cer') + expect(file.metadata_parsable?).to be true + end + + it 'returns false when the file extension does not have a supported parser' do + file = build(:ci_secure_file, name: 'file1.foo') + expect(file.metadata_parsable?).to be false + end + end + + describe '#metadata_parser' do + it 'returns an instance of Gitlab::Ci::SecureFiles::Cer when a .cer file is supplied' do + file = build(:ci_secure_file, name: 'file1.cer') + expect(file.metadata_parser).to be_an_instance_of(Gitlab::Ci::SecureFiles::Cer) + end + + it 'returns nil when the file type is not supported by any parsers' do + file = build(:ci_secure_file, name: 'file1.foo') + expect(file.metadata_parser).to be nil + end + end + + describe '#update_metadata!' do + it 'assigns the expected metadata when a parsable file is supplied' do + file = create(:ci_secure_file, name: 'file1.cer', + file: CarrierWaveStringFile.new(fixture_file('ci_secure_files/sample.cer') )) + file.update_metadata! + + expect(file.expires_at).to eq(DateTime.parse('2022-04-26 19:20:40')) + expect(file.metadata['id']).to eq('33669367788748363528491290218354043267') + expect(file.metadata['issuer']['CN']).to eq('Apple Worldwide Developer Relations Certification Authority') + expect(file.metadata['subject']['OU']).to eq('N7SYAN8PX8') + end + + it 'logs an error when something goes wrong with the file parsing' do + corrupt_file = create(:ci_secure_file, name: 'file1.cer', file: CarrierWaveStringFile.new('11111111')) + message = 'Validation failed: Metadata must be a valid json schema - not enough data.' + expect(Gitlab::AppLogger).to receive(:error).with("Secure File Parser Failure (#{corrupt_file.id}): #{message}") + corrupt_file.update_metadata! + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3b949bcfe65..d789533f76b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -6235,6 +6235,13 @@ RSpec.describe User do it { is_expected.to be_falsey } end end + + context 'user with autogenerated_password' do + let(:user) { build_stubbed(:user, password_automatically_set: true) } + let(:password) { user.password } + + it { is_expected.to be_falsey } + end end # These entire test section can be removed once the :pbkdf2_password_encryption feature flag is removed. diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index 163f9d02a3b..3b8beb4f798 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -708,7 +708,21 @@ RSpec.describe 'getting an issue list for a project' do end # Executes 3 extra queries to fetch participant_attrs - include_examples 'N+1 query check', 3 + include_examples 'N+1 query check', threshold: 3 + end + + context 'when requesting labels' do + let(:requested_fields) { ['labels { nodes { id } }'] } + + before do + project_labels = create_list(:label, 2, project: project) + group_labels = create_list(:group_label, 2, group: group) + + issue_a.update!(labels: [project_labels.first, group_labels.first].flatten) + issue_b.update!(labels: [project_labels, group_labels].flatten) + end + + include_examples 'N+1 query check', skip_cached: false end end diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index ef83f9be8ac..2895737ae6f 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -5,12 +5,14 @@ require 'spec_helper' RSpec.describe 'getting merge request listings nested in a project' do include GraphqlHelpers - let_it_be(:project) { create(:project, :repository, :public) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :repository, :public, group: group) } let_it_be(:current_user) { create(:user) } let_it_be(:label) { create(:label, project: project) } + let_it_be(:group_label) { create(:group_label, group: group) } let_it_be_with_reload(:merge_request_a) do - create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label]) + create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label, group_label]) end let_it_be(:merge_request_b) do @@ -18,7 +20,7 @@ RSpec.describe 'getting merge request listings nested in a project' do end let_it_be(:merge_request_c) do - create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label]) + create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label, group_label]) end let_it_be(:merge_request_d) do @@ -357,7 +359,20 @@ RSpec.describe 'getting merge request listings nested in a project' do end # Executes 3 extra queries to fetch participant_attrs - include_examples 'N+1 query check', 3 + include_examples 'N+1 query check', threshold: 3 + end + + context 'when requesting labels' do + let(:requested_fields) { ['labels { nodes { id } }'] } + + before do + project_labels = create_list(:label, 2, project: project) + group_labels = create_list(:group_label, 2, group: group) + + merge_request_c.update!(labels: [project_labels, group_labels].flatten) + end + + include_examples 'N+1 query check', skip_cached: false end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 5a774619e75..20d298edfe5 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -1759,8 +1759,7 @@ RSpec.describe 'Git HTTP requests' do end describe "User with LDAP identity" do - let(:user) { create(:omniauth_user, extern_uid: dn) } - let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } + let(:user) { create(:omniauth_user, :ldap) } let(:path) { 'doesnt/exist.git' } before do diff --git a/spec/scripts/lib/glfm/update_specification_spec.rb b/spec/scripts/lib/glfm/update_specification_spec.rb index 9fb671e0016..1cdc90876de 100644 --- a/spec/scripts/lib/glfm/update_specification_spec.rb +++ b/spec/scripts/lib/glfm/update_specification_spec.rb @@ -7,11 +7,11 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do let(:ghfm_spec_txt_uri) { described_class::GHFM_SPEC_TXT_URI } let(:ghfm_spec_txt_uri_io) { StringIO.new(ghfm_spec_txt_contents) } - let(:ghfm_spec_txt_path) { described_class::GHFM_SPEC_TXT_PATH } + let(:ghfm_spec_md_path) { described_class::GHFM_SPEC_MD_PATH } let(:ghfm_spec_txt_local_io) { StringIO.new(ghfm_spec_txt_contents) } - let(:glfm_intro_txt_path) { described_class::GLFM_INTRO_TXT_PATH } - let(:glfm_intro_txt_io) { StringIO.new(glfm_intro_txt_contents) } + let(:glfm_intro_md_path) { described_class::GLFM_INTRO_MD_PATH } + let(:glfm_intro_md_io) { StringIO.new(glfm_intro_md_contents) } let(:glfm_examples_txt_path) { described_class::GLFM_EXAMPLES_TXT_PATH } let(:glfm_examples_txt_io) { StringIO.new(glfm_examples_txt_contents) } let(:glfm_spec_txt_path) { described_class::GLFM_SPEC_TXT_PATH } @@ -52,7 +52,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do MARKDOWN end - let(:glfm_intro_txt_contents) do + let(:glfm_intro_md_contents) do # language=Markdown <<~MARKDOWN # Introduction @@ -73,15 +73,15 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do before do # Mock default ENV var values - allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return(nil) + allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_MD').and_return(nil) allow(ENV).to receive(:[]).and_call_original # We mock out the URI and local file IO objects with real StringIO, instead of just mock # objects. This gives better and more realistic coverage, while still avoiding # actual network and filesystem I/O during the spec run. allow(URI).to receive(:open).with(ghfm_spec_txt_uri) { ghfm_spec_txt_uri_io } - allow(File).to receive(:open).with(ghfm_spec_txt_path) { ghfm_spec_txt_local_io } - allow(File).to receive(:open).with(glfm_intro_txt_path) { glfm_intro_txt_io } + allow(File).to receive(:open).with(ghfm_spec_md_path) { ghfm_spec_txt_local_io } + allow(File).to receive(:open).with(glfm_intro_md_path) { glfm_intro_md_io } allow(File).to receive(:open).with(glfm_examples_txt_path) { glfm_examples_txt_io } allow(File).to receive(:open).with(glfm_spec_txt_path, 'w') { glfm_spec_txt_io } @@ -90,7 +90,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do end describe 'retrieving latest GHFM spec.txt' do - context 'when UPDATE_GHFM_SPEC_TXT is not true (default)' do + context 'when UPDATE_GHFM_SPEC_MD is not true (default)' do it 'does not download' do expect(URI).not_to receive(:open).with(ghfm_spec_txt_uri) @@ -100,12 +100,12 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do end end - context 'when UPDATE_GHFM_SPEC_TXT is true' do + context 'when UPDATE_GHFM_SPEC_MD is true' do let(:ghfm_spec_txt_local_io) { StringIO.new } before do - allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return('true') - allow(File).to receive(:open).with(ghfm_spec_txt_path, 'w') { ghfm_spec_txt_local_io } + allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_MD').and_return('true') + allow(File).to receive(:open).with(ghfm_spec_md_path, 'w') { ghfm_spec_txt_local_io } end context 'with success' do @@ -170,7 +170,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do it 'replaces the intro section with the GitLab version' do expect(glfm_contents).not_to match(/What is GitHub Flavored Markdown/m) - expect(glfm_contents).to match(/#{Regexp.escape(glfm_intro_txt_contents)}/m) + expect(glfm_contents).to match(/#{Regexp.escape(glfm_intro_md_contents)}/m) end it 'inserts the GitLab examples sections before the appendix section' do diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb index aa5d6dcd1fb..5027acbba0a 100644 --- a/spec/services/merge_requests/ff_merge_service_spec.rb +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -108,7 +108,8 @@ RSpec.describe MergeRequests::FfMergeService do service.execute(merge_request) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(message: a_string_matching(error_message))) end it 'logs and saves error if there is an PreReceiveError exception' do @@ -122,7 +123,8 @@ RSpec.describe MergeRequests::FfMergeService do service.execute(merge_request) expect(merge_request.merge_error).to include(error_message) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(message: a_string_matching(error_message))) end it 'does not update squash_commit_sha if squash merge is not successful' do diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index a2d73d8c9b1..d3bf203d6bb 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -95,6 +95,42 @@ RSpec.describe MergeRequests::MergeService do end end + context 'running the service once' do + let(:ref) { merge_request.to_reference(full: true) } + let(:jid) { SecureRandom.hex } + + let(:messages) do + [ + /#{ref} - Git merge started on JID #{jid}/, + /#{ref} - Git merge finished on JID #{jid}/, + /#{ref} - Post merge started on JID #{jid}/, + /#{ref} - Post merge finished on JID #{jid}/, + /#{ref} - Merge process finished on JID #{jid}/ + ] + end + + before do + merge_request.update!(merge_jid: jid) + ::Gitlab::ApplicationContext.push(caller_id: 'MergeWorker') + end + + it 'logs status messages' do + allow(Gitlab::AppLogger).to receive(:info).and_call_original + + messages.each do |message| + expect(Gitlab::AppLogger).to receive(:info).with( + hash_including( + 'meta.caller_id' => 'MergeWorker', + message: message, + merge_request_info: ref + ) + ).and_call_original + end + + service.execute(merge_request) + end + end + context 'running the service multiple time' do it 'is idempotent' do 2.times { service.execute(merge_request) } @@ -315,7 +351,9 @@ RSpec.describe MergeRequests::MergeService do service.execute(merge_request) expect(merge_request.merge_error).to eq(error_message) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message))) end end @@ -328,7 +366,9 @@ RSpec.describe MergeRequests::MergeService do service.execute(merge_request) expect(merge_request.merge_error).to eq(described_class::GENERIC_ERROR_MESSAGE) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message))) end it 'logs and saves error if user is not authorized' do @@ -354,7 +394,9 @@ RSpec.describe MergeRequests::MergeService do service.execute(merge_request) expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook') - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message))) end it 'logs and saves error if commit is not created' do @@ -366,7 +408,9 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request).to be_open expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.merge_error).to include(described_class::GENERIC_ERROR_MESSAGE) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(described_class::GENERIC_ERROR_MESSAGE)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(described_class::GENERIC_ERROR_MESSAGE))) end context 'when squashing is required' do @@ -385,7 +429,9 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message))) end end @@ -406,7 +452,9 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message))) end it 'logs and saves error if there is an PreReceiveError exception' do @@ -422,7 +470,9 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook') - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message))) end context 'when fast-forward merge is not allowed' do @@ -444,7 +494,9 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message))) end end end @@ -461,7 +513,9 @@ RSpec.describe MergeRequests::MergeService do it 'logs and saves error' do service.execute(merge_request) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message))) end end @@ -473,7 +527,9 @@ RSpec.describe MergeRequests::MergeService do it 'logs and saves error' do service.execute(merge_request) - expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + expect(Gitlab::AppLogger).to have_received(:error) + .with(hash_including(merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message))) end context 'when passing `skip_discussions_check: true` as `options` parameter' do diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb index b882c253613..35b2cc56973 100644 --- a/spec/services/pages_domains/create_acme_order_service_spec.rb +++ b/spec/services/pages_domains/create_acme_order_service_spec.rb @@ -38,21 +38,13 @@ RSpec.describe PagesDomains::CreateAcmeOrderService do expect(challenge).to have_received(:request_validation).ordered end - it 'generates and saves private key: rsa' do - stub_feature_flags(pages_lets_encrypt_ecdsa: false) + it 'generates and saves private key' do service.execute saved_order = PagesDomainAcmeOrder.last expect { OpenSSL::PKey::RSA.new(saved_order.private_key) }.not_to raise_error end - it 'generates and saves private key: ec' do - service.execute - - saved_order = PagesDomainAcmeOrder.last - expect { OpenSSL::PKey::EC.new(saved_order.private_key) }.not_to raise_error - end - it 'properly saves order attributes' do service.execute diff --git a/spec/support/helpers/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb index 2f5f8be518c..48b593fb3d1 100644 --- a/spec/support/helpers/ldap_helpers.rb +++ b/spec/support/helpers/ldap_helpers.rb @@ -69,6 +69,32 @@ module LdapHelpers allow_any_instance_of(Gitlab::Auth::Ldap::Adapter) .to receive(:ldap_search).and_raise(Gitlab::Auth::Ldap::LdapConnectionError) end + + def stub_ldap_access(user, provider, provider_label) + ldap_server_config = + { + 'label' => provider_label, + 'provider_name' => provider, + 'attributes' => {}, + 'encryption' => 'plain', + 'uid' => 'uid', + 'base' => 'dc=example,dc=com' + } + uid = 'my-uid' + allow(::Gitlab::Auth::Ldap::Config).to receive_messages(enabled: true, servers: [ldap_server_config]) + allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [provider.to_sym]) + + Ldap::OmniauthCallbacksController.define_providers! + Rails.application.reload_routes! + + mock_auth_hash(provider, uid, user.email) + allow(Gitlab::Auth::Ldap::Access).to receive(:allowed?).with(user).and_return(true) + + allow_next_instance_of(ActionDispatch::Routing::RoutesProxy) do |instance| + allow(instance).to receive(:"user_#{provider}_omniauth_callback_path") + .and_return("/users/auth/#{provider}/callback") + end + end end LdapHelpers.include_mod_with('LdapHelpers') diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index 87a1f5459ec..44237b821c3 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -119,6 +119,16 @@ module LoginHelpers click_button "oauth-login-#{provider}" end + def sign_in_using_ldap!(user, ldap_tab, ldap_name) + visit new_user_session_path + click_link ldap_tab + fill_in 'username', with: user.username + fill_in 'password', with: user.password + within("##{ldap_name}") do + click_button 'Sign in' + end + end + def register_via(provider, uid, email, additional_info: {}) mock_auth_hash(provider, uid, email, additional_info: additional_info) visit new_user_registration_path diff --git a/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb b/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb index ea14c465872..b4afde311ba 100644 --- a/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb +++ b/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb @@ -1,14 +1,23 @@ # frozen_string_literal: true -RSpec.shared_examples 'N+1 query check' do |threshold = 0| + +RSpec.shared_examples 'N+1 query check' do |threshold: 0, skip_cached: true| it 'prevents N+1 queries' do execute_query # "warm up" to prevent undeterministic counts expect(graphql_errors).to be_blank # Sanity check - ex falso quodlibet! - control = ActiveRecord::QueryRecorder.new { execute_query } + control = ActiveRecord::QueryRecorder.new(skip_cached: skip_cached) { execute_query } expect(control.count).to be > 0 search_params[:iids] << extra_iid_for_second_query - expect { execute_query }.not_to exceed_query_limit(control).with_threshold(threshold) + expect { execute_query }.not_to exceed_query_count_limit(control, skip_cached: skip_cached, threshold: threshold) + end + + def exceed_query_count_limit(control, skip_cached: true, threshold: 0) + if skip_cached + exceed_query_limit(control).with_threshold(threshold) + else + exceed_all_query_limit(control).with_threshold(threshold) + end end end diff --git a/spec/workers/ci/parse_secure_file_metadata_worker_spec.rb b/spec/workers/ci/parse_secure_file_metadata_worker_spec.rb new file mode 100644 index 00000000000..57bbd8a6ff0 --- /dev/null +++ b/spec/workers/ci/parse_secure_file_metadata_worker_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::ParseSecureFileMetadataWorker do + describe '#perform' do + include_examples 'an idempotent worker' do + let(:secure_file) { create(:ci_secure_file) } + subject { described_class.new.perform(secure_file&.id) } + + context 'when the file is found' do + it 'calls update_metadata!' do + allow(::Ci::SecureFile).to receive(:find_by_id).and_return(secure_file) + expect(secure_file).to receive(:update_metadata!) + + subject + end + end + end + + context 'when file is not found' do + let(:secure_file) { nil } + + it 'does not call update_metadata!' do + expect(secure_file).not_to receive(:update_metadata!) + + subject + end + end + end +end |