提交 d7b72321 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 ed16c943
......@@ -25,6 +25,11 @@ export default {
containerClasses: ['dag-graph-container', 'gl-display-flex', 'gl-flex-direction-column'].join(
' ',
),
hoverFadeClasses: [
'gl-cursor-pointer',
'gl-transition-duration-slow',
'gl-transition-timing-function-ease',
].join(' '),
},
gitLabColorRotation: [
'#e17223',
......@@ -230,7 +235,10 @@ export default {
.attr('id', d => {
return this.createAndAssignId(d, 'uid', LINK_SELECTOR);
})
.classed(`${LINK_SELECTOR} gl-cursor-pointer`, true);
.classed(
`${LINK_SELECTOR} gl-transition-property-stroke-opacity ${this.$options.viewOptions.hoverFadeClasses}`,
true,
);
},
generateNodes(svg, nodeData) {
......@@ -242,7 +250,10 @@ export default {
.data(nodeData)
.enter()
.append('line')
.classed(`${NODE_SELECTOR} gl-cursor-pointer`, true)
.classed(
`${NODE_SELECTOR} gl-transition-property-stroke ${this.$options.viewOptions.hoverFadeClasses}`,
true,
)
.attr('id', d => {
return this.createAndAssignId(d, 'uid', NODE_SELECTOR);
})
......
<script>
import { __ } from '~/locale';
import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import Poll from '~/lib/utils/poll';
export default {
name: 'MRWidgetTerraformPlan',
components: {
GlIcon,
GlLink,
GlLoadingIcon,
GlSprintf,
},
props: {
endpoint: {
type: String,
required: true,
},
},
data() {
return {
loading: true,
plans: {},
};
},
computed: {
addNum() {
return Number(this.plan.create);
},
changeNum() {
return Number(this.plan.update);
},
deleteNum() {
return Number(this.plan.delete);
},
logUrl() {
return this.plan.job_path;
},
plan() {
const firstPlanKey = Object.keys(this.plans)[0];
return this.plans[firstPlanKey] ?? {};
},
validPlanValues() {
return this.addNum + this.changeNum + this.deleteNum >= 0;
},
},
created() {
this.fetchPlans();
},
methods: {
fetchPlans() {
this.loading = true;
const poll = new Poll({
resource: {
fetchPlans: () => axios.get(this.endpoint),
},
data: this.endpoint,
method: 'fetchPlans',
successCallback: ({ data }) => {
this.plans = data;
if (Object.keys(this.plan).length) {
this.loading = false;
poll.stop();
}
},
errorCallback: () => {
this.plans = {};
this.loading = false;
flash(__('An error occurred while loading terraform report'));
},
});
poll.makeRequest();
},
},
};
</script>
<template>
<section class="mr-widget-section">
<div class="mr-widget-body media d-flex flex-row">
<span class="append-right-default align-self-start align-self-lg-center">
<gl-icon name="status_warning" :size="24" />
</span>
<div class="d-flex flex-fill flex-column flex-md-row">
<div class="terraform-mr-plan-text normal d-flex flex-column flex-lg-row">
<p class="m-0 pr-1">{{ __('A terraform report was generated in your pipelines.') }}</p>
<gl-loading-icon v-if="loading" size="md" />
<p v-else-if="validPlanValues" class="m-0">
<gl-sprintf
:message="
__(
'Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete',
)
"
>
<template #addNum>
<strong>{{ addNum }}</strong>
</template>
<template #changeNum>
<strong>{{ changeNum }}</strong>
</template>
<template #deleteNum>
<strong>{{ deleteNum }}</strong>
</template>
</gl-sprintf>
</p>
<p v-else class="m-0">{{ __('Changes are unknown') }}</p>
</div>
<div class="terraform-mr-plan-actions">
<gl-link
v-if="logUrl"
:href="logUrl"
target="_blank"
data-track-event="click_terraform_mr_plan_button"
data-track-label="mr_widget_terraform_mr_plan_button"
data-track-property="terraform_mr_plan_button"
class="btn btn-sm js-terraform-report-link"
rel="noopener"
>
{{ __('View full log') }}
<gl-icon name="external-link" />
</gl-link>
</div>
</div>
</div>
</section>
</template>
<script>
import { GlSkeletonLoading } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import Poll from '~/lib/utils/poll';
import TerraformPlan from './terraform_plan.vue';
export default {
name: 'MRWidgetTerraformContainer',
components: {
GlSkeletonLoading,
TerraformPlan,
},
props: {
endpoint: {
type: String,
required: true,
},
},
data() {
return {
loading: true,
plans: {},
poll: null,
};
},
created() {
this.fetchPlans();
},
beforeDestroy() {
this.poll.stop();
},
methods: {
fetchPlans() {
this.loading = true;
this.poll = new Poll({
resource: {
fetchPlans: () => axios.get(this.endpoint),
},
data: this.endpoint,
method: 'fetchPlans',
successCallback: ({ data }) => {
this.plans = data;
if (Object.keys(this.plans).length) {
this.loading = false;
this.poll.stop();
}
},
errorCallback: () => {
this.plans = { bad_plan: {} };
this.loading = false;
this.poll.stop();
},
});
this.poll.makeRequest();
},
},
};
</script>
<template>
<section class="mr-widget-section">
<div v-if="loading" class="mr-widget-body media">
<gl-skeleton-loading />
</div>
<terraform-plan
v-for="(plan, key) in plans"
v-else
:key="key"
:plan="plan"
class="mr-widget-body media"
/>
</section>
</template>
<script>
import { __ } from '~/locale';
import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
export default {
name: 'TerraformPlan',
components: {
GlIcon,
GlLink,
GlSprintf,
},
props: {
plan: {
required: true,
type: Object,
},
},
computed: {
addNum() {
return Number(this.plan.create);
},
changeNum() {
return Number(this.plan.update);
},
deleteNum() {
return Number(this.plan.delete);
},
reportChangeText() {
if (this.validPlanValues) {
return __(
'Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete',
);
}
return __('Generating the report caused an error.');
},
reportHeaderText() {
if (this.plan.job_name) {
return __('The Terraform report %{name} was generated in your pipelines.');
}
return __('A Terraform report was generated in your pipelines.');
},
validPlanValues() {
return this.addNum + this.changeNum + this.deleteNum >= 0;
},
},
};
</script>
<template>
<div class="gl-display-flex">
<span
class="gl-display-flex gl-align-items-center gl-justify-content-center append-right-default gl-align-self-start gl-mt-1"
>
<gl-icon name="status_warning" :size="24" />
</span>
<div class="gl-display-flex gl-flex-fill-1 gl-flex-direction-column flex-md-row">
<div class="terraform-mr-plan-text normal gl-display-flex gl-flex-direction-column">
<p class="gl-m-0 gl-pr-1">
<gl-sprintf :message="reportHeaderText">
<template #name>
<strong>{{ plan.job_name }}</strong>
</template>
</gl-sprintf>
</p>
<p class="gl-m-0">
<gl-sprintf :message="reportChangeText">
<template #addNum>
<strong>{{ addNum }}</strong>
</template>
<template #changeNum>
<strong>{{ changeNum }}</strong>
</template>
<template #deleteNum>
<strong>{{ deleteNum }}</strong>
</template>
</gl-sprintf>
</p>
</div>
<div>
<gl-link
v-if="plan.job_path"
:href="plan.job_path"
target="_blank"
data-track-event="click_terraform_mr_plan_button"
data-track-label="mr_widget_terraform_mr_plan_button"
data-track-property="terraform_mr_plan_button"
class="btn btn-sm js-terraform-report-link"
rel="noopener"
>
{{ __('View full log') }}
<gl-icon name="external-link" />
</gl-link>
</div>
</div>
</div>
</template>
......@@ -36,7 +36,7 @@ import CheckingState from './components/states/mr_widget_checking.vue';
import eventHub from './event_hub';
import notify from '~/lib/utils/notify';
import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue';
import TerraformPlan from './components/mr_widget_terraform_plan.vue';
import TerraformPlan from './components/terraform/mr_widget_terraform_container.vue';
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
import { setFaviconOverlay } from '../lib/utils/common_utils';
import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue';
......
import { __ } from '~/locale';
import { generateToolbarItem } from './editor_service';
import buildCustomHTMLRenderer from './services/build_custom_renderer';
export const CUSTOM_EVENTS = {
openAddImageModal: 'gl_openAddImageModal',
......@@ -31,6 +32,7 @@ const TOOLBAR_ITEM_CONFIGS = [
export const EDITOR_OPTIONS = {
toolbarItems: TOOLBAR_ITEM_CONFIGS.map(config => generateToolbarItem(config)),
customHTMLRenderer: buildCustomHTMLRenderer(),
};
export const EDITOR_TYPES = {
......
import renderKramdownList from './renderers/render_kramdown_list';
import renderKramdownText from './renderers/render_kramdown_text';
const listRenderers = [renderKramdownList];
const textRenderers = [renderKramdownText];
const executeRenderer = (renderers, node, context) => {
const availableRenderer = renderers.find(renderer => renderer.canRender(node, context));
return availableRenderer ? availableRenderer.render(context) : context.origin();
};
const buildCustomRendererFunctions = (customRenderers, defaults) => {
const customTypes = Object.keys(customRenderers).filter(type => !defaults[type]);
const customEntries = customTypes.map(type => {
const fn = (node, context) => executeRenderer(customRenderers[type], node, context);
return [type, fn];
});
return Object.fromEntries(customEntries);
};
const buildCustomHTMLRenderer = (customRenderers = { list: [], text: [] }) => {
const defaults = {
list(node, context) {
const allListRenderers = [...customRenderers.list, ...listRenderers];
return executeRenderer(allListRenderers, node, context);
},
text(node, context) {
const allTextRenderers = [...customRenderers.text, ...textRenderers];
return executeRenderer(allTextRenderers, node, context);
},
};
return {
...buildCustomRendererFunctions(customRenderers, defaults),
...defaults,
};
};
export default buildCustomHTMLRenderer;
const buildToken = (type, tagName, props) => {
return { type, tagName, ...props };
};
export const buildUneditableOpenTokens = token => {
return [
buildToken('openTag', 'div', {
attributes: { contenteditable: false },
classNames: [
'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
],
}),
token,
];
};
export const buildUneditableCloseToken = () => buildToken('closeTag', 'div');
export const buildUneditableTokens = token => {
return [...buildUneditableOpenTokens(token), buildUneditableCloseToken()];
};
import { buildUneditableOpenTokens, buildUneditableCloseToken } from './build_uneditable_token';
const isKramdownTOC = ({ type, literal }) => type === 'text' && literal === 'TOC';
const canRender = node => {
let targetNode = node;
while (targetNode !== null) {
const { firstChild } = targetNode;
const isLeaf = firstChild === null;
if (isLeaf) {
if (isKramdownTOC(targetNode)) {
return true;
}
break;
}
targetNode = targetNode.firstChild;
}
return false;
};
const render = ({ entering, origin }) =>
entering ? buildUneditableOpenTokens(origin()) : buildUneditableCloseToken();
export default { canRender, render };
import { buildUneditableTokens } from './build_uneditable_token';
const canRender = ({ literal }) => {
const kramdownRegex = /(^{:.+}$)/gm;
return kramdownRegex.test(literal);
};
const render = ({ origin }) => {
return buildUneditableTokens(origin());
};
export default { canRender, render };
......@@ -100,3 +100,11 @@
.gl-pl-7 {
padding-left: $gl-spacing-scale-7;
}
.gl-transition-property-stroke-opacity {
transition-property: stroke-opacity;
}
.gl-transition-property-stroke {
transition-property: stroke;
}
......@@ -97,29 +97,6 @@ class IssuableBaseService < BaseService
params.delete(label_key) if params[label_key].nil?
end
def filter_labels_in_param(key)
return if params[key].to_a.empty?
params[key] = available_labels.id_in(params[key]).pluck_primary_key
end
def find_or_create_label_ids
labels = params.delete(:labels)
return unless labels
params[:label_ids] = labels.map do |label_name|
label = Labels::FindOrCreateService.new(
current_user,
parent,
title: label_name.strip,
available_labels: available_labels
).execute
label.try(:id)
end.compact
end
def labels_service
@labels_service ||= ::Labels::AvailableLabelsService.new(current_user, parent, params)
end
......
- if (readme = @repository.readme) && readme.rich_viewer
.tree-holder
.nav-block.mt-0
= render 'projects/tree/tree_header', tree: @tree
%article.file-holder.readme-holder{ id: 'readme', class: ("limited-width-container" unless fluid_layout) }
.js-file-title.file-title
= blob_icon readme.mode, readme.name
= link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
%strong
= readme.name
.js-file-title.file-title-flex-parent
.file-header-content
= blob_icon readme.mode, readme.name
= link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
%strong
= readme.name
= render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
- else
......
---
title: Show clone button on project page for readme preference
merge_request: 33023
author:
type: changed
---
title: Display Multiple Terraform Reports in MR Widget
merge_request: 34392
author:
type: added
---
title: "Prevents editing of non-markdown kramdown content in the Static Site Editor's WYSIWYG mode"
merge_request: 34185
author:
type: changed
---
title: Create time-space partitions in separate schema
merge_request: 34504
author:
type: other
# Ignore table used temporarily in background migration
ActiveRecord::SchemaDumper.ignore_tables = ["untracked_files_for_uploads"]
# Ignore dynamically managed partitions in static application schema
ActiveRecord::SchemaDumper.ignore_tables += ["partitions_dynamic.*"]
# frozen_string_literal: true
class CreateDynamicPartitionsSchema < ActiveRecord::Migration[6.0]
include Gitlab::Database::SchemaHelpers
DOWNTIME = false
def up
execute 'CREATE SCHEMA partitions_dynamic'
create_comment(:schema, :partitions_dynamic, <<~EOS.strip)
Schema to hold partitions managed dynamically from the application, e.g. for time space partitioning.
EOS
end
def down
execute 'DROP SCHEMA partitions_dynamic'
end
end
SET search_path=public;
CREATE SCHEMA partitions_dynamic;
COMMENT ON SCHEMA partitions_dynamic IS 'Schema to hold partitions managed dynamically from the application, e.g. for time space partitioning.';
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
CREATE TABLE public.abuse_reports (
......@@ -14068,7 +14064,6 @@ COPY "schema_migrations" (version) FROM STDIN;
20200610130002
20200613104045
20200615083635
20200615101135
20200615121217
20200615123055
20200615232735
......
......@@ -61,6 +61,7 @@ export default {
<gl-icon
name="issues"
:size="24"
class="class-name"
/>
</template>
```
......@@ -68,7 +69,7 @@ export default {
- **name** Name of the Icon in the SVG Sprite ([Overview is available here](https://gitlab-org.gitlab.io/gitlab-svgs)).
- **size (optional)** Number value for the size which is then mapped to a specific CSS class
(Available Sizes: 8, 12, 16, 18, 24, 32, 48, 72 are mapped to `sXX` CSS classes)
- **css-classes (optional)** Additional CSS Classes to add to the SVG tag.
- **class (optional)** Additional CSS Classes to add to the SVG tag.
### Usage in HTML/JS
......
......@@ -12,7 +12,7 @@ Some gems may not include their license information in their `gemspec` file, and
### License Finder commands
> Note: License Finder currently uses GitLab misused terms of whitelist and blacklist. As a result, the commands below references those terms. We've created an [issue on their project](https://github.com/pivotal/LicenseFinder/issues/745) to propose that they rename their commands.
> Note: License Finder currently uses GitLab misused terms of `whitelist` and `blacklist`. As a result, the commands below reference those terms. We've created an [issue on their project](https://github.com/pivotal/LicenseFinder/issues/745) to propose that they rename their commands.
There are a few basic commands License Finder provides that you'll need in order to manage license detection.
......
......@@ -242,7 +242,7 @@ a `before_script` execution to prepare your scan job.
To pass your project's dependencies as artifacts, the dependencies must be included
in the project's working directory and specified using the `artifacts:path` configuration.
If all dependencies are present, the `-compile=false` flag can be provided to the
If all dependencies are present, the `COMPILE=false` variable can be provided to the
analyzer and compilation will be skipped:
```yaml
......@@ -267,10 +267,9 @@ build:
spotbugs-sast:
dependencies:
- build
script:
- /analyzer run -compile=false
variables:
MAVEN_REPO_PATH: ./.m2/repository
COMPILE: false
artifacts:
reports:
sast: gl-sast-report.json
......@@ -339,6 +338,7 @@ Some analyzers can be customized with environment variables.
| `SCAN_KUBERNETES_MANIFESTS` | Kubesec | Set to `"true"` to scan Kubernetes manifests. |
| `KUBESEC_HELM_CHARTS_PATH` | Kubesec | Optional path to Helm charts that `helm` will use to generate a Kubernetes manifest that `kubesec` will scan. If dependencies are defined, `helm dependency build` should be ran in a `before_script` to fetch the necessary dependencies. |
| `KUBESEC_HELM_OPTIONS` | Kubesec | Additional arguments for the `helm` executable. |
| `COMPILE` | SpotBugs | Set to `"false"` to disable project compilation and dependency fetching |
| `ANT_HOME` | SpotBugs | The `ANT_HOME` environment variable. |
| `ANT_PATH` | SpotBugs | Path to the `ant` executable. |
| `GRADLE_PATH` | SpotBugs | Path to the `gradle` executable. |
......
......@@ -821,6 +821,16 @@ user's home location (in this case the user is `root` since it runs in a
Docker container), and Maven will use the configured CI
[environment variables](../../../ci/variables/README.md#predefined-environment-variables).
### Version validation
The version string is validated using the following regex.
```ruby
\A(\.?[\w\+-]+\.?)+\z
```
You can play around with the regex and try your version strings on [this regular expression editor](https://rubular.com/r/rrLQqUXjfKEoL6).
## Troubleshooting
### Useful Maven command line options
......
......@@ -173,6 +173,24 @@ Read through the documentation on [project settings](settings/index.md).
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
## Remove a project
To remove a project, first navigate to the home page for that project.
1. Navigate to **Settings > General**.
1. Expand the **Advanced** section.
1. Scroll down to the **Remove project** section.
1. Click **Remove project**
1. Confirm this action by typing in the expected text.
### Delayed removal **(PREMIUM)**
By default, clicking to remove a project is followed by a seven day delay. Admins can restore the project during this period of time.
This delay [may be changed by an admin](../admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only).
Admins can view all projects pending deletion. If you're an administrator, go to the top navigation bar, click **Projects > Your projects**, and then select the **Removed projects** tab.
From this tab an admin can restore any project.
## CI/CD for external repositories **(PREMIUM)**
Instead of importing a repository directly to GitLab, you can connect your repository
......
......@@ -8,7 +8,6 @@ module Gitlab
WHITELISTED_TABLES = %w[audit_events].freeze
ERROR_SCOPE = 'table partitioning'
DYNAMIC_PARTITIONS_SCHEMA = 'partitions_dynamic'
# Creates a partitioned copy of an existing table, using a RANGE partitioning strategy on a timestamp column.
# One partition is created per month between the given `min_date` and `max_date`.
......@@ -126,7 +125,7 @@ module Gitlab
min_date = min_date.beginning_of_month.to_date
max_date = max_date.next_month.beginning_of_month.to_date
create_range_partition_safely("#{table_name}_000000", table_name, 'MINVALUE', to_sql_date_literal(min_date), schema: DYNAMIC_PARTITIONS_SCHEMA)
create_range_partition_safely("#{table_name}_000000", table_name, 'MINVALUE', to_sql_date_literal(min_date))
while min_date < max_date
partition_name = "#{table_name}_#{min_date.strftime('%Y%m')}"
......@@ -134,7 +133,7 @@ module Gitlab
lower_bound = to_sql_date_literal(min_date)
upper_bound = to_sql_date_literal(next_date)
create_range_partition_safely(partition_name, table_name, lower_bound, upper_bound, schema: DYNAMIC_PARTITIONS_SCHEMA)
create_range_partition_safely(partition_name, table_name, lower_bound, upper_bound)
min_date = next_date
end
end
......@@ -143,8 +142,8 @@ module Gitlab
connection.quote(date.strftime('%Y-%m-%d'))
end
def create_range_partition_safely(partition_name, table_name, lower_bound, upper_bound, schema:)
if table_exists?("#{schema}.#{partition_name}")
def create_range_partition_safely(partition_name, table_name, lower_bound, upper_bound)
if table_exists?(partition_name)
# rubocop:disable Gitlab/RailsLogger
Rails.logger.warn "Partition not created because it already exists" \
" (this may be due to an aborted migration or similar): partition_name: #{partition_name}"
......@@ -152,7 +151,7 @@ module Gitlab
return
end
create_range_partition(partition_name, table_name, lower_bound, upper_bound, schema: schema)
create_range_partition(partition_name, table_name, lower_bound, upper_bound)
end
def create_sync_trigger(source_table, target_table, unique_key)
......
......@@ -69,11 +69,9 @@ module Gitlab
private
def create_range_partition(partition_name, table_name, lower_bound, upper_bound, schema:)
raise ArgumentError, 'explicit schema is required but currently missing' unless schema
def create_range_partition(partition_name, table_name, lower_bound, upper_bound)
execute(<<~SQL)
CREATE TABLE #{schema}.#{partition_name} PARTITION OF #{table_name}
CREATE TABLE #{partition_name} PARTITION OF #{table_name}
FOR VALUES FROM (#{lower_bound}) TO (#{upper_bound})
SQL
end
......
......@@ -43,6 +43,10 @@ module Gitlab
@maven_app_name_regex ||= /\A[\w\-\.]+\z/.freeze
end
def maven_version_regex
@maven_version_regex ||= /\A(\.?[\w\+-]+\.?)+\z/.freeze
end
def maven_app_group_regex
maven_app_name_regex
end
......
......@@ -972,6 +972,9 @@ msgstr ""
msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
msgstr ""
msgid "A Terraform report was generated in your pipelines."
msgstr ""
msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages"
msgstr ""
......@@ -1059,9 +1062,6 @@ msgstr ""
msgid "A suggestion is not applicable."
msgstr ""
msgid "A terraform report was generated in your pipelines."
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
......@@ -2392,9 +2392,6 @@ msgstr ""
msgid "An error occurred while loading project creation UI"
msgstr ""
msgid "An error occurred while loading terraform report"
msgstr ""
msgid "An error occurred while loading the data. Please try again."
msgstr ""
......@@ -4054,9 +4051,6 @@ msgstr ""
msgid "Changes are still tracked. Useful for cluster/index migrations."
msgstr ""
msgid "Changes are unknown"
msgstr ""
msgid "Changes suppressed. Click to show."
msgstr ""
......@@ -10187,6 +10181,9 @@ msgstr ""
msgid "Generate new export"
msgstr ""
msgid "Generating the report caused an error."
msgstr ""
msgid "Geo"
msgstr ""
......@@ -22235,6 +22232,9 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
msgid "The Terraform report %{name} was generated in your pipelines."
msgstr ""
msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
msgstr ""
......
......@@ -3,7 +3,7 @@
exports[`The DAG graph in the basic case renders the graph svg 1`] = `
"<svg viewBox=\\"0,0,1000,540\\" width=\\"1000\\" height=\\"540\\">
<g fill=\\"none\\" stroke-opacity=\\"0.8\\">
<g id=\\"dag-link43\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link43\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad53\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#e17223\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#83ab4a\\"></stop>
......@@ -20,7 +20,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,129L190,129L190,129L369.3333333333333,129\\" stroke=\\"url(#dag-grad53)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip63)\\"></path>
</g>
<g id=\\"dag-link44\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link44\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad54\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#83ab4a\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#6f3500\\"></stop>
......@@ -37,7 +37,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,129L509.3333333333333,129L509.3333333333333,129.0000000000002L630.6666666666666,129.0000000000002\\" stroke=\\"url(#dag-grad54)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip64)\\"></path>
</g>
<g id=\\"dag-link45\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link45\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad55\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#5772ff\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#6f3500\\"></stop>
......@@ -54,7 +54,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,212.00000000000003L306,212.00000000000003L306,187.0000000000002L630.6666666666666,187.0000000000002\\" stroke=\\"url(#dag-grad55)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip65)\\"></path>
</g>
<g id=\\"dag-link46\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link46\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad56\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#b24800\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#006887\\"></stop>
......@@ -71,7 +71,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,295L338.93333333333334,295L338.93333333333334,269.9999999999998L369.3333333333333,269.9999999999998\\" stroke=\\"url(#dag-grad56)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip66)\\"></path>
</g>
<g id=\\"dag-link47\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link47\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad57\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#25d2d2\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#487900\\"></stop>
......@@ -88,7 +88,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,378.00000000000006L144.66666666666669,378.00000000000006L144.66666666666669,352.99999999999994L369.3333333333333,352.99999999999994\\" stroke=\\"url(#dag-grad57)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip67)\\"></path>
</g>
<g id=\\"dag-link48\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link48\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad58\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#006887\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#d84280\\"></stop>
......@@ -105,7 +105,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,269.9999999999998L464,269.9999999999998L464,270.0000000000001L630.6666666666666,270.0000000000001\\" stroke=\\"url(#dag-grad58)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip68)\\"></path>
</g>
<g id=\\"dag-link49\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link49\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad59\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#487900\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#d84280\\"></stop>
......@@ -122,7 +122,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,352.99999999999994L522,352.99999999999994L522,328.0000000000001L630.6666666666666,328.0000000000001\\" stroke=\\"url(#dag-grad59)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip69)\\"></path>
</g>
<g id=\\"dag-link50\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link50\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad60\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#487900\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#3547de\\"></stop>
......@@ -139,7 +139,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,410.99999999999994L580,410.99999999999994L580,411L630.6666666666666,411\\" stroke=\\"url(#dag-grad60)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip70)\\"></path>
</g>
<g id=\\"dag-link51\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link51\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad61\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"638.6666666666666\\" x2=\\"884\\">
<stop offset=\\"0%\\" stop-color=\\"#d84280\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#006887\\"></stop>
......@@ -156,7 +156,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M630.6666666666666,270.0000000000001L861.6,270.0000000000001L861.6,270.1890725105691L892,270.1890725105691\\" stroke=\\"url(#dag-grad61)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip71)\\"></path>
</g>
<g id=\\"dag-link52\\" class=\\"dag-link gl-cursor-pointer\\">
<g id=\\"dag-link52\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad62\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"638.6666666666666\\" x2=\\"884\\">
<stop offset=\\"0%\\" stop-color=\\"#3547de\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#275600\\"></stop>
......@@ -175,18 +175,18 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</g>
</g>
<g>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node73\\" stroke=\\"#e17223\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"104\\" y2=\\"154.00000000000003\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node74\\" stroke=\\"#83ab4a\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"104\\" y2=\\"154\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node75\\" stroke=\\"#5772ff\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"187.00000000000003\\" y2=\\"237.00000000000003\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node76\\" stroke=\\"#b24800\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"270\\" y2=\\"320.00000000000006\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node77\\" stroke=\\"#25d2d2\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"353.00000000000006\\" y2=\\"403.0000000000001\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node78\\" stroke=\\"#6f3500\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"104.0000000000002\\" y2=\\"212.00000000000009\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node79\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"244.99999999999977\\" y2=\\"294.99999999999994\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node80\\" stroke=\\"#487900\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"327.99999999999994\\" y2=\\"436\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node81\\" stroke=\\"#d84280\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"245.00000000000009\\" y2=\\"353\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node82\\" stroke=\\"#3547de\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"386\\" y2=\\"436\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node83\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"245.18907251056908\\" y2=\\"295.1890725105691\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node84\\" stroke=\\"#275600\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"386\\" y2=\\"436\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node73\\" stroke=\\"#e17223\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"104\\" y2=\\"154.00000000000003\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node74\\" stroke=\\"#83ab4a\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"104\\" y2=\\"154\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node75\\" stroke=\\"#5772ff\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"187.00000000000003\\" y2=\\"237.00000000000003\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node76\\" stroke=\\"#b24800\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"270\\" y2=\\"320.00000000000006\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node77\\" stroke=\\"#25d2d2\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"353.00000000000006\\" y2=\\"403.0000000000001\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node78\\" stroke=\\"#6f3500\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"104.0000000000002\\" y2=\\"212.00000000000009\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node79\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"244.99999999999977\\" y2=\\"294.99999999999994\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node80\\" stroke=\\"#487900\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"327.99999999999994\\" y2=\\"436\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node81\\" stroke=\\"#d84280\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"245.00000000000009\\" y2=\\"353\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node82\\" stroke=\\"#3547de\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"386\\" y2=\\"436\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node83\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"245.18907251056908\\" y2=\\"295.1890725105691\\"></line>
<line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node84\\" stroke=\\"#275600\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"386\\" y2=\\"436\\"></line>
</g>
<g class=\\"gl-font-sm\\">
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"58.00000000000003px\\" width=\\"84\\" x=\\"8\\" y=\\"100\\" class=\\"gl-overflow-visible\\">
......
export const invalidPlan = {};
export const validPlan = {
create: 10,
update: 20,
delete: 30,
job_name: 'Plan Changes',
job_path: '/path/to/ci/logs/1',
};
export const plans = {
'1': validPlan,
'2': invalidPlan,
'3': {
create: 1,
update: 2,
delete: 3,
job_name: 'Plan 3',
job_path: '/path/to/ci/logs/3',
},
};
import { GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { GlSkeletonLoading } from '@gitlab/ui';
import { plans } from './mock_data';
import { shallowMount } from '@vue/test-utils';
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import MrWidgetTerraformPlan from '~/vue_merge_request_widget/components/mr_widget_terraform_plan.vue';
import MrWidgetTerraformContainer from '~/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue';
import Poll from '~/lib/utils/poll';
import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue';
const plan = {
create: 10,
update: 20,
delete: 30,
job_path: '/path/to/ci/logs',
};
describe('MrWidgetTerraformPlan', () => {
describe('MrWidgetTerraformConainer', () => {
let mock;
let wrapper;
const propsData = { endpoint: '/path/to/terraform/report.json' };
const findPlans = () => wrapper.findAll(TerraformPlan).wrappers.map(x => x.props('plan'));
const mockPollingApi = (response, body, header) => {
mock.onGet(propsData.endpoint).reply(response, body, header);
};
const mountWrapper = () => {
wrapper = shallowMount(MrWidgetTerraformPlan, { propsData });
wrapper = shallowMount(MrWidgetTerraformContainer, { propsData });
return axios.waitForAll();
};
......@@ -36,9 +33,9 @@ describe('MrWidgetTerraformPlan', () => {
mock.restore();
});
describe('loading poll', () => {
describe('when data is loading', () => {
beforeEach(() => {
mockPollingApi(200, { '123': plan }, {});
mockPollingApi(200, plans, {});
return mountWrapper().then(() => {
wrapper.setData({ loading: true });
......@@ -46,28 +43,20 @@ describe('MrWidgetTerraformPlan', () => {
});
});
it('Diplays loading icon when loading is true', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(GlSprintf).exists()).toBe(false);
it('diplays loading skeleton', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(true);
expect(wrapper.text()).not.toContain(
'A terraform report was generated in your pipelines. Changes are unknown',
);
expect(findPlans()).toEqual([]);
});
});
describe('successful poll', () => {
describe('polling', () => {
let pollRequest;
let pollStop;
beforeEach(() => {
pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
pollStop = jest.spyOn(Poll.prototype, 'stop');
mockPollingApi(200, { '123': plan }, {});
return mountWrapper();
});
afterEach(() => {
......@@ -75,33 +64,43 @@ describe('MrWidgetTerraformPlan', () => {
pollStop.mockRestore();
});
it('content change text', () => {
expect(wrapper.find(GlSprintf).exists()).toBe(true);
});
describe('successful poll', () => {
beforeEach(() => {
mockPollingApi(200, plans, {});
it('renders button when url is found', () => {
expect(wrapper.find(GlLink).exists()).toBe(true);
});
return mountWrapper();
});
it('does not make additional requests after poll is successful', () => {
expect(pollRequest).toHaveBeenCalledTimes(1);
expect(pollStop).toHaveBeenCalledTimes(1);
});
});
it('diplays terraform components and stops loading', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(false);
describe('polling fails', () => {
beforeEach(() => {
mockPollingApi(500, null, {});
return mountWrapper();
expect(findPlans()).toEqual(Object.values(plans));
});
it('does not make additional requests after poll is successful', () => {
expect(pollRequest).toHaveBeenCalledTimes(1);
expect(pollStop).toHaveBeenCalledTimes(1);
});
});
it('does not display changes text when api fails', () => {
expect(wrapper.text()).toContain(
'A terraform report was generated in your pipelines. Changes are unknown',
);
describe('polling fails', () => {
beforeEach(() => {
mockPollingApi(500, null, {});
return mountWrapper();
});
it('stops loading', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(false);
});
expect(wrapper.find('.js-terraform-report-link').exists()).toBe(false);
expect(wrapper.find(GlLink).exists()).toBe(false);
it('generates one broken plan', () => {
expect(findPlans()).toEqual([{}]);
});
it('does not make additional requests after poll is unsuccessful', () => {
expect(pollRequest).toHaveBeenCalledTimes(1);
expect(pollStop).toHaveBeenCalledTimes(1);
});
});
});
});
import { invalidPlan, validPlan } from './mock_data';
import { GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue';
describe('TerraformPlan', () => {
let wrapper;
const findLogButton = () => wrapper.find('.js-terraform-report-link');
const mountWrapper = propsData => {
wrapper = shallowMount(TerraformPlan, { stubs: { GlLink, GlSprintf }, propsData });
};
afterEach(() => {
wrapper.destroy();
});
describe('validPlan', () => {
beforeEach(() => {
mountWrapper({ plan: validPlan });
});
it('diplays the plan job_name', () => {
expect(wrapper.text()).toContain(
`The Terraform report ${validPlan.job_name} was generated in your pipelines.`,
);
});
it('diplays the reported changes', () => {
expect(wrapper.text()).toContain(
`Reported Resource Changes: ${validPlan.create} to add, ${validPlan.update} to change, ${validPlan.delete} to delete`,
);
});
it('renders button when url is found', () => {
expect(findLogButton().exists()).toBe(true);
expect(findLogButton().text()).toEqual('View full log');
});
});
describe('invalidPlan', () => {
beforeEach(() => {
mountWrapper({ plan: invalidPlan });
});
it('diplays generic header since job_name is missing', () => {
expect(wrapper.text()).toContain('A Terraform report was generated in your pipelines.');
});
it('diplays generic error since report values are missing', () => {
expect(wrapper.text()).toContain('Generating the report caused an error.');
});
it('does not render button because url is missing', () => {
expect(findLogButton().exists()).toBe(false);
});
});
});
const buildMockTextNode = literal => {
return {
firstChild: null,
literal,
type: 'text',
};
};
const buildMockListNode = literal => {
return {
firstChild: {
firstChild: {
firstChild: buildMockTextNode(literal),
type: 'paragraph',
},
type: 'item',
},
type: 'list',
};
};
export const kramdownListNode = buildMockListNode('TOC');
export const normalListNode = buildMockListNode('Just another bullet point');
export const kramdownTextNode = buildMockTextNode('{:toc}');
export const normalTextNode = buildMockTextNode('This is just normal text.');
const uneditableOpenToken = {
type: 'openTag',
tagName: 'div',
attributes: { contenteditable: false },
classNames: [
'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
],
};
export const uneditableCloseToken = { type: 'closeTag', tagName: 'div' };
export const originToken = {
type: 'text',
content: '{:.no_toc .hidden-md .hidden-lg}',
};
export const uneditableOpenTokens = [uneditableOpenToken, originToken];
export const uneditableTokens = [...uneditableOpenTokens, uneditableCloseToken];
import buildCustomHTMLRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer';
describe('Build Custom Renderer Service', () => {
describe('buildCustomHTMLRenderer', () => {
it('should return an object with the default renderer functions when lacking arguments', () => {
expect(buildCustomHTMLRenderer()).toEqual(
expect.objectContaining({
list: expect.any(Function),
text: expect.any(Function),
}),
);
});
it('should return an object with both custom and default renderer functions when passed customRenderers', () => {
const mockHtmlCustomRenderer = jest.fn();
const customRenderers = {
html: [mockHtmlCustomRenderer],
};
expect(buildCustomHTMLRenderer(customRenderers)).toEqual(
expect.objectContaining({
html: expect.any(Function),
list: expect.any(Function),
text: expect.any(Function),
}),
);
});
});
});
import {
buildUneditableOpenTokens,
buildUneditableCloseToken,
buildUneditableTokens,
} from '~/vue_shared/components/rich_content_editor/services/renderers//build_uneditable_token';
import {
originToken,
uneditableOpenTokens,
uneditableCloseToken,
uneditableTokens,
} from '../../mock_data';
describe('Build Uneditable Token renderer helper', () => {
describe('buildUneditableOpenTokens', () => {
it('returns a 2-item array of tokens with the originToken appended to an open token', () => {
const result = buildUneditableOpenTokens(originToken);
expect(result).toHaveLength(2);
expect(result).toStrictEqual(uneditableOpenTokens);
});
});
describe('buildUneditableCloseToken', () => {
it('returns an object literal representing the uneditable close token', () => {
expect(buildUneditableCloseToken()).toStrictEqual(uneditableCloseToken);
});
});
describe('buildUneditableTokens', () => {
it('returns a 3-item array of tokens with the originToken wrapped in the middle', () => {
const result = buildUneditableTokens(originToken);
expect(result).toHaveLength(3);
expect(result).toStrictEqual(uneditableTokens);
});
});
});
import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list';
import {
buildUneditableOpenTokens,
buildUneditableCloseToken,
} from '~/vue_shared/components/rich_content_editor/services/renderers//build_uneditable_token';
import { kramdownListNode, normalListNode } from '../../mock_data';
describe('Render Kramdown List renderer', () => {
describe('canRender', () => {
it('should return true when the argument is a special kramdown TOC ordered/unordered list', () => {
expect(renderer.canRender(kramdownListNode)).toBe(true);
});
it('should return false when the argument is a normal ordered/unordered list', () => {
expect(renderer.canRender(normalListNode)).toBe(false);
});
});
describe('render', () => {
const origin = jest.fn();
it('should return uneditable open tokens when entering', () => {
const context = { entering: true, origin };
expect(renderer.render(context)).toStrictEqual(buildUneditableOpenTokens(origin()));
});
it('should return an uneditable close tokens when exiting', () => {
const context = { entering: false, origin };
expect(renderer.render(context)).toStrictEqual(buildUneditableCloseToken(origin()));
});
});
});
import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text';
import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers//build_uneditable_token';
import { kramdownTextNode, normalTextNode } from '../../mock_data';
describe('Render Kramdown Text renderer', () => {
describe('canRender', () => {
it('should return true when the argument `literal` has kramdown syntax', () => {
expect(renderer.canRender(kramdownTextNode)).toBe(true);
});
it('should return false when the argument `literal` lacks kramdown syntax', () => {
expect(renderer.canRender(normalTextNode)).toBe(false);
});
});
describe('render', () => {
const origin = jest.fn();
it('should return uneditable tokens', () => {
const context = { origin };
expect(renderer.render(context)).toStrictEqual(buildUneditableTokens(origin()));
});
});
});
......@@ -241,7 +241,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
describe '#drop_partitioned_table_for' do
let(:expected_tables) do
%w[000000 201912 202001 202002].map { |suffix| "partitions_dynamic.#{partitioned_table}_#{suffix}" }.unshift(partitioned_table)
%w[000000 201912 202001 202002].map { |suffix| "#{partitioned_table}_#{suffix}" }.unshift(partitioned_table)
end
context 'when the table is not whitelisted' do
......
......@@ -262,6 +262,39 @@ describe Gitlab::Regex do
it { is_expected.not_to match('!!()()') }
end
describe '.maven_version_regex' do
subject { described_class.maven_version_regex }
it { is_expected.to match('0')}
it { is_expected.to match('1') }
it { is_expected.to match('03') }
it { is_expected.to match('2.0') }
it { is_expected.to match('01.2') }
it { is_expected.to match('10.2.3-beta')}
it { is_expected.to match('1.2-SNAPSHOT') }
it { is_expected.to match('20') }
it { is_expected.to match('20.3') }
it { is_expected.to match('1.2.1') }
it { is_expected.to match('1.4.2-12') }
it { is_expected.to match('1.2-beta-2') }
it { is_expected.to match('12.1.2-2-1') }
it { is_expected.to match('1.1-beta-2') }
it { is_expected.to match('1.3.350.v20200505-1744') }
it { is_expected.to match('2.0.0.v200706041905-7C78EK9E_EkMNfNOd2d8qq') }
it { is_expected.to match('1.2-alpha-1-20050205.060708-1') }
it { is_expected.to match('703220b4e2cea9592caeb9f3013f6b1e5335c293') }
it { is_expected.to match('RELEASE') }
it { is_expected.not_to match('..1.2.3') }
it { is_expected.not_to match(' 1.2.3') }
it { is_expected.not_to match("1.2.3 \r\t") }
it { is_expected.not_to match("\r\t 1.2.3") }
it { is_expected.not_to match('1./2.3') }
it { is_expected.not_to match('1.2.3-4/../../') }
it { is_expected.not_to match('1.2.3-4%2e%2e%') }
it { is_expected.not_to match('../../../../../1.2.3') }
it { is_expected.not_to match('%2e%2e%2f1.2.3') }
end
describe '.semver_regex' do
subject { described_class.semver_regex }
......
......@@ -8,8 +8,8 @@ module PartitioningHelpers
expect(columns_with_part_type).to match_array(actual_columns)
end
def expect_range_partition_of(partition_name, table_name, min_value, max_value, schema: 'partitions_dynamic')
definition = find_partition_definition(partition_name, schema: schema)
def expect_range_partition_of(partition_name, table_name, min_value, max_value)
definition = find_partition_definition(partition_name)
expect(definition).not_to be_nil
expect(definition['base_table']).to eq(table_name.to_s)
......@@ -40,7 +40,7 @@ module PartitioningHelpers
SQL
end
def find_partition_definition(partition, schema: 'partitions_dynamic')
def find_partition_definition(partition)
connection.select_one(<<~SQL)
select
parent_class.relname as base_table,
......@@ -48,10 +48,7 @@ module PartitioningHelpers
from pg_class
inner join pg_inherits i on pg_class.oid = inhrelid
inner join pg_class parent_class on parent_class.oid = inhparent
inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace
where pg_namespace.nspname = '#{schema}'
and pg_class.relname = '#{partition}'
and pg_class.relispartition
where pg_class.relname = '#{partition}' and pg_class.relispartition;
SQL
end
end
......@@ -1138,20 +1138,20 @@
dependencies:
defer-to-connect "^1.0.1"
"@toast-ui/editor@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@toast-ui/editor/-/editor-2.0.1.tgz#749e5be1f02f42ded51488d1575ab1c19ca59952"
integrity sha512-TC481O/zP37boY6H6oVN6KLVMY7yrU8zQu+3xqZ71V3Sr6D2XyaGb2Xub9XqTdqzBmzsf7y4Gi+EXO0IQ3rGVA==
"@toast-ui/editor@2.1.2", "@toast-ui/editor@^2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@toast-ui/editor/-/editor-2.1.2.tgz#0472431bd039ae70882d77910e83f0ad222d0b1c"
integrity sha512-yoWRVyp2m1dODH+bmzJaILUgl2L57GCQJ8c8+XRgJMwfxb/TFz5U+oT8JGAU5VwozIzKF0SyVMs8AEePwwhIIA==
dependencies:
"@types/codemirror" "0.0.71"
codemirror "^5.48.4"
"@toast-ui/vue-editor@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@toast-ui/vue-editor/-/vue-editor-2.0.1.tgz#c9c8c8da4c0a67b9fbc4240464388c67d72a0c22"
integrity sha512-sGsApl0n+GVAZbmPA+tTrq9rmmyh2mRgCgg2/mu1/lN7S4vPv/nQH8KXxLG9Y6hG2+kgelqz6wvbOCdzlM/HmQ==
"@toast-ui/vue-editor@2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@toast-ui/vue-editor/-/vue-editor-2.1.2.tgz#a790e69fcf7fb426e6b8ea190733477c3cc756aa"
integrity sha512-RK01W6D8FqtNq4MjWsXk6KRzOU/vL6mpiADAnH5l/lFK4G6UQJhLKsMRfmxIqCH+ivm8VtQzGdd9obUfD+XbCw==
dependencies:
"@toast-ui/editor" "^2.0.1"
"@toast-ui/editor" "^2.1.2"
"@types/anymatch@*":
version "1.3.0"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册