Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-10-06 21:09:01 +00:00
parent 1287690a36
commit ca3ff7f842
71 changed files with 1112 additions and 285 deletions

View File

@ -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'

View File

@ -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>

View File

@ -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 @@
}
}
}
}
}

View File

@ -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>

View File

@ -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 }">

View File

@ -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;

View File

@ -1,5 +0,0 @@
.milestone-combobox {
.dropdown-menu.show {
overflow: hidden;
}
}

View File

@ -1,3 +0,0 @@
.release-block {
transition: background-color 1s linear;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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

View File

@ -47,7 +47,6 @@ module Resolvers
def preloads
{
alert_management_alert: [:alert_management_alert],
labels: [:labels],
assignees: [:assignees],
participants: Issue.participant_includes,
timelogs: [:timelogs],

View File

@ -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

View File

@ -42,7 +42,6 @@ module ResolvesMergeRequests
assignees: [:assignees],
reviewers: [:reviewers],
participants: MergeRequest.participant_includes,
labels: [:labels],
author: [:author],
merged_at: [:metrics],
commit_count: [:metrics],

View File

@ -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.'

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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 })

View File

@ -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?

View File

@ -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

View File

@ -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)

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
class GroupLabelPolicy < BasePolicy
delegate { @subject.group }
delegate { @subject.parent_container }
end

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
class ProjectLabelPolicy < BasePolicy
delegate { @subject.project }
delegate { @subject.parent_container }
end

View File

@ -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

View File

@ -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?

View File

@ -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,

View File

@ -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)

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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).

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 =

View File

@ -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

View File

@ -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

BIN
spec/fixtures/ci_secure_files/sample.cer vendored Normal file

Binary file not shown.

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -95,6 +95,7 @@ label_links:
label:
- subscriptions
- project
- parent_container
- lists
- label_links
- issues

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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