Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b71a496c7a
commit
00a8c64ffd
|
@ -45,19 +45,16 @@ export const annotationsYAxis = {
|
|||
* Fetched list of annotations are parsed into a
|
||||
* format the eCharts accepts to draw markLines
|
||||
*
|
||||
* If Annotation is a single line, the `from` property
|
||||
* has a value and the `to` is null. Because annotations
|
||||
* only supports lines the from value does not exist yet.
|
||||
* If Annotation is a single line, the `starting_at` property
|
||||
* has a value and the `ending_at` is null. Because annotations
|
||||
* only supports lines the `ending_at` value does not exist yet.
|
||||
*
|
||||
*
|
||||
* @param {Object} annotation object
|
||||
* @returns {Object} markLine object
|
||||
*/
|
||||
export const parseAnnotations = ({
|
||||
from: annotationFrom = '',
|
||||
color = colorValues.primaryColor,
|
||||
}) => ({
|
||||
xAxis: annotationFrom,
|
||||
export const parseAnnotations = ({ starting_at = '', color = colorValues.primaryColor }) => ({
|
||||
xAxis: starting_at,
|
||||
lineStyle: {
|
||||
color,
|
||||
},
|
||||
|
@ -105,7 +102,7 @@ export const generateAnnotationsSeries = ({ deployments = [], annotations = [] }
|
|||
const annotationsData = annotations.map(annotation => {
|
||||
return {
|
||||
name: 'annotations',
|
||||
value: [annotation.from, annotationsYAxisCoords.pos],
|
||||
value: [annotation.starting_at, annotationsYAxisCoords.pos],
|
||||
// style options
|
||||
symbol: 'none',
|
||||
// metadata that are accessible in `formatTooltipText` method
|
||||
|
|
|
@ -4,8 +4,8 @@ query getAnnotations($projectPath: ID!) {
|
|||
annotations: nodes {
|
||||
id
|
||||
description
|
||||
from
|
||||
to
|
||||
starting_at
|
||||
ending_at
|
||||
panelId
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,11 @@ import CommitsList from '~/commits';
|
|||
import GpgBadges from '~/gpg_badges';
|
||||
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
||||
|
||||
import mountCommits from '~/projects/commits';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new CommitsList(document.querySelector('.js-project-commits-show').dataset.commitsLimit); // eslint-disable-line no-new
|
||||
new ShortcutsNavigation(); // eslint-disable-line no-new
|
||||
GpgBadges.fetch();
|
||||
mountCommits(document.getElementById('js-author-dropdown'));
|
||||
});
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
<script>
|
||||
import { debounce } from 'lodash';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import {
|
||||
GlNewDropdown,
|
||||
GlNewDropdownHeader,
|
||||
GlNewDropdownItem,
|
||||
GlSearchBoxByType,
|
||||
GlNewDropdownDivider,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { redirectTo } from '~/lib/utils/url_utility';
|
||||
import { urlParamsToObject } from '~/lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
const tooltipMessage = __('Searching by both author and message is currently not supported.');
|
||||
|
||||
export default {
|
||||
name: 'AuthorSelect',
|
||||
components: {
|
||||
GlNewDropdown,
|
||||
GlNewDropdownHeader,
|
||||
GlNewDropdownItem,
|
||||
GlSearchBoxByType,
|
||||
GlNewDropdownDivider,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
projectCommitsEl: {
|
||||
type: HTMLDivElement,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasSearchParam: false,
|
||||
searchTerm: '',
|
||||
authorInput: '',
|
||||
currentAuthor: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['commitsPath', 'commitsAuthors']),
|
||||
dropdownText() {
|
||||
return this.currentAuthor || __('Author');
|
||||
},
|
||||
tooltipTitle() {
|
||||
return this.hasSearchParam && tooltipMessage;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchAuthors();
|
||||
const params = urlParamsToObject(window.location.search);
|
||||
const { search: searchParam, author: authorParam } = params;
|
||||
const commitsSearchInput = this.projectCommitsEl.querySelector('#commits-search');
|
||||
|
||||
if (authorParam) {
|
||||
commitsSearchInput.setAttribute('disabled', true);
|
||||
commitsSearchInput.setAttribute('data-toggle', 'tooltip');
|
||||
commitsSearchInput.setAttribute('title', tooltipMessage);
|
||||
this.currentAuthor = authorParam;
|
||||
}
|
||||
|
||||
if (searchParam) {
|
||||
this.hasSearchParam = true;
|
||||
}
|
||||
|
||||
commitsSearchInput.addEventListener(
|
||||
'keyup',
|
||||
debounce(event => this.setSearchParam(event.target.value), 500), // keyup & time is to match effect of "filter by commit message"
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchAuthors']),
|
||||
selectAuthor(author) {
|
||||
const { name: user } = author || {};
|
||||
|
||||
// Follow up issue "Remove usage of $.fadeIn from the codebase"
|
||||
// > https://gitlab.com/gitlab-org/gitlab/-/issues/214395
|
||||
|
||||
// Follow up issue "Refactor commit list to a Vue Component"
|
||||
// To resolving mixing Vue + Vanilla JS
|
||||
// > https://gitlab.com/gitlab-org/gitlab/-/issues/214010
|
||||
const commitListElement = this.projectCommitsEl.querySelector('#commits-list');
|
||||
|
||||
// To mimick effect of "filter by commit message"
|
||||
commitListElement.style.opacity = 0.5;
|
||||
commitListElement.style.transition = 'opacity 200ms';
|
||||
|
||||
if (!user) {
|
||||
return redirectTo(this.commitsPath);
|
||||
}
|
||||
|
||||
return redirectTo(`${this.commitsPath}?author=${user}`);
|
||||
},
|
||||
searchAuthors() {
|
||||
this.fetchAuthors(this.authorInput);
|
||||
},
|
||||
setSearchParam(value) {
|
||||
this.hasSearchParam = Boolean(value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="dropdownContainer" v-gl-tooltip :title="tooltipTitle" :disabled="!hasSearchParam">
|
||||
<gl-new-dropdown
|
||||
:text="dropdownText"
|
||||
:disabled="hasSearchParam"
|
||||
class="gl-dropdown w-100 mt-2 mt-sm-0"
|
||||
>
|
||||
<gl-new-dropdown-header>
|
||||
{{ __('Search by author') }}
|
||||
</gl-new-dropdown-header>
|
||||
<gl-new-dropdown-divider />
|
||||
<gl-search-box-by-type
|
||||
v-model.trim="authorInput"
|
||||
class="m-2"
|
||||
:placeholder="__('Search')"
|
||||
@input="searchAuthors"
|
||||
/>
|
||||
<gl-new-dropdown-item :is-checked="!currentAuthor" @click="selectAuthor(null)">
|
||||
{{ __('Any Author') }}
|
||||
</gl-new-dropdown-item>
|
||||
<gl-new-dropdown-divider />
|
||||
<gl-new-dropdown-item
|
||||
v-for="author in commitsAuthors"
|
||||
:key="author.id"
|
||||
:is-checked="author.name === currentAuthor"
|
||||
:avatar-url="author.avatar_url"
|
||||
:secondary-text="author.username"
|
||||
@click="selectAuthor(author)"
|
||||
>
|
||||
{{ author.name }}
|
||||
</gl-new-dropdown-item>
|
||||
</gl-new-dropdown>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,26 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import AuthorSelectApp from './components/author_select.vue';
|
||||
import store from './store';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default el => {
|
||||
if (!el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
store.dispatch('setInitialData', el.dataset);
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
store,
|
||||
render(h) {
|
||||
return h(AuthorSelectApp, {
|
||||
props: {
|
||||
projectCommitsEl: document.querySelector('.js-project-commits-show'),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
import * as types from './mutation_types';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
setInitialData({ commit }, data) {
|
||||
commit(types.SET_INITIAL_DATA, data);
|
||||
},
|
||||
receiveAuthorsSuccess({ commit }, authors) {
|
||||
commit(types.COMMITS_AUTHORS, authors);
|
||||
},
|
||||
receiveAuthorsError() {
|
||||
createFlash(__('An error occurred fetching the project authors.'));
|
||||
},
|
||||
fetchAuthors({ dispatch, state }, author = null) {
|
||||
const { projectId } = state;
|
||||
const path = '/autocomplete/users.json';
|
||||
|
||||
return axios
|
||||
.get(path, {
|
||||
params: {
|
||||
project_id: projectId,
|
||||
active: true,
|
||||
search: author,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => dispatch('receiveAuthorsSuccess', data))
|
||||
.catch(() => dispatch('receiveAuthorsError'));
|
||||
},
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import actions from './actions';
|
||||
import mutations from './mutations';
|
||||
import state from './state';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export const createStore = () => ({
|
||||
actions,
|
||||
mutations,
|
||||
state: state(),
|
||||
});
|
||||
|
||||
export default new Vuex.Store(createStore());
|
|
@ -0,0 +1,2 @@
|
|||
export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
|
||||
export const COMMITS_AUTHORS = 'COMMITS_AUTHORS';
|
|
@ -0,0 +1,10 @@
|
|||
import * as types from './mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SET_INITIAL_DATA](state, data) {
|
||||
Object.assign(state, data);
|
||||
},
|
||||
[types.COMMITS_AUTHORS](state, data) {
|
||||
state.commitsAuthors = data;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export default () => ({
|
||||
commitsPath: null,
|
||||
projectId: null,
|
||||
commitsAuthors: [],
|
||||
});
|
|
@ -317,7 +317,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
// Temporary fix to ensure tick is aligned
|
||||
// Follow up Issue to remove after the GlNewDropdownItem component is fixed
|
||||
// > https://gitlab.com/gitlab-org/gitlab/-/issues/213948
|
||||
li:not(.gl-new-dropdown-item) .dropdown-item {
|
||||
@include dropdown-link;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
|
||||
%ul.breadcrumb.repo-breadcrumb
|
||||
= commits_breadcrumbs
|
||||
.tree-controls.d-none.d-sm-none.d-md-block<
|
||||
#js-author-dropdown{ data: { 'commits_path': project_commits_path(@project), 'project_id': @project.id } }
|
||||
.tree-controls.d-none.d-sm-none.d-md-block
|
||||
- if @merge_request.present?
|
||||
.control
|
||||
= link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add ability to filter commits by author
|
||||
merge_request: 28509
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add rake task to update x509 signatures
|
||||
merge_request: 28406
|
||||
author: Roger Meier
|
||||
type: added
|
|
@ -147,7 +147,12 @@ released.
|
|||
#### TLS enabled
|
||||
|
||||
NOTE: **Note**
|
||||
This requires GitLab Runner 11.11 or higher.
|
||||
Requires GitLab Runner 11.11 or later, but is not supported if GitLab
|
||||
Runner is installed using the [Helm
|
||||
chart](https://docs.gitlab.com/runner/install/kubernetes.html). See the
|
||||
[related
|
||||
issue](https://gitlab.com/gitlab-org/charts/gitlab-runner/issues/83) for
|
||||
details.
|
||||
|
||||
The Docker daemon supports connection over TLS and it's done by default
|
||||
for Docker 19.03.8 or higher. This is the **suggested** way to use the
|
||||
|
|
|
@ -1264,7 +1264,9 @@ osx job:
|
|||
|
||||
`allow_failure` allows a job to fail without impacting the rest of the CI
|
||||
suite.
|
||||
The default value is `false`, except for [manual](#whenmanual) jobs.
|
||||
The default value is `false`, except for [manual](#whenmanual) jobs using the
|
||||
`when: manual` syntax, unless using [`rules:`](#rules) syntax, where all jobs
|
||||
default to false, *including* `when: manual` jobs.
|
||||
|
||||
When enabled and the job fails, the job will show an orange warning in the UI.
|
||||
However, the logical flow of the pipeline will consider the job a
|
||||
|
@ -1379,14 +1381,17 @@ manual action by clicking a _play_ button.
|
|||
|
||||
When a pipeline is blocked, it will not be merged if Merge When Pipeline Succeeds
|
||||
is set. Blocked pipelines also do have a special status, called _manual_.
|
||||
Manual actions are non-blocking by default. If you want to make manual action
|
||||
blocking, it is necessary to add `allow_failure: false` to the job's definition
|
||||
in `.gitlab-ci.yml`.
|
||||
When the `when:manual` syntax is used, manual actions are non-blocking by
|
||||
default. If you want to make manual action blocking, it is necessary to add
|
||||
`allow_failure: false` to the job's definition in `.gitlab-ci.yml`.
|
||||
|
||||
Optional manual actions have `allow_failure: true` set by default and their
|
||||
Statuses do not contribute to the overall pipeline status. So, if a manual
|
||||
action fails, the pipeline will eventually succeed.
|
||||
|
||||
NOTE: **Note:**
|
||||
When using [`rules:`](#rules), `allow_failure` defaults to `false`, including for manual jobs.
|
||||
|
||||
Manual actions are considered to be write actions, so permissions for
|
||||
[protected branches](../../user/project/protected_branches.md) are used when
|
||||
a user wants to trigger an action. In other words, in order to trigger a manual
|
||||
|
|
|
@ -36,3 +36,4 @@ The following are available Rake tasks:
|
|||
| [Uploads sanitize](../administration/raketasks/uploads/sanitize.md) | Remove EXIF data from images uploaded to earlier versions of GitLab. |
|
||||
| [User management](user_management.md) | Perform user management tasks. |
|
||||
| [Webhooks administration](web_hooks.md) | Maintain project Webhooks. |
|
||||
| [X509 signatures](x509_signatures.md) | Update x509 commit signatures, useful if certificate store has changed. |
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# X509 signatures
|
||||
|
||||
When [signing commits with x509](../user/project/repository/x509_signed_commits/index.md)
|
||||
the trust anchor might change and the signatures stored within the database have
|
||||
to be updated.
|
||||
|
||||
## Update all x509 signatures
|
||||
|
||||
This task loops through all X509 signed commits and updates their verification
|
||||
based on current certificate store.
|
||||
|
||||
**Omnibus Installation**
|
||||
|
||||
```shell
|
||||
sudo gitlab-rake gitlab:x509:update_signatures
|
||||
```
|
||||
|
||||
**Source Installation**
|
||||
|
||||
```shell
|
||||
sudo -u git -H bundle exec rake gitlab:x509:update_signatures RAILS_ENV=production
|
||||
```
|
|
@ -8,8 +8,8 @@ GitLab offers tiers of features. Your subscription determines which tier you hav
|
|||
|
||||
GitLab provides special subscriptions to participants in the [GitLab Education Program](https://about.gitlab.com/solutions/education/) and [GitLab Open Source Program](https://about.gitlab.com/solutions/open-source/). For details on obtaining and renewing these subscriptions, see:
|
||||
|
||||
- [GitLab Education Program subscriptions](#gitlab-education-program-subscriptions)
|
||||
- [GitLab Open Source Program subscriptions](#gitlab-open-source-program-subscriptions)
|
||||
- [GitLab for Education subscriptions](#gitlab-for-education-subscriptions)
|
||||
- [GitLab for Open Source subscriptions](#gitlab-for-open-source-subscriptions)
|
||||
|
||||
## Choosing a GitLab subscription
|
||||
|
||||
|
@ -493,9 +493,9 @@ Learn more about:
|
|||
- The tiers of [GitLab Support](https://about.gitlab.com/support/).
|
||||
- [Submit a request via the Support Portal](https://support.gitlab.com/hc/en-us/requests/new).
|
||||
|
||||
## GitLab Education Program subscriptions
|
||||
## GitLab for Education subscriptions
|
||||
|
||||
To renew a [GitLab Education Program](https://about.gitlab.com/solutions/education/) subscription, send an email to `education@gitlab.com` with the following information:
|
||||
To renew a [GitLab for Education](https://about.gitlab.com/solutions/education/) subscription, send an email to `education@gitlab.com` with the following information:
|
||||
|
||||
1. The number of seats for the renewal. You can add seats if needed.
|
||||
1. The use case for the license. Specifically, we need verification that the use meets the conditions of the [End User License Agreement](https://about.gitlab.com/terms/#edu-oss). Note that university infrastructure operations and information technology operations don't fall within the stated terms of the Education Program. For details, see the [Education FAQ](https://about.gitlab.com/solutions/education/#FAQ).
|
||||
|
@ -503,9 +503,9 @@ To renew a [GitLab Education Program](https://about.gitlab.com/solutions/educati
|
|||
|
||||
After we receive the above information, we will process the request and return a renewal quote for signature. Please allow a minimum of 2 business days for return. Email us at `education@gitlab.com` with any questions.
|
||||
|
||||
## GitLab Open Source Program subscriptions
|
||||
## GitLab for Open Source subscriptions
|
||||
|
||||
All requests for our GitLab Open Source program, including subscription renewals, must be made by using the [Open Source Program](https://about.gitlab.com/solutions/open-source/program/) application process. If you have any questions, send an email to `opensource@gitlab.com` for assistance.
|
||||
All [GitLab for Open Source](https://about.gitlab.com/solutions/open-source/program/) requests, including subscription renewals, must be made by using the application process. If you have any questions, send an email to `opensource@gitlab.com` for assistance.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
|
|
|
@ -53,8 +53,10 @@ also requires the GitLab Runner 11.5 or later. For earlier versions, use the
|
|||
|
||||
This example shows how to run Code Quality on your code by using GitLab CI/CD and Docker.
|
||||
|
||||
First, you need GitLab Runner with
|
||||
[docker-in-docker executor](../../../ci/docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
|
||||
First, you need GitLab Runner with:
|
||||
|
||||
- The [docker-in-docker executor](../../../ci/docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
|
||||
- Enough disk space to handle generated Code Quality files. For example on the [GitLab project](https://gitlab.com/gitlab-org/gitlab) the files are approximately 7 GB.
|
||||
|
||||
Once you set up the Runner, include the CodeQuality template in your CI config:
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
module Gitlab
|
||||
class GitAccessWiki < GitAccess
|
||||
prepend_if_ee('EE::Gitlab::GitAccessWiki') # rubocop: disable Cop/InjectEnterpriseEditionModule
|
||||
|
||||
ERROR_MESSAGES = {
|
||||
read_only: "You can't push code to a read-only GitLab instance.",
|
||||
write_to_wiki: "You are not allowed to write to this project's wiki."
|
||||
|
@ -33,10 +31,8 @@ module Gitlab
|
|||
ERROR_MESSAGES[:read_only]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository
|
||||
project.wiki.repository
|
||||
def container
|
||||
project.wiki
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
require 'logger'
|
||||
|
||||
desc "GitLab | X509 | Update signatures when certificate store has changed"
|
||||
namespace :gitlab do
|
||||
namespace :x509 do
|
||||
task update_signatures: :environment do
|
||||
update_certificates
|
||||
end
|
||||
|
||||
def update_certificates
|
||||
logger = Logger.new(STDOUT)
|
||||
|
||||
unless X509CommitSignature.exists?
|
||||
logger.info("Unable to find any x509 commit signatures. Exiting.")
|
||||
return
|
||||
end
|
||||
|
||||
logger.info("Start to update x509 commit signatures")
|
||||
|
||||
X509CommitSignature.find_each do |sig|
|
||||
sig.x509_commit&.update_signature!(sig)
|
||||
end
|
||||
|
||||
logger.info("End update x509 commit signatures")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1289,6 +1289,9 @@ msgstr ""
|
|||
msgid "Added at"
|
||||
msgstr ""
|
||||
|
||||
msgid "Added for this merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "Added in this version"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1855,6 +1858,9 @@ msgstr ""
|
|||
msgid "An error occurred fetching the dropdown data."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred fetching the project authors."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred previewing the blob"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2173,6 +2179,9 @@ msgstr ""
|
|||
msgid "Any"
|
||||
msgstr ""
|
||||
|
||||
msgid "Any Author"
|
||||
msgstr ""
|
||||
|
||||
msgid "Any Label"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14195,6 +14204,9 @@ msgstr ""
|
|||
msgid "OutdatedBrowser|You can provide feedback %{feedback_link_start}on this issue%{feedback_link_end} or via your usual support channels."
|
||||
msgstr ""
|
||||
|
||||
msgid "Overridden"
|
||||
msgstr ""
|
||||
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
|
||||
|
@ -17691,6 +17703,9 @@ msgstr ""
|
|||
msgid "Search branches and tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search by author"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search files"
|
||||
msgstr ""
|
||||
|
||||
|
@ -17851,6 +17866,9 @@ msgid_plural "SearchResults|wiki results"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Searching by both author and message is currently not supported."
|
||||
msgstr ""
|
||||
|
||||
msgid "Seat Link"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -213,23 +213,23 @@ export const deploymentData = [
|
|||
export const annotationsData = [
|
||||
{
|
||||
id: 'gid://gitlab/Metrics::Dashboard::Annotation/1',
|
||||
from: '2020-04-01T12:51:58.373Z',
|
||||
to: null,
|
||||
starting_at: '2020-04-01T12:51:58.373Z',
|
||||
ending_at: null,
|
||||
panelId: null,
|
||||
description: 'This is a test annotation',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/Metrics::Dashboard::Annotation/2',
|
||||
description: 'test annotation 2',
|
||||
from: '2020-04-02T12:51:58.373Z',
|
||||
to: null,
|
||||
starting_at: '2020-04-02T12:51:58.373Z',
|
||||
ending_at: null,
|
||||
panelId: null,
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/Metrics::Dashboard::Annotation/3',
|
||||
description: 'test annotation 3',
|
||||
from: '2020-04-04T12:51:58.373Z',
|
||||
to: null,
|
||||
starting_at: '2020-04-04T12:51:58.373Z',
|
||||
ending_at: null,
|
||||
panelId: null,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import * as urlUtility from '~/lib/utils/url_utility';
|
||||
import AuthorSelect from '~/projects/commits/components/author_select.vue';
|
||||
import { createStore } from '~/projects/commits/store';
|
||||
import {
|
||||
GlNewDropdown,
|
||||
GlNewDropdownHeader,
|
||||
GlSearchBoxByType,
|
||||
GlNewDropdownItem,
|
||||
} from '@gitlab/ui';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
const commitsPath = 'author/search/url';
|
||||
const currentAuthor = 'lorem';
|
||||
const authors = [
|
||||
{
|
||||
id: 1,
|
||||
name: currentAuthor,
|
||||
username: 'ipsum',
|
||||
avatar_url: 'some/url',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'lorem2',
|
||||
username: 'ipsum2',
|
||||
avatar_url: 'some/url/2',
|
||||
},
|
||||
];
|
||||
|
||||
describe('Author Select', () => {
|
||||
let store;
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
setFixtures(`
|
||||
<div class="js-project-commits-show">
|
||||
<input id="commits-search" type="text" />
|
||||
<div id="commits-list"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
wrapper = shallowMount(AuthorSelect, {
|
||||
localVue,
|
||||
store: new Vuex.Store(store),
|
||||
propsData: {
|
||||
projectCommitsEl: document.querySelector('.js-project-commits-show'),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
store.actions.fetchAuthors = jest.fn();
|
||||
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findDropdownContainer = () => wrapper.find({ ref: 'dropdownContainer' });
|
||||
const findDropdown = () => wrapper.find(GlNewDropdown);
|
||||
const findDropdownHeader = () => wrapper.find(GlNewDropdownHeader);
|
||||
const findSearchBox = () => wrapper.find(GlSearchBoxByType);
|
||||
const findDropdownItems = () => wrapper.findAll(GlNewDropdownItem);
|
||||
|
||||
describe('user is searching via "filter by commit message"', () => {
|
||||
it('disables dropdown container', () => {
|
||||
wrapper.setData({ hasSearchParam: true });
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findDropdownContainer().attributes('disabled')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('has correct tooltip message', () => {
|
||||
wrapper.setData({ hasSearchParam: true });
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findDropdownContainer().attributes('title')).toBe(
|
||||
'Searching by both author and message is currently not supported.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('disables dropdown', () => {
|
||||
wrapper.setData({ hasSearchParam: false });
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findDropdown().attributes('disabled')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('hasSearchParam if user types a truthy string', () => {
|
||||
wrapper.vm.setSearchParam('false');
|
||||
|
||||
expect(wrapper.vm.hasSearchParam).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('dropdown', () => {
|
||||
it('displays correct default text', () => {
|
||||
expect(findDropdown().attributes('text')).toBe('Author');
|
||||
});
|
||||
|
||||
it('displays the current selected author', () => {
|
||||
wrapper.setData({ currentAuthor });
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findDropdown().attributes('text')).toBe(currentAuthor);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays correct header text', () => {
|
||||
expect(findDropdownHeader().text()).toBe('Search by author');
|
||||
});
|
||||
|
||||
it('does not have popover text by default', () => {
|
||||
expect(wrapper.attributes('title')).not.toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('dropdown search box', () => {
|
||||
it('has correct placeholder', () => {
|
||||
expect(findSearchBox().attributes('placeholder')).toBe('Search');
|
||||
});
|
||||
|
||||
it('fetch authors on input change', () => {
|
||||
const authorName = 'lorem';
|
||||
findSearchBox().vm.$emit('input', authorName);
|
||||
|
||||
expect(store.actions.fetchAuthors).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
authorName,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dropdown list', () => {
|
||||
beforeEach(() => {
|
||||
store.state.commitsAuthors = authors;
|
||||
store.state.commitsPath = commitsPath;
|
||||
});
|
||||
|
||||
it('has a "Any Author" as the first list item', () => {
|
||||
expect(
|
||||
findDropdownItems()
|
||||
.at(0)
|
||||
.text(),
|
||||
).toBe('Any Author');
|
||||
});
|
||||
|
||||
it('displays the project authors', () => {
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findDropdownItems()).toHaveLength(authors.length + 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('has the correct props', () => {
|
||||
const [{ avatar_url, username }] = authors;
|
||||
const result = {
|
||||
avatarUrl: avatar_url,
|
||||
secondaryText: username,
|
||||
isChecked: true,
|
||||
};
|
||||
|
||||
wrapper.setData({ currentAuthor });
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(
|
||||
findDropdownItems()
|
||||
.at(1)
|
||||
.props(),
|
||||
).toEqual(expect.objectContaining(result));
|
||||
});
|
||||
});
|
||||
|
||||
it("display the author's name", () => {
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(
|
||||
findDropdownItems()
|
||||
.at(1)
|
||||
.text(),
|
||||
).toBe(currentAuthor);
|
||||
});
|
||||
});
|
||||
|
||||
it('passes selected author to redirectPath', () => {
|
||||
const redirectToUrl = `${commitsPath}?author=${currentAuthor}`;
|
||||
const spy = jest.spyOn(urlUtility, 'redirectTo');
|
||||
spy.mockImplementation(() => 'mock');
|
||||
|
||||
findDropdownItems()
|
||||
.at(1)
|
||||
.vm.$emit('click');
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(redirectToUrl);
|
||||
});
|
||||
|
||||
it('does not pass any author to redirectPath', () => {
|
||||
const redirectToUrl = commitsPath;
|
||||
const spy = jest.spyOn(urlUtility, 'redirectTo');
|
||||
spy.mockImplementation();
|
||||
|
||||
findDropdownItems()
|
||||
.at(0)
|
||||
.vm.$emit('click');
|
||||
expect(spy).toHaveBeenCalledWith(redirectToUrl);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,69 @@
|
|||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import * as types from '~/projects/commits/store/mutation_types';
|
||||
import testAction from 'helpers/vuex_action_helper';
|
||||
import actions from '~/projects/commits/store/actions';
|
||||
import createState from '~/projects/commits/store/state';
|
||||
import createFlash from '~/flash';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
describe('Project commits actions', () => {
|
||||
let state;
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createState();
|
||||
mock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('setInitialData', () => {
|
||||
it(`commits ${types.SET_INITIAL_DATA}`, () =>
|
||||
testAction(actions.setInitialData, undefined, state, [{ type: types.SET_INITIAL_DATA }]));
|
||||
});
|
||||
|
||||
describe('receiveAuthorsSuccess', () => {
|
||||
it(`commits ${types.COMMITS_AUTHORS}`, () =>
|
||||
testAction(actions.receiveAuthorsSuccess, undefined, state, [
|
||||
{ type: types.COMMITS_AUTHORS },
|
||||
]));
|
||||
});
|
||||
|
||||
describe('shows a flash message when there is an error', () => {
|
||||
it('creates a flash', () => {
|
||||
const mockDispatchContext = { dispatch: () => {}, commit: () => {}, state };
|
||||
actions.receiveAuthorsError(mockDispatchContext);
|
||||
|
||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||
expect(createFlash).toHaveBeenCalledWith('An error occurred fetching the project authors.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAuthors', () => {
|
||||
it('dispatches request/receive', () => {
|
||||
const path = '/autocomplete/users.json';
|
||||
state.projectId = '8';
|
||||
const data = [{ id: 1 }];
|
||||
|
||||
mock.onGet(path).replyOnce(200, data);
|
||||
testAction(
|
||||
actions.fetchAuthors,
|
||||
null,
|
||||
state,
|
||||
[],
|
||||
[{ type: 'receiveAuthorsSuccess', payload: data }],
|
||||
);
|
||||
});
|
||||
|
||||
it('dispatches request/receive on error', () => {
|
||||
const path = '/autocomplete/users.json';
|
||||
mock.onGet(path).replyOnce(500);
|
||||
|
||||
testAction(actions.fetchAuthors, null, state, [], [{ type: 'receiveAuthorsError' }]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
import * as types from '~/projects/commits/store/mutation_types';
|
||||
import mutations from '~/projects/commits/store/mutations';
|
||||
import createState from '~/projects/commits/store/state';
|
||||
|
||||
describe('Project commits mutations', () => {
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createState();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
state = null;
|
||||
});
|
||||
|
||||
describe(`${types.SET_INITIAL_DATA}`, () => {
|
||||
it('sets initial data', () => {
|
||||
state.commitsPath = null;
|
||||
state.projectId = null;
|
||||
state.commitsAuthors = [];
|
||||
|
||||
const data = {
|
||||
commitsPath: 'some/path',
|
||||
projectId: '8',
|
||||
};
|
||||
|
||||
mutations[types.SET_INITIAL_DATA](state, data);
|
||||
|
||||
expect(state).toEqual(expect.objectContaining(data));
|
||||
});
|
||||
});
|
||||
|
||||
describe(`${types.COMMITS_AUTHORS}`, () => {
|
||||
it('sets commitsAuthors', () => {
|
||||
const authors = [{ id: 1 }, { id: 2 }];
|
||||
state.commitsAuthors = [];
|
||||
|
||||
mutations[types.COMMITS_AUTHORS](state, authors);
|
||||
|
||||
expect(state.commitsAuthors).toEqual(authors);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -21,7 +21,7 @@ describe Gitlab::ImportExport::JSON::NdjsonReader do
|
|||
describe '#exist?' do
|
||||
subject { ndjson_reader.exist? }
|
||||
|
||||
context 'given valid dir_path', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/213843' do
|
||||
context 'given valid dir_path' do
|
||||
let(:dir_path) { fixture }
|
||||
|
||||
it { is_expected.to be true }
|
||||
|
|
|
@ -15,15 +15,17 @@ module ImportExport
|
|||
export_path = [prefix, 'spec', 'fixtures', 'lib', 'gitlab', 'import_export', name].compact
|
||||
export_path = File.join(*export_path)
|
||||
|
||||
extract_archive(export_path, 'tree.tar.gz')
|
||||
if File.exist?(File.join(export_path, 'tree.tar.gz'))
|
||||
extract_archive(export_path, 'tree.tar.gz')
|
||||
end
|
||||
|
||||
allow_any_instance_of(Gitlab::ImportExport).to receive(:export_path) { export_path }
|
||||
end
|
||||
|
||||
def extract_archive(path, archive)
|
||||
if File.exist?(File.join(path, archive))
|
||||
system("cd #{path}; tar xzvf #{archive} &> /dev/null")
|
||||
end
|
||||
output, exit_status = Gitlab::Popen.popen(["cd #{path}; tar xzf #{archive}"])
|
||||
|
||||
raise "Failed to extract archive. Output: #{output}" unless exit_status.zero?
|
||||
end
|
||||
|
||||
def cleanup_artifacts_from_extract_archive(name, prefix = nil)
|
||||
|
@ -31,7 +33,7 @@ module ImportExport
|
|||
export_path = File.join(*export_path)
|
||||
|
||||
if File.exist?(File.join(export_path, 'tree.tar.gz'))
|
||||
system("cd #{export_path}; rm -fr tree &> /dev/null")
|
||||
system("cd #{export_path}; rm -fr tree")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rake_helper'
|
||||
|
||||
describe 'gitlab:x509 namespace rake task' do
|
||||
before :all do
|
||||
Rake.application.rake_require 'tasks/gitlab/x509/update'
|
||||
end
|
||||
|
||||
describe 'update_signatures' do
|
||||
subject { run_rake_task('gitlab:x509:update_signatures') }
|
||||
|
||||
let(:project) { create :project, :repository, path: X509Helpers::User1.path }
|
||||
let(:x509_signed_commit) { project.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') }
|
||||
let(:x509_commit) { Gitlab::X509::Commit.new(x509_signed_commit).signature }
|
||||
|
||||
it 'changes from unverified to verified if the certificate store contains the root certificate' do
|
||||
x509_commit
|
||||
|
||||
store = OpenSSL::X509::Store.new
|
||||
certificate = OpenSSL::X509::Certificate.new X509Helpers::User1.trust_cert
|
||||
store.add_cert(certificate)
|
||||
allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
|
||||
|
||||
expect(x509_commit.verification_status).to eq('unverified')
|
||||
expect_any_instance_of(Gitlab::X509::Commit).to receive(:update_signature!).and_call_original
|
||||
|
||||
subject
|
||||
|
||||
x509_commit.reload
|
||||
expect(x509_commit.verification_status).to eq('verified')
|
||||
end
|
||||
|
||||
it 'returns if no signature is available' do
|
||||
expect_any_instance_of(Gitlab::X509::Commit) do |x509_commit|
|
||||
expect(x509_commit).not_to receive(:update_signature!)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue