Commit 67714641 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 84521bef 0267193b
......@@ -184,7 +184,12 @@ export default {
'viewDiffsFileByFile',
'mrReviews',
]),
...mapGetters('diffs', ['whichCollapsedTypes', 'isParallelView', 'currentDiffIndex']),
...mapGetters('diffs', [
'whichCollapsedTypes',
'isParallelView',
'currentDiffIndex',
'fileCodequalityDiff',
]),
...mapGetters(['isNotesFetched', 'getNoteableData']),
diffs() {
if (!this.viewDiffsFileByFile) {
......@@ -282,6 +287,7 @@ export default {
endpointMetadata: this.endpointMetadata,
endpointBatch: this.endpointBatch,
endpointCoverage: this.endpointCoverage,
endpointCodequality: this.endpointCodequality,
endpointUpdateUser: this.endpointUpdateUser,
projectPath: this.projectPath,
dismissEndpoint: this.dismissEndpoint,
......@@ -291,10 +297,6 @@ export default {
mrReviews: this.rehydratedMrReviews,
});
if (this.endpointCodequality) {
this.setCodequalityEndpoint(this.endpointCodequality);
}
if (this.shouldShow) {
this.fetchData();
}
......@@ -339,7 +341,6 @@ export default {
...mapActions('diffs', [
'moveToNeighboringCommit',
'setBaseConfig',
'setCodequalityEndpoint',
'fetchDiffFilesMeta',
'fetchDiffFilesBatch',
'fetchCoverageFiles',
......@@ -531,6 +532,7 @@ export default {
:help-page-path="helpPagePath"
:can-current-user-fork="canCurrentUserFork"
:view-diffs-file-by-file="viewDiffsFileByFile"
:codequality-diff="fileCodequalityDiff(file.file_path)"
/>
<div
v-if="showFileByFileNavigation"
......
......@@ -67,6 +67,11 @@ export default {
type: Boolean,
required: true,
},
codequalityDiff: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
......@@ -80,7 +85,7 @@ export default {
genericError: GENERIC_ERROR,
},
computed: {
...mapState('diffs', ['currentDiffFileId', 'codequalityDiff']),
...mapState('diffs', ['currentDiffFileId']),
...mapGetters(['isNotesFetched']),
...mapGetters('diffs', ['getDiffFileDiscussions']),
viewBlobHref() {
......@@ -149,9 +154,7 @@ export default {
return loggedIn && featureOn;
},
hasCodequalityChanges() {
return (
this.codequalityDiff?.files && this.codequalityDiff?.files[this.file.file_path]?.length > 0
);
return this.codequalityDiff.length > 0;
},
},
watch: {
......
......@@ -13,6 +13,11 @@ import {
CONFLICT_THEIR,
CONFLICT_MARKER,
} from '../constants';
import {
getInteropInlineAttributes,
getInteropOldSideAttributes,
getInteropNewSideAttributes,
} from '../utils/interoperability';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
import * as utils from './diff_row_utils';
......@@ -116,6 +121,16 @@ export default {
isLeftConflictMarker() {
return [CONFLICT_MARKER_OUR, CONFLICT_MARKER_THEIR].includes(this.line.left?.type);
},
interopLeftAttributes() {
if (this.inline) {
return getInteropInlineAttributes(this.line.left);
}
return getInteropOldSideAttributes(this.line.left);
},
interopRightAttributes() {
return getInteropNewSideAttributes(this.line.right);
},
},
mounted() {
this.scrollToLineIfNeededParallel(this.line);
......@@ -181,6 +196,7 @@ export default {
<div
data-testid="left-side"
class="diff-grid-left left-side"
v-bind="interopLeftAttributes"
@dragover.prevent
@dragenter="onDragEnter(line.left, index)"
@dragend="onDragEnd"
......@@ -286,6 +302,7 @@ export default {
v-if="!inline"
data-testid="right-side"
class="diff-grid-right right-side"
v-bind="interopRightAttributes"
@dragover.prevent
@dragenter="onDragEnter(line.right, index)"
@dragend="onDragEnd"
......
......@@ -2,6 +2,7 @@
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import { CONTEXT_LINE_CLASS_NAME } from '../constants';
import { getInteropInlineAttributes } from '../utils/interoperability';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
import {
isHighlighted,
......@@ -96,6 +97,9 @@ export default {
shouldShowAvatarsOnGutter() {
return this.line.hasDiscussions;
},
interopAttrs() {
return getInteropInlineAttributes(this.line);
},
},
mounted() {
this.scrollToLineIfNeededInline(this.line);
......@@ -124,6 +128,7 @@ export default {
:id="inlineRowId"
:class="classNameMap"
class="line_holder"
v-bind="interopAttrs"
@mouseover="handleMouseMove"
@mouseout="handleMouseMove"
>
......
......@@ -3,6 +3,10 @@ import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gi
import $ from 'jquery';
import { mapActions, mapGetters, mapState } from 'vuex';
import { CONTEXT_LINE_CLASS_NAME, PARALLEL_DIFF_VIEW_TYPE } from '../constants';
import {
getInteropOldSideAttributes,
getInteropNewSideAttributes,
} from '../utils/interoperability';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
import * as utils from './diff_row_utils';
......@@ -108,6 +112,12 @@ export default {
this.line.hasDiscussionsRight,
);
},
interopLeftAttributes() {
return getInteropOldSideAttributes(this.line.left);
},
interopRightAttributes() {
return getInteropNewSideAttributes(this.line.right);
},
},
mounted() {
this.scrollToLineIfNeededParallel(this.line);
......@@ -217,6 +227,7 @@ export default {
:key="line.left.line_code"
v-safe-html="line.left.rich_text"
:class="parallelViewLeftLineType"
v-bind="interopLeftAttributes"
class="line_content with-coverage parallel left-side"
@mousedown="handleParallelLineMouseDown"
></td>
......@@ -283,6 +294,7 @@ export default {
hll: isHighlighted,
},
]"
v-bind="interopRightAttributes"
class="line_content with-coverage parallel right-side"
@mousedown="handleParallelLineMouseDown"
></td>
......
import Cookies from 'js-cookie';
import Visibility from 'visibilityjs';
import Vue from 'vue';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { diffViewerModes } from '~/ide/constants';
......@@ -52,12 +53,15 @@ import {
prepareLineForRenamedFile,
} from './utils';
let eTagPoll;
export const setBaseConfig = ({ commit }, options) => {
const {
endpoint,
endpointMetadata,
endpointBatch,
endpointCoverage,
endpointCodequality,
endpointUpdateUser,
projectPath,
dismissEndpoint,
......@@ -71,6 +75,7 @@ export const setBaseConfig = ({ commit }, options) => {
endpointMetadata,
endpointBatch,
endpointCoverage,
endpointCodequality,
endpointUpdateUser,
projectPath,
dismissEndpoint,
......@@ -233,6 +238,48 @@ export const fetchCoverageFiles = ({ commit, state }) => {
coveragePoll.makeRequest();
};
export const clearEtagPoll = () => {
eTagPoll = null;
};
export const stopCodequalityPolling = () => {
if (eTagPoll) eTagPoll.stop();
};
export const restartCodequalityPolling = () => {
if (eTagPoll) eTagPoll.restart();
};
export const fetchCodequality = ({ commit, state, dispatch }) => {
eTagPoll = new Poll({
resource: {
getCodequalityDiffReports: (endpoint) => axios.get(endpoint),
},
data: state.endpointCodequality,
method: 'getCodequalityDiffReports',
successCallback: ({ status, data }) => {
if (status === httpStatusCodes.OK) {
commit(types.SET_CODEQUALITY_DATA, data);
eTagPoll.stop();
}
},
errorCallback: () => createFlash(__('Something went wrong on our end. Please try again!')),
});
if (!Visibility.hidden()) {
eTagPoll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
dispatch('restartCodequalityPolling');
} else {
dispatch('stopCodequalityPolling');
}
});
};
export const setHighlightedRow = ({ commit }, lineCode) => {
const fileHash = lineCode.split('_')[0];
commit(types.SET_HIGHLIGHTED_ROW, lineCode);
......
......@@ -135,6 +135,16 @@ export const fileLineCoverage = (state) => (file, line) => {
return {};
};
/**
* Returns the codequality diff data for a given file
* @param {string} filePath
* @returns {Array}
*/
export const fileCodequalityDiff = (state) => (filePath) => {
if (!state.codequalityDiff.files || !state.codequalityDiff.files[filePath]) return [];
return state.codequalityDiff.files[filePath];
};
/**
* Returns index of a currently selected diff in diffFiles
* @returns {number}
......
......@@ -9,7 +9,8 @@ import {
import { fileByFile } from '../../utils/preferences';
import { getDefaultWhitespace } from '../utils';
const viewTypeFromQueryString = getParameterValues('view')[0];
const getViewTypeFromQueryString = () => getParameterValues('view')[0];
const viewTypeFromCookie = Cookies.get(DIFF_VIEW_COOKIE_NAME);
const defaultViewType = INLINE_DIFF_VIEW_TYPE;
const whiteSpaceFromQueryString = getParameterValues('w')[0];
......@@ -29,9 +30,10 @@ export default () => ({
startVersion: null, // Null unless a target diff is selected for comparison that is not the "base" diff
diffFiles: [],
coverageFiles: {},
codequalityDiff: {},
mergeRequestDiffs: [],
mergeRequestDiff: null,
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
diffViewType: getViewTypeFromQueryString() || viewTypeFromCookie || defaultViewType,
tree: [],
treeEntries: {},
showTreeList: true,
......
import * as actions from 'ee_else_ce/diffs/store/actions';
import createState from 'ee_else_ce/diffs/store/modules/diff_state';
import mutations from 'ee_else_ce/diffs/store/mutations';
import * as actions from '../actions';
import * as getters from '../getters';
import mutations from '../mutations';
import createState from './diff_state';
export default () => ({
namespaced: true,
......
......@@ -11,6 +11,7 @@ export const SET_MR_FILE_REVIEWS = 'SET_MR_FILE_REVIEWS';
export const SET_DIFF_VIEW_TYPE = 'SET_DIFF_VIEW_TYPE';
export const SET_COVERAGE_DATA = 'SET_COVERAGE_DATA';
export const SET_CODEQUALITY_DATA = 'SET_CODEQUALITY_DATA';
export const SET_MERGE_REQUEST_DIFFS = 'SET_MERGE_REQUEST_DIFFS';
export const TOGGLE_LINE_HAS_FORM = 'TOGGLE_LINE_HAS_FORM';
export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES';
......
......@@ -33,6 +33,7 @@ export default {
endpointMetadata,
endpointBatch,
endpointCoverage,
endpointCodequality,
endpointUpdateUser,
projectPath,
dismissEndpoint,
......@@ -46,6 +47,7 @@ export default {
endpointMetadata,
endpointBatch,
endpointCoverage,
endpointCodequality,
endpointUpdateUser,
projectPath,
dismissEndpoint,
......@@ -89,6 +91,10 @@ export default {
Object.assign(state, { coverageFiles });
},
[types.SET_CODEQUALITY_DATA](state, codequalityDiffData) {
Object.assign(state, { codequalityDiff: codequalityDiffData });
},
[types.RENDER_FILE](state, file) {
renderFile(file);
},
......
const OLD = 'old';
const NEW = 'new';
const ATTR_PREFIX = 'data-interop-';
export const ATTR_TYPE = `${ATTR_PREFIX}type`;
export const ATTR_LINE = `${ATTR_PREFIX}line`;
export const ATTR_NEW_LINE = `${ATTR_PREFIX}new-line`;
export const ATTR_OLD_LINE = `${ATTR_PREFIX}old-line`;
export const getInteropInlineAttributes = (line) => {
if (!line) {
return null;
}
const interopType = line.type?.startsWith(OLD) ? OLD : NEW;
const interopLine = interopType === OLD ? line.old_line : line.new_line;
return {
[ATTR_TYPE]: interopType,
[ATTR_LINE]: interopLine,
[ATTR_NEW_LINE]: line.new_line,
[ATTR_OLD_LINE]: line.old_line,
};
};
export const getInteropOldSideAttributes = (line) => {
if (!line) {
return null;
}
return {
[ATTR_TYPE]: OLD,
[ATTR_LINE]: line.old_line,
[ATTR_OLD_LINE]: line.old_line,
};
};
export const getInteropNewSideAttributes = (line) => {
if (!line) {
return null;
}
return {
[ATTR_TYPE]: NEW,
[ATTR_LINE]: line.new_line,
[ATTR_NEW_LINE]: line.new_line,
};
};
---
title: Schedule artifact expiry backfill again.
merge_request: 59270
author:
type: changed
# frozen_string_literal: true
class RescheduleArtifactExpiryBackfillAgain < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'BackfillArtifactExpiryDate'
SWITCH_DATE = Date.new(2020, 06, 22).freeze
disable_ddl_transaction!
class JobArtifact < ActiveRecord::Base
include EachBatch
self.inheritance_column = :_type_disabled
self.table_name = 'ci_job_artifacts'
scope :without_expiry_date, -> { where(expire_at: nil) }
scope :before_switch, -> { where("date(created_at AT TIME ZONE 'UTC') < ?::date", SWITCH_DATE) }
end
def up
Gitlab::BackgroundMigration.steal(MIGRATION) do |job|
job.delete
false
end
queue_background_migration_jobs_by_range_at_intervals(
JobArtifact.without_expiry_date.before_switch,
MIGRATION,
2.minutes,
batch_size: 200_000
)
end
def down
Gitlab::BackgroundMigration.steal(MIGRATION) do |job|
job.delete
false
end
end
end
407806cc168ef9859c9a4f1bd4db7a56aee01367e784ea0767889863b9ace35d
\ No newline at end of file
......@@ -139,6 +139,18 @@ The following metrics can be controlled by feature flags:
| `gitlab_method_call_duration_seconds` | `prometheus_metrics_method_instrumentation` |
| `gitlab_view_rendering_duration_seconds` | `prometheus_metrics_view_instrumentation` |
## Praefect metrics
You can [configure Praefect to report metrics](../../gitaly/praefect.md#praefect).
These are some of the Praefect metrics served from the `/metrics` path on the [configured port](index.md#changing-the-port-and-address-prometheus-listens-on)
(9652 by default).
| Metric | Type | Since | Description | Labels |
| :----- | :--- | ----: | :---------- | :----- |
| `gitaly_praefect_replication_latency_bucket` | Histogram | 12.10 | The amount of time it takes for replication to complete once the replication job starts. | |
| `gitaly_praefect_replication_delay_bucket` | Histogram | 12.10 | A measure of how much time passes between when the replication job is created and when it starts. | |
| `gitaly_praefect_node_latency_bucket` | Histogram | 12.10 | The latency in Gitaly returning health check information to Praefect. This indicates Praefect connection saturation. | |
## Sidekiq metrics
Sidekiq jobs may also gather metrics, and these metrics can be accessed if the
......
---
stage: none
group: unassigned
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
redirect_to: 'https://about.gitlab.com/handbook/engineering/ux/technical-writing/workflow/#monthly-documentation-releases'
---
# Monthly release process
This file was moved to [another location](https://about.gitlab.com/handbook/engineering/ux/technical-writing/workflow/#monthly-documentation-releases).
When a new GitLab version is released on the 22nd, we release version-specific published
documentation for the new version.
We complete the process as soon as possible after the GitLab version is announced. The result is:
- The [online published documentation](https://docs.gitlab.com) includes:
- The three most recent minor releases of the current major version. For example 13.9, 13.8, and
13.7.
- The most recent minor releases of the last two major versions. For example 12.10, and 11.11.
- Documentation updates after the 22nd are for the next release. The versions drop down
should have the current milestone with `-pre` appended to it, for example `13.10-pre`.
Each documentation release:
- Has a dedicated branch, named in the format `XX.yy`.
- Has a Docker image that contains a build of that branch.
For example:
- For [GitLab 13.9](https://docs.gitlab.com/13.9/index.html), the
[stable branch](https://gitlab.com/gitlab-org/gitlab-docs/-/tree/13.9) and Docker image:
[`registry.gitlab.com/gitlab-org/gitlab-docs:13.9`](https://gitlab.com/gitlab-org/gitlab-docs/container_registry/631635).
- For [GitLab 13.8](https://docs.gitlab.com/13.8/index.html), the
[stable branch](https://gitlab.com/gitlab-org/gitlab-docs/-/tree/13.8) and Docker image:
[`registry.gitlab.com/gitlab-org/gitlab-docs:13.8`](https://gitlab.com/gitlab-org/gitlab-docs/container_registry/631635).
## Recommended timeline
To minimize problems during the documentation release process, use the following timeline:
- Any time before the 17th of the month:
[Add the charts version](#add-chart-version), so that the documentation is built using the
[version of the charts project that maps to](https://docs.gitlab.com/charts/installation/version_mappings.html)
the GitLab release. This step may have been completed already.
- Between the 17th and the 20th of the month:
1. [Create a stable branch and Docker image](#create-stable-branch-and-docker-image-for-release) for
the new version.
1. [Create a release merge request](#create-release-merge-request) for the new version, which
updates the version dropdown menu for the current documentation and adds the release to the
Docker configuration. For example, the
[release merge request for 13.9](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/1555).
1. [Update the three online versions](#update-dropdown-for-online-versions), so that they display the new release on their
version dropdown menus.
- On the 22nd of the month:
[Merge the release merge requests and run the necessary Docker image builds](#merge-merge-requests-and-run-docker-image-builds).
## Add chart version
To add a new charts version for the release:
1. Make sure you're in the root path of the `gitlab-docs` repository.
1. Open `content/_data/chart_versions.yaml` and add the new stable branch version using the
[version mapping](https://docs.gitlab.com/charts/installation/version_mappings.html). Only the
`major.minor` version is needed.
1. Create a new merge request and merge it.
NOTE:
If you have time, add anticipated future mappings to `content/_data/chart_versions.yaml`. This saves
a step for the next GitLab release.
## Create stable branch and Docker image for release
To create a stable branch and Docker image for the release:
1. Make sure you're in the root path of the `gitlab-docs` repository.
1. Run the Rake task to create the single version. For example, to create the 13.9 release branch
and perform others tasks:
```shell
./bin/rake "release:single[13.9]"
```
A branch for the release is created, a new `Dockerfile.13.9` is created, and `.gitlab-ci.yml`
has branches variables updated into a new branch. These files are automatically committed.
1. Push the newly created branch, but **don't create a merge request**. After you push, the
`image:docs-single` job creates a new Docker image tagged with the name of the branch you created
earlier. You can see the Docker image in the `registry` environment at
<https://gitlab.com/gitlab-org/gitlab-docs/-/environments/folders/registry>.
For example, see [the 13.9 release pipeline](https://gitlab.com/gitlab-org/gitlab-docs/-/pipelines/260288747).
Optionally, you can test locally by:
1. Building the image and running it. For example, for GitLab 13.9 documentation:
```shell
docker build -t docs:13.9 -f Dockerfile.13.9 .
docker run -it --rm -p 4000:4000 docs:13.9
```
1. Visiting <http://localhost:4000/13.9/> to see if everything works correctly.
## Create release merge request
NOTE:
An [epic is open](https://gitlab.com/groups/gitlab-org/-/epics/4361) to automate this step.
To create the release merge request for the release:
1. Make sure you're in the root path of the `gitlab-docs` repository.
1. Create a branch `release-X-Y`. For example:
```shell
git checkout master
git checkout -b release-13-9
```
1. Edit `content/_data/versions.yaml` and update the lists of versions to reflect the new release:
- Add the latest version to the `online:` section.
- Move the oldest version in `online:` to the `offline:` section. There should now be three
versions in `online:`.
1. Update these Dockerfiles:
- `dockerfiles/Dockerfile.archives`: Add the latest version to the top of the list.
- `Dockerfile.master`: Remove the oldest version, and add the newest version to the
top of the list.
1. Commit and push to create the merge request. For example:
```shell
git add content/ Dockerfile.master dockerfiles/Dockerfile.archives
git commit -m "Release 13.9"
git push origin release-13-9
```
Do not merge the release merge request yet.
## Update dropdown for online versions
To update `content/_data/versions.yaml` for all online versions (stable branches `X.Y` of the
`gitlab-docs` project). For example:
- The merge request to [update the 13.9 version dropdown menu for the 13.9 release](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/1556).
- The merge request to [update the 13.8 version dropdown menu for the 13.9 release](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/1557).
- The merge request to [update the 13.7 version dropdown menu for the 13.9 release](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/1558).
1. Run the Rake task that creates all of the necessary merge requests to update the dropdowns. For
example, for the 13.9 release:
```shell
git checkout release-13-9
./bin/rake release:dropdowns
```
1. [Visit the merge requests page](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests?label_name%5B%5D=release)
to check that their pipelines pass.
Do not merge these merge requests yet.
## Merge merge requests and run Docker image builds
The merge requests for the dropdowns should now all be merged into their respective stable branches.
Each merge triggers a new pipeline for each stable branch. Wait for the stable branch pipelines to
complete, then:
1. Check the [pipelines page](https://gitlab.com/gitlab-org/gitlab-docs/pipelines)
and make sure all stable branches have green pipelines.
1. After all the pipelines succeed:
1. Merge all of the [dropdown merge requests](#update-dropdown-for-online-versions).
1. Merge the [release merge request](#create-release-merge-request).
1. Finally, run the
[`Build docker images weekly` pipeline](https://gitlab.com/gitlab-org/gitlab-docs/pipeline_schedules)
that builds the `:latest` and `:archives` Docker images.
As the last step in the scheduled pipeline, the documentation site deploys with all new versions.
<!-- This redirect file can be deleted after <2021-07-12>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
......@@ -51,8 +51,10 @@ directory in your repository. Commit and push to your default branch.
To create a Markdown file:
1. Click the `+` button next to `master` and click **New file**.
1. Add the name of your issue template to the **File name** text field next to `master`.
1. In a project, go to **Repository**.
1. Next to the default branch, select the **{plus}** button.
1. Select **New file**.
1. Next to the default branch, in the **File name** field, add the name of your issue template.
Make sure that your file has the `.md` extension, for
example `feature_request.md` or `Feature Request.md`.
1. Commit and push to your default branch.
......@@ -61,9 +63,12 @@ If you don't have a `.gitlab/issue_templates` directory in your repository, you
To create the `.gitlab/issue_templates` directory:
1. Click the `+` button next to `master` and select **New directory**.
1. In a project, go to **Repository**.
1. Next to the default branch, select the **{plus}** button.
1. Select **New directory**.
1. Name this new directory `.gitlab` and commit to your default branch.
1. Click the `+` button next to `master` again and select **New directory**.
1. Next to the default branch, select the **{plus}** button.
1. Select **New directory**.
1. Name your directory `issue_templates` and commit to your default branch.
To check if this has worked correctly, [create a new issue](issues/managing_issues.md#create-a-new-issue)
......
......@@ -12,7 +12,7 @@ export default {
i18n: {
badgeTitle: __('Code Quality'),
badgeTooltip: __(
'The merge request has made changes to this file that affect the number of code quality violations in it.',
'The merge request has been updated, and the number of code quality violations in this file has changed.',
),
},
};
......
import Visibility from 'visibilityjs';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import Poll from '~/lib/utils/poll';
import { __ } from '~/locale';
import * as types from './mutation_types';
export * from '~/diffs/store/actions';
let codequalityPoll;
export const setCodequalityEndpoint = ({ commit }, endpoint) => {
commit(types.SET_CODEQUALITY_ENDPOINT, endpoint);
};
export const clearCodequalityPoll = () => {
codequalityPoll = null;
};
export const stopCodequalityPolling = () => {
if (codequalityPoll) codequalityPoll.stop();
};
export const restartCodequalityPolling = () => {
if (codequalityPoll) codequalityPoll.restart();
};
export const fetchCodequality = ({ commit, state, dispatch }) => {
codequalityPoll = new Poll({
resource: {
getCodequalityDiffReports: (endpoint) => axios.get(endpoint),
},
data: state.endpointCodequality,
method: 'getCodequalityDiffReports',
successCallback: ({ status, data }) => {
if (status === httpStatusCodes.OK) {
commit(types.SET_CODEQUALITY_DATA, data);
dispatch('stopCodequalityPolling');
}
},
errorCallback: () =>
createFlash(__('Something went wrong on our end while loading the code quality diff.')),
});
if (!Visibility.hidden()) {
codequalityPoll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
dispatch('restartCodequalityPolling');
} else {
dispatch('stopCodequalityPolling');
}
});
};
import createStateCE from '~/diffs/store/modules/diff_state';
export default () => ({
...createStateCE(),
endpointCodequality: '',
codequalityDiff: {},
});
export const SET_CODEQUALITY_ENDPOINT = 'SET_CODEQUALITY_ENDPOINT';
export const SET_CODEQUALITY_DATA = 'SET_CODEQUALITY_DATA';
import CEMutations from '~/diffs/store/mutations';
import * as types from './mutation_types';
export default {
...CEMutations,
[types.SET_CODEQUALITY_ENDPOINT](state, endpoint) {
Object.assign(state, { endpointCodequality: endpoint });
},
[types.SET_CODEQUALITY_DATA](state, codequalityDiffData) {
Object.assign(state, { codequalityDiff: codequalityDiffData });
},
};
import { mount, createLocalVue } from '@vue/test-utils';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import CodeQualityBadge from 'ee/diffs/components/code_quality_badge.vue';
......@@ -9,7 +9,7 @@ import createDiffsStore from '~/diffs/store/modules';
const getReadableFile = () => JSON.parse(JSON.stringify(diffFileMockDataReadable));
function createComponent({ withCodequality = true }) {
function createComponent({ first = false, last = false, options = {}, props = {} }) {
const file = getReadableFile();
const localVue = createLocalVue();
......@@ -23,31 +23,18 @@ function createComponent({ withCodequality = true }) {
store.state.diffs.diffFiles = [file];
if (withCodequality) {
store.state.diffs.codequalityDiff = {
files: {
[file.file_path]: [
{ line: 1, description: 'Unexpected alert.', severity: 'minor' },
{
line: 3,
description: 'Arrow function has too many statements (52). Maximum allowed is 30.',
severity: 'minor',
},
],
},
};
}
const wrapper = mount(DiffFileComponent, {
const wrapper = shallowMount(DiffFileComponent, {
store,
localVue,
propsData: {
file,
canCurrentUserFork: false,
viewDiffsFileByFile: false,
isFirstFile: false,
isLastFile: false,
isFirstFile: first,
isLastFile: last,
...props,
},
...options,
});
return {
......@@ -65,24 +52,27 @@ describe('EE DiffFile', () => {
});
describe('code quality badge', () => {
describe('when there is diff data for the file', () => {
beforeEach(() => {
({ wrapper } = createComponent({ withCodequality: true }));
});
it('shows the code quality badge', () => {
expect(wrapper.find(CodeQualityBadge).exists()).toBe(true);
});
it('is shown when there is diff data for the file', () => {
({ wrapper } = createComponent({
props: {
codequalityDiff: [
{ line: 1, description: 'Unexpected alert.', severity: 'minor' },
{
line: 3,
description: 'Arrow function has too many statements (52). Maximum allowed is 30.',
severity: 'minor',
},
],
},
}));
expect(wrapper.find(CodeQualityBadge)).toExist();
});
describe('when there is no diff data for the file', () => {
beforeEach(() => {
({ wrapper } = createComponent({ withCodequality: false }));
});
it('is not shown when there is no diff data for the file', () => {
({ wrapper } = createComponent({}));
it('does not show the code quality badge', () => {
expect(wrapper.find(CodeQualityBadge).exists()).toBe(false);
});
expect(wrapper.find(CodeQualityBadge)).toExist();
});
});
});
import MockAdapter from 'axios-mock-adapter';
import {
setCodequalityEndpoint,
clearCodequalityPoll,
stopCodequalityPolling,
fetchCodequality,
} from 'ee/diffs/store/actions';
import * as types from 'ee/diffs/store/mutation_types';
import testAction from 'helpers/vuex_action_helper';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
jest.mock('~/flash');
describe('EE DiffsStoreActions', () => {
describe('setCodequalityEndpoint', () => {
it('should set given endpoint', (done) => {
const endpoint = '/codequality_mr_diff.json';
testAction(
setCodequalityEndpoint,
{ endpoint },
{},
[{ type: types.SET_CODEQUALITY_ENDPOINT, payload: { endpoint } }],
[],
done,
);
});
});
describe('fetchCodequality', () => {
let mock;
const endpoint = '/codequality_mr_diff.json';
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
stopCodequalityPolling();
clearCodequalityPoll();
});
it('should commit SET_CODEQUALITY_DATA with received response and stop polling', (done) => {
const data = {
files: { 'app.js': [{ line: 1, description: 'Unexpected alert.', severity: 'minor' }] },
};
mock.onGet(endpoint).reply(200, { data });
testAction(
fetchCodequality,
{},
{ endpointCodequality: endpoint },
[{ type: types.SET_CODEQUALITY_DATA, payload: { data } }],
[{ type: 'stopCodequalityPolling' }],
done,
);
});
it('should show flash on API error', (done) => {
mock.onGet(endpoint).reply(400);
testAction(fetchCodequality, {}, { endpoint }, [], [], () => {
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith(expect.stringMatching('Something went wrong'));
done();
});
});
});
});
import * as types from 'ee/diffs/store/mutation_types';
import mutations from 'ee/diffs/store/mutations';
describe('EE DiffsStoreMutations', () => {
describe('SET_CODEQUALITY_ENDPOINT', () => {
it('sets the endpoint into state', () => {
const state = {};
const endpoint = '/codequality_mr_diff.json';
mutations[types.SET_CODEQUALITY_ENDPOINT](state, endpoint);
expect(state.endpointCodequality).toEqual(endpoint);
});
});
describe('SET_CODEQUALITY_DATA', () => {
it('should set codequality data', () => {
const state = { codequalityDiff: {} };
const codequality = {
files: { 'app.js': [{ line: 1, description: 'Unexpected alert.', severity: 'minor' }] },
};
mutations[types.SET_CODEQUALITY_DATA](state, codequality);
expect(state.codequalityDiff).toEqual(codequality);
});
});
});
......@@ -29053,9 +29053,6 @@ msgstr ""
msgid "Something went wrong on our end"
msgstr ""
msgid "Something went wrong on our end while loading the code quality diff."
msgstr ""
msgid "Something went wrong on our end."
msgstr ""
......@@ -31027,7 +31024,7 @@ msgstr ""
msgid "The merge request can now be merged."
msgstr ""
msgid "The merge request has made changes to this file that affect the number of code quality violations in it."
msgid "The merge request has been updated, and the number of code quality violations in this file has changed."
msgstr ""
msgid "The metric must be one of %{metrics}."
......
......@@ -211,19 +211,20 @@ describe('CompareVersions', () => {
});
describe('prev commit', () => {
const { location } = window;
beforeAll(() => {
delete window.location;
window.location = { href: `${TEST_HOST}?commit_id=${mrCommit.id}` };
global.jsdom.reconfigure({
url: `${TEST_HOST}?commit_id=${mrCommit.id}`,
});
});
beforeEach(() => {
jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {});
afterAll(() => {
global.jsdom.reconfigure({
url: TEST_HOST,
});
});
afterAll(() => {
window.location = location;
beforeEach(() => {
jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {});
});
it('uses the correct href', () => {
......@@ -253,19 +254,20 @@ describe('CompareVersions', () => {
});
describe('next commit', () => {
const { location } = window;
beforeAll(() => {
delete window.location;
window.location = { href: `${TEST_HOST}?commit_id=${mrCommit.id}` };
global.jsdom.reconfigure({
url: `${TEST_HOST}?commit_id=${mrCommit.id}`,
});
});
beforeEach(() => {
jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {});
afterAll(() => {
global.jsdom.reconfigure({
url: TEST_HOST,
});
});
afterAll(() => {
window.location = location;
beforeEach(() => {
jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {});
});
it('uses the correct href', () => {
......
......@@ -4,6 +4,7 @@ import Vuex from 'vuex';
import DiffRow from '~/diffs/components/diff_row.vue';
import { mapParallel } from '~/diffs/components/diff_row_utils';
import diffsModule from '~/diffs/store/modules';
import { findInteropAttributes } from '../find_interop_attributes';
import diffFileMockData from '../mock_data/diff_file';
describe('DiffRow', () => {
......@@ -211,4 +212,20 @@ describe('DiffRow', () => {
expect(coverage.classes('no-coverage')).toBeFalsy();
});
});
describe('interoperability', () => {
it.each`
desc | line | inline | leftSide | rightSide
${'with inline and new_line'} | ${{ left: { old_line: 3, new_line: 5, type: 'new' } }} | ${true} | ${{ type: 'new', line: '5', oldLine: '3', newLine: '5' }} | ${null}
${'with inline and no new_line'} | ${{ left: { old_line: 3, type: 'old' } }} | ${true} | ${{ type: 'old', line: '3', oldLine: '3' }} | ${null}
${'with parallel and no right side'} | ${{ left: { old_line: 3, new_line: 5 } }} | ${false} | ${{ type: 'old', line: '3', oldLine: '3' }} | ${null}
${'with parallel and no left side'} | ${{ right: { old_line: 3, new_line: 5 } }} | ${false} | ${null} | ${{ type: 'new', line: '5', newLine: '5' }}
${'with parallel and right side'} | ${{ left: { old_line: 3 }, right: { new_line: 5 } }} | ${false} | ${{ type: 'old', line: '3', oldLine: '3' }} | ${{ type: 'new', line: '5', newLine: '5' }}
`('$desc, sets interop data attributes', ({ line, inline, leftSide, rightSide }) => {
const wrapper = createWrapper({ props: { line, inline } });
expect(findInteropAttributes(wrapper, '[data-testid="left-side"]')).toEqual(leftSide);
expect(findInteropAttributes(wrapper, '[data-testid="right-side"]')).toEqual(rightSide);
});
});
});
......@@ -3,6 +3,7 @@ import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import { mapInline } from '~/diffs/components/diff_row_utils';
import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
import { createStore } from '~/mr_notes/stores';
import { findInteropAttributes } from '../find_interop_attributes';
import discussionsMockData from '../mock_data/diff_discussions';
import diffFileMockData from '../mock_data/diff_file';
......@@ -310,4 +311,16 @@ describe('InlineDiffTableRow', () => {
});
});
});
describe('interoperability', () => {
it.each`
desc | line | expectation
${'with type old'} | ${{ ...thisLine, type: 'old', old_line: 3, new_line: 5 }} | ${{ type: 'old', line: '3', oldLine: '3', newLine: '5' }}
${'with type new'} | ${{ ...thisLine, type: 'new', old_line: 3, new_line: 5 }} | ${{ type: 'new', line: '5', oldLine: '3', newLine: '5' }}
`('$desc, sets interop data attributes', ({ line, expectation }) => {
createComponent({ line });
expect(findInteropAttributes(wrapper)).toEqual(expectation);
});
});
});
......@@ -5,6 +5,7 @@ import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import { mapParallel } from '~/diffs/components/diff_row_utils';
import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue';
import { createStore } from '~/mr_notes/stores';
import { findInteropAttributes } from '../find_interop_attributes';
import discussionsMockData from '../mock_data/diff_discussions';
import diffFileMockData from '../mock_data/diff_file';
......@@ -418,5 +419,27 @@ describe('ParallelDiffTableRow', () => {
});
});
});
describe('interoperability', () => {
beforeEach(() => {
createComponent();
});
it('adds old side interoperability data attributes', () => {
expect(findInteropAttributes(wrapper, '.line_content.left-side')).toEqual({
type: 'old',
line: thisLine.left.old_line.toString(),
oldLine: thisLine.left.old_line.toString(),
});
});
it('adds new side interoperability data attributes', () => {
expect(findInteropAttributes(wrapper, '.line_content.right-side')).toEqual({
type: 'new',
line: thisLine.right.new_line.toString(),
newLine: thisLine.right.new_line.toString(),
});
});
});
});
});
export const findInteropAttributes = (parent, sel) => {
const target = sel ? parent.find(sel) : parent;
if (!target.exists()) {
return null;
}
const type = target.attributes('data-interop-type');
if (!type) {
return null;
}
return {
type,
line: target.attributes('data-interop-line'),
oldLine: target.attributes('data-interop-old-line'),
newLine: target.attributes('data-interop-new-line'),
};
};
......@@ -17,6 +17,9 @@ import {
fetchDiffFilesBatch,
fetchDiffFilesMeta,
fetchCoverageFiles,
clearEtagPoll,
stopCodequalityPolling,
fetchCodequality,
assignDiscussionsToDiff,
removeDiscussionsFromDiff,
startRenderDiffsQueue,
......@@ -98,6 +101,7 @@ describe('DiffsStoreActions', () => {
const endpointMetadata = '/diffs/set/endpoint/metadata';
const endpointBatch = '/diffs/set/endpoint/batch';
const endpointCoverage = '/diffs/set/coverage_reports';
const endpointCodequality = '/diffs/set/codequality_diff';
const projectPath = '/root/project';
const dismissEndpoint = '/-/user_callouts';
const showSuggestPopover = false;
......@@ -109,6 +113,7 @@ describe('DiffsStoreActions', () => {
endpointBatch,
endpointMetadata,
endpointCoverage,
endpointCodequality,
projectPath,
dismissEndpoint,
showSuggestPopover,
......@@ -118,6 +123,7 @@ describe('DiffsStoreActions', () => {
endpointBatch: '',
endpointMetadata: '',
endpointCoverage: '',
endpointCodequality: '',
projectPath: '',
dismissEndpoint: '',
showSuggestPopover: true,
......@@ -130,6 +136,7 @@ describe('DiffsStoreActions', () => {
endpointMetadata,
endpointBatch,
endpointCoverage,
endpointCodequality,
projectPath,
dismissEndpoint,
showSuggestPopover,
......@@ -299,6 +306,47 @@ describe('DiffsStoreActions', () => {
});
});
describe('fetchCodequality', () => {
let mock;
const endpointCodequality = '/fetch';
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
stopCodequalityPolling();
clearEtagPoll();
});
it('should commit SET_CODEQUALITY_DATA with received response', (done) => {
const data = {
files: { 'app.js': [{ line: 1, description: 'Unexpected alert.', severity: 'minor' }] },
};
mock.onGet(endpointCodequality).reply(200, { data });
testAction(
fetchCodequality,
{},
{ endpointCodequality },
[{ type: types.SET_CODEQUALITY_DATA, payload: { data } }],
[],
done,
);
});
it('should show flash on API error', (done) => {
mock.onGet(endpointCodequality).reply(400);
testAction(fetchCodequality, {}, { endpointCodequality }, [], [], () => {
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith(expect.stringMatching('Something went wrong'));
done();
});
});
});
describe('setHighlightedRow', () => {
it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => {
testAction(setHighlightedRow, 'ABC_123', {}, [
......
......@@ -376,6 +376,26 @@ describe('Diffs Module Getters', () => {
});
});
describe('fileCodequalityDiff', () => {
beforeEach(() => {
Object.assign(localState.codequalityDiff, {
files: { 'app.js': [{ line: 1, description: 'Unexpected alert.', severity: 'minor' }] },
});
});
it('returns empty array when no codequality data is available', () => {
Object.assign(localState.codequalityDiff, {});
expect(getters.fileCodequalityDiff(localState)('test.js')).toEqual([]);
});
it('returns array when codequality data is available for given file', () => {
expect(getters.fileCodequalityDiff(localState)('app.js')).toEqual([
{ line: 1, description: 'Unexpected alert.', severity: 'minor' },
]);
});
});
describe('suggestionCommitMessage', () => {
let rootState;
......
......@@ -115,6 +115,19 @@ describe('DiffsStoreMutations', () => {
});
});
describe('SET_CODEQUALITY_DATA', () => {
it('should set codequality data', () => {
const state = { codequalityDiff: {} };
const codequality = {
files: { 'app.js': [{ line: 1, description: 'Unexpected alert.', severity: 'minor' }] },
};
mutations[types.SET_CODEQUALITY_DATA](state, codequality);
expect(state.codequalityDiff).toEqual(codequality);
});
});
describe('SET_DIFF_VIEW_TYPE', () => {
it('should set diff view type properly', () => {
const state = {};
......
import {
getInteropInlineAttributes,
getInteropNewSideAttributes,
getInteropOldSideAttributes,
ATTR_TYPE,
ATTR_LINE,
ATTR_NEW_LINE,
ATTR_OLD_LINE,
} from '~/diffs/utils/interoperability';
describe('~/diffs/utils/interoperability', () => {
describe('getInteropInlineAttributes', () => {
it.each([
['with null input', { input: null, output: null }],
[
'with type=old input',
{
input: { type: 'old', old_line: 3, new_line: 5 },
output: { [ATTR_TYPE]: 'old', [ATTR_LINE]: 3, [ATTR_OLD_LINE]: 3, [ATTR_NEW_LINE]: 5 },
},
],
[
'with type=old-nonewline input',
{
input: { type: 'old-nonewline', old_line: 3, new_line: 5 },
output: { [ATTR_TYPE]: 'old', [ATTR_LINE]: 3, [ATTR_OLD_LINE]: 3, [ATTR_NEW_LINE]: 5 },
},
],
[
'with type=new input',
{
input: { type: 'new', old_line: 3, new_line: 5 },
output: { [ATTR_TYPE]: 'new', [ATTR_LINE]: 5, [ATTR_OLD_LINE]: 3, [ATTR_NEW_LINE]: 5 },
},
],
[
'with type=bogus input',
{
input: { type: 'bogus', old_line: 3, new_line: 5 },
output: { [ATTR_TYPE]: 'new', [ATTR_LINE]: 5, [ATTR_OLD_LINE]: 3, [ATTR_NEW_LINE]: 5 },
},
],
])('%s', (desc, { input, output }) => {
expect(getInteropInlineAttributes(input)).toEqual(output);
});
});
describe('getInteropOldSideAttributes', () => {
it.each`
input | output
${null} | ${null}
${{ old_line: 2 }} | ${{ [ATTR_TYPE]: 'old', [ATTR_LINE]: 2, [ATTR_OLD_LINE]: 2 }}
`('with input=$input', ({ input, output }) => {
expect(getInteropOldSideAttributes(input)).toEqual(output);
});
});
describe('getInteropNewSideAttributes', () => {
it.each`
input | output
${null} | ${null}
${{ new_line: 2 }} | ${{ [ATTR_TYPE]: 'new', [ATTR_LINE]: 2, [ATTR_NEW_LINE]: 2 }}
`('with input=$input', ({ input, output }) => {
expect(getInteropNewSideAttributes(input)).toEqual(output);
});
});
});
......@@ -25,6 +25,10 @@ RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)'
end
before do
# Create a user that matches the project.commit author
# This is so that the "author" information will be populated
create(:user, email: project.commit.author_email, name: project.commit.author_name)
sign_in(user)
end
......@@ -33,17 +37,21 @@ RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)'
end
it 'merge_request_diffs/with_commit.json' do
# Create a user that matches the project.commit author
# This is so that the "author" information will be populated
create(:user, email: project.commit.author_email, name: project.commit.author_name)
render_merge_request(merge_request, commit_id: project.commit.sha)
end
it 'merge_request_diffs/diffs_metadata.json' do
render_merge_request(merge_request, action: :diffs_metadata)
end
it 'merge_request_diffs/diffs_batch.json' do
render_merge_request(merge_request, action: :diffs_batch, page: 1, per_page: 30)
end
private
def render_merge_request(merge_request, view: 'inline', **extra_params)
get :show, params: {
def render_merge_request(merge_request, action: :show, view: 'inline', **extra_params)
get action, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.to_param,
......
/**
* This helper module contains the API expectation of the diff output HTML.
*
* This helps simulate what third-party HTML scrapers, such as Sourcegraph,
* should be looking for.
*/
export const getDiffCodePart = (codeElement) => {
const el = codeElement.closest('[data-interop-type]');
return el.dataset.interopType === 'old' ? 'base' : 'head';
};
export const getCodeElementFromLineNumber = (codeView, line, part) => {
const type = part === 'base' ? 'old' : 'new';
const el = codeView.querySelector(`[data-interop-${type}-line="${line}"]`);
return el ? el.querySelector('span.line') : null;
};
export const getLineNumberFromCodeElement = (codeElement) => {
const el = codeElement.closest('[data-interop-line]');
return parseInt(el.dataset.interopLine || '', 10);
};
import { waitFor } from '@testing-library/dom';
import { TEST_HOST } from 'helpers/test_constants';
import initDiffsApp from '~/diffs';
import { createStore } from '~/mr_notes/stores';
import {
getDiffCodePart,
getLineNumberFromCodeElement,
getCodeElementFromLineNumber,
} from './diffs_interopability_api';
jest.mock('~/vue_shared/mixins/gl_feature_flags_mixin', () => () => ({
inject: {
glFeatures: {
from: 'window.gon.features',
default: () => global.window.gon?.features,
},
},
}));
const TEST_PROJECT_PATH = 'gitlab-org/gitlab-test';
const TEST_BASE_URL = `/${TEST_PROJECT_PATH}/-/merge_requests/1/`;
const TEST_DIFF_FILE = 'files/js/commit.coffee';
const EXPECT_INLINE = [
['head', 1],
['head', 2],
['head', 3],
['base', 4],
['head', 4],
null,
['base', 6],
['head', 6],
null,
];
const EXPECT_PARALLEL_LEFT_SIDE = [
['base', 1],
['base', 2],
['base', 3],
['base', 4],
null,
['base', 6],
null,
];
const EXPECT_PARALLEL_RIGHT_SIDE = [
['head', 1],
['head', 2],
['head', 3],
['head', 4],
null,
['head', 6],
null,
];
const startDiffsApp = () => {
const el = document.createElement('div');
el.id = 'js-diffs-app';
document.body.appendChild(el);
Object.assign(el.dataset, {
endpoint: TEST_BASE_URL,
endpointMetadata: `${TEST_BASE_URL}diffs_metadata.json`,
endpointBatch: `${TEST_BASE_URL}diffs_batch.json`,
projectPath: TEST_PROJECT_PATH,
helpPagePath: '/help',
currentUserData: 'null',
changesEmptyStateIllustration: '',
isFluidLayout: 'false',
dismissEndpoint: '',
showSuggestPopover: 'false',
showWhitespaceDefault: 'true',
viewDiffsFileByFile: 'false',
defaultSuggestionCommitMessage: 'Lorem ipsum',
});
const store = createStore();
const vm = initDiffsApp(store);
store.dispatch('setActiveTab', 'diffs');
return vm;
};
describe('diffs third party interoperability', () => {
let vm;
afterEach(() => {
vm.$destroy();
document.body.innerHTML = '';
});
const tryOrErrorMessage = (fn) => (...args) => {
try {
return fn(...args);
} catch (e) {
return e.message;
}
};
const findDiffFile = () => document.querySelector(`.diff-file[data-path="${TEST_DIFF_FILE}"]`);
const hasLines = (sel = 'tr.line_holder') => findDiffFile().querySelectorAll(sel).length > 0;
const findLineElements = (sel = 'tr.line_holder') =>
Array.from(findDiffFile().querySelectorAll(sel));
const findCodeElements = (lines, sel = 'td.line_content') => {
return lines.map((x) => x.querySelector(`${sel} span.line`));
};
const getCodeElementsInteropModel = (codeElements) =>
codeElements.map(
(x) =>
x && [
tryOrErrorMessage(getDiffCodePart)(x),
tryOrErrorMessage(getLineNumberFromCodeElement)(x),
],
);
describe.each`
desc | unifiedDiffComponents | view | rowSelector | codeSelector | expectation
${'inline view'} | ${false} | ${'inline'} | ${'tr.line_holder'} | ${'td.line_content'} | ${EXPECT_INLINE}
${'parallel view left side'} | ${false} | ${'parallel'} | ${'tr.line_holder'} | ${'td.line_content.left-side'} | ${EXPECT_PARALLEL_LEFT_SIDE}
${'parallel view right side'} | ${false} | ${'parallel'} | ${'tr.line_holder'} | ${'td.line_content.right-side'} | ${EXPECT_PARALLEL_RIGHT_SIDE}
${'inline view'} | ${true} | ${'inline'} | ${'.diff-tr.line_holder'} | ${'.diff-td.line_content'} | ${EXPECT_INLINE}
${'parallel view left side'} | ${true} | ${'parallel'} | ${'.diff-tr.line_holder'} | ${'.diff-td.line_content.left-side'} | ${EXPECT_PARALLEL_LEFT_SIDE}
${'parallel view right side'} | ${true} | ${'parallel'} | ${'.diff-tr.line_holder'} | ${'.diff-td.line_content.right-side'} | ${EXPECT_PARALLEL_RIGHT_SIDE}
`(
'$desc (unifiedDiffComponents=$unifiedDiffComponents)',
({ unifiedDiffComponents, view, rowSelector, codeSelector, expectation }) => {
beforeEach(async () => {
global.jsdom.reconfigure({
url: `${TEST_HOST}/${TEST_BASE_URL}/diffs?view=${view}`,
});
window.gon.features = { unifiedDiffComponents };
vm = startDiffsApp();
await waitFor(() => expect(hasLines(rowSelector)).toBe(true));
});
it('should match diff model', () => {
const lines = findLineElements(rowSelector);
const codes = findCodeElements(lines, codeSelector);
expect(getCodeElementsInteropModel(codes)).toEqual(expectation);
});
it.each`
lineNumber | part | expectedText
${4} | ${'base'} | ${'new CommitFile(this)'}
${4} | ${'head'} | ${'new CommitFile(@)'}
${2} | ${'base'} | ${'constructor: ->'}
${2} | ${'head'} | ${'constructor: ->'}
`(
'should find code element lineNumber=$lineNumber part=$part',
({ lineNumber, part, expectedText }) => {
const codeElement = getCodeElementFromLineNumber(findDiffFile(), lineNumber, part);
expect(codeElement.textContent.trim()).toBe(expectedText);
},
);
},
);
});
......@@ -40,6 +40,12 @@ export const getMergeRequestVersions = factory.json(() =>
export const getRepositoryFiles = factory.json(() =>
require('test_fixtures/projects_json/files.json'),
);
export const getDiffsMetadata = factory.json(() =>
require('test_fixtures/merge_request_diffs/diffs_metadata.json'),
);
export const getDiffsBatch = factory.json(() =>
require('test_fixtures/merge_request_diffs/diffs_batch.json'),
);
export const getPipelinesEmptyResponse = factory.json(() =>
require('test_fixtures/projects_json/pipelines_empty.json'),
);
......
import { getDiffsMetadata, getDiffsBatch } from 'test_helpers/fixtures';
import { withValues } from 'test_helpers/utils/obj';
export default (server) => {
server.get('/:namespace/:project/-/merge_requests/:mrid/diffs_metadata.json', () => {
return getDiffsMetadata();
});
server.get('/:namespace/:project/-/merge_requests/:mrid/diffs_batch.json', () => {
const { pagination, ...result } = getDiffsBatch();
return {
...result,
pagination: withValues(pagination, {
current_page: null,
next_page: null,
total_pages: 1,
next_page_href: null,
}),
};
});
};
......@@ -5,6 +5,7 @@ export default (server) => {
require('./projects'),
require('./repository'),
require('./ci'),
require('./diffs'),
require('./404'),
].forEach(({ default: setup }) => {
setup(server);
......
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20210413132500_reschedule_artifact_expiry_backfill_again.rb')
RSpec.describe RescheduleArtifactExpiryBackfillAgain, :migration do
let(:migration_class) { Gitlab::BackgroundMigration::BackfillArtifactExpiryDate }
let(:migration_name) { migration_class.to_s.demodulize }
before do
table(:namespaces).create!(id: 123, name: 'test_namespace', path: 'test_namespace')
table(:projects).create!(id: 123, name: 'sample_project', path: 'sample_project', namespace_id: 123)
end
it 'correctly schedules background migrations' do
first_artifact = create_artifact(job_id: 0, expire_at: nil, created_at: Date.new(2020, 06, 21))
second_artifact = create_artifact(job_id: 1, expire_at: nil, created_at: Date.new(2020, 06, 21))
create_artifact(job_id: 2, expire_at: Date.yesterday, created_at: Date.new(2020, 06, 21))
create_artifact(job_id: 3, expire_at: nil, created_at: Date.new(2020, 06, 23))
Sidekiq::Testing.fake! do
freeze_time do
migrate!
expect(BackgroundMigrationWorker.jobs.size).to eq(1)
expect(migration_name).to be_scheduled_migration_with_multiple_args(first_artifact.id, second_artifact.id)
end
end
end
private
def create_artifact(params)
table(:ci_builds).create!(id: params[:job_id], project_id: 123)
table(:ci_job_artifacts).create!(project_id: 123, file_type: 1, **params)
end
end
......@@ -12429,10 +12429,10 @@ webidl-conversions@^6.1.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
webpack-bundle-analyzer@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz#74013106e7e2b07cbd64f3a5ae847f7e814802c7"
integrity sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g==
webpack-bundle-analyzer@^4.4.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz#c71fb2eaffc10a4754d7303b224adb2342069da1"
integrity sha512-j5m7WgytCkiVBoOGavzNokBOqxe6Mma13X1asfVYtKWM3wxBiRRu1u1iG0Iol5+qp9WgyhkMmBAcvjEfJ2bdDw==
dependencies:
acorn "^8.0.4"
acorn-walk "^8.0.0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment