Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6ba372cf11
commit
250c4c2596
28 changed files with 401 additions and 126 deletions
|
@ -15,7 +15,9 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
selectedRadio: this.$options.ALL_PROJECTS,
|
||||
selectedRadio: !this.inputAttrs.value
|
||||
? this.$options.ALL_PROJECTS
|
||||
: this.$options.SELECTED_PROJECTS,
|
||||
selectedProjects: [],
|
||||
};
|
||||
},
|
||||
|
@ -28,6 +30,13 @@ export default {
|
|||
? null
|
||||
: this.selectedProjects.map((project) => project.id).join(',');
|
||||
},
|
||||
initialProjectIds() {
|
||||
if (!this.inputAttrs.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.inputAttrs.value.split(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleTokenSelectorFocus() {
|
||||
|
@ -50,7 +59,11 @@ export default {
|
|||
__('Selected projects')
|
||||
}}</gl-form-radio>
|
||||
<input :id="inputAttrs.id" type="hidden" :name="inputAttrs.name" :value="hiddenInputValue" />
|
||||
<projects-token-selector v-model="selectedProjects" @focus="handleTokenSelectorFocus" />
|
||||
<projects-token-selector
|
||||
v-model="selectedProjects"
|
||||
:initial-project-ids="initialProjectIds"
|
||||
@focus="handleTokenSelectorFocus"
|
||||
/>
|
||||
</gl-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -8,12 +8,13 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import produce from 'immer';
|
||||
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
|
||||
import getProjectsQuery from '../graphql/queries/get_projects.query.graphql';
|
||||
|
||||
const DEBOUNCE_DELAY = 250;
|
||||
const PROJECTS_PER_PAGE = 20;
|
||||
const GRAPHQL_ENTITY_TYPE = 'Project';
|
||||
|
||||
export default {
|
||||
name: 'ProjectsTokenSelector',
|
||||
|
@ -32,6 +33,10 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
initialProjectIds: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
projects: {
|
||||
|
@ -46,10 +51,7 @@ export default {
|
|||
},
|
||||
update({ projects }) {
|
||||
return {
|
||||
list: projects.nodes.map((project) => ({
|
||||
...project,
|
||||
id: getIdFromGraphQLId(project.id),
|
||||
})),
|
||||
list: this.formatProjectNodes(projects),
|
||||
pageInfo: projects.pageInfo,
|
||||
};
|
||||
},
|
||||
|
@ -58,6 +60,21 @@ export default {
|
|||
this.isSearching = false;
|
||||
},
|
||||
},
|
||||
initialProjects: {
|
||||
query: getProjectsQuery,
|
||||
variables() {
|
||||
return {
|
||||
ids: this.initialProjectIds.map((id) => convertToGraphQLId(GRAPHQL_ENTITY_TYPE, id)),
|
||||
};
|
||||
},
|
||||
manual: true,
|
||||
skip() {
|
||||
return !this.initialProjectIds.length;
|
||||
},
|
||||
result({ data: { projects } }) {
|
||||
this.$emit('input', this.formatProjectNodes(projects));
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -71,6 +88,12 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
formatProjectNodes(projects) {
|
||||
return projects.nodes.map((project) => ({
|
||||
...project,
|
||||
id: getIdFromGraphQLId(project.id),
|
||||
}));
|
||||
},
|
||||
handleSearch(query) {
|
||||
this.isSearching = true;
|
||||
this.searchQuery = query;
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
|
||||
|
||||
query getProjects($search: String!, $after: String = "", $first: Int!) {
|
||||
query getProjects(
|
||||
$search: String = ""
|
||||
$after: String = ""
|
||||
$first: Int = null
|
||||
$ids: [ID!] = null
|
||||
) {
|
||||
projects(
|
||||
search: $search
|
||||
after: $after
|
||||
first: $first
|
||||
ids: $ids
|
||||
membership: true
|
||||
searchNamespaces: true
|
||||
sort: "UPDATED_ASC"
|
||||
|
|
|
@ -10,6 +10,7 @@ const getInputAttrs = (el) => {
|
|||
return {
|
||||
id: input.id,
|
||||
name: input.name,
|
||||
value: input.value,
|
||||
placeholder: input.placeholder,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -63,7 +63,7 @@ export default {
|
|||
|
||||
<span class="text-truncate w-100">{{ job.name ? job.name : job.id }}</span>
|
||||
|
||||
<gl-icon v-if="job.retried" name="retry" class="js-retry-icon" />
|
||||
<gl-icon v-if="job.retried" name="retry" />
|
||||
</gl-link>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -70,7 +70,6 @@ export default {
|
|||
},
|
||||
{
|
||||
key: 'actions',
|
||||
label: '',
|
||||
thClass: DEFAULT_TH_CLASSES,
|
||||
tdClass: DEFAULT_TD_CLASS,
|
||||
columnClass: 'gl-w-20p',
|
||||
|
@ -190,6 +189,7 @@ export default {
|
|||
fixed
|
||||
>
|
||||
<template #head(actions)>
|
||||
<span class="gl-display-block gl-lg-display-none!">{{ s__('Pipeline|Actions') }}</span>
|
||||
<slot name="table-header-actions"></slot>
|
||||
</template>
|
||||
|
||||
|
@ -202,11 +202,7 @@ export default {
|
|||
</template>
|
||||
|
||||
<template #cell(pipeline)="{ item }">
|
||||
<pipeline-url
|
||||
class="gl-text-truncate"
|
||||
:pipeline="item"
|
||||
:pipeline-schedule-url="pipelineScheduleUrl"
|
||||
/>
|
||||
<pipeline-url :pipeline="item" :pipeline-schedule-url="pipelineScheduleUrl" />
|
||||
</template>
|
||||
|
||||
<template #cell(triggerer)="{ item }">
|
||||
|
|
27
app/graphql/resolvers/group_packages_resolver.rb
Normal file
27
app/graphql/resolvers/group_packages_resolver.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class GroupPackagesResolver < BaseResolver
|
||||
type Types::Packages::PackageType.connection_type, null: true
|
||||
|
||||
def ready?(**args)
|
||||
context[self.class] ||= { executions: 0 }
|
||||
context[self.class][:executions] += 1
|
||||
raise GraphQL::ExecutionError, "Packages can be requested only for one group at a time" if context[self.class][:executions] > 1
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def resolve(**args)
|
||||
return unless packages_available?
|
||||
|
||||
::Packages::GroupPackagesFinder.new(current_user, object).execute
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def packages_available?
|
||||
::Gitlab.config.packages.enabled
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class PackagesResolver < BaseResolver
|
||||
class ProjectPackagesResolver < BaseResolver
|
||||
type Types::Packages::PackageType.connection_type, null: true
|
||||
|
||||
def resolve(**args)
|
|
@ -95,6 +95,10 @@ module Types
|
|||
field :container_repositories_count, GraphQL::INT_TYPE, null: false,
|
||||
description: 'Number of container repositories in the group.'
|
||||
|
||||
field :packages,
|
||||
description: 'Packages of the group.',
|
||||
resolver: Resolvers::GroupPackagesResolver
|
||||
|
||||
def label(title:)
|
||||
BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args|
|
||||
LabelsFinder
|
||||
|
|
|
@ -181,7 +181,7 @@ module Types
|
|||
|
||||
field :packages,
|
||||
description: 'Packages of the project.',
|
||||
resolver: Resolvers::PackagesResolver
|
||||
resolver: Resolvers::ProjectPackagesResolver
|
||||
|
||||
field :pipelines,
|
||||
null: true,
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
= form_for @hook, as: :hook, url: admin_hook_path do |f|
|
||||
= render partial: 'form', locals: { form: f, hook: @hook }
|
||||
.form-actions
|
||||
%span>= f.submit _('Save changes'), class: 'btn gl-button btn-success gl-mr-3'
|
||||
%span>= f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mr-3'
|
||||
= render 'shared/web_hooks/test_button', hook: @hook
|
||||
= link_to _('Delete'), admin_hook_path(@hook), method: :delete, class: 'btn gl-button btn-danger float-right', data: { confirm: _('Are you sure?') }
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
.col-lg-8.gl-mb-3
|
||||
= form_for @hook, as: :hook, url: admin_hooks_path do |f|
|
||||
= render partial: 'form', locals: { form: f, hook: @hook }
|
||||
= f.submit _('Add system hook'), class: 'btn gl-button btn-success'
|
||||
= f.submit _('Add system hook'), class: 'btn gl-button btn-confirm'
|
||||
|
||||
= render 'shared/web_hooks/index', hooks: @hooks, hook_class: @hook.class
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add package list to group graphql type
|
||||
merge_request: 54672
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/btn-confirm-admin-hooks.yml
Normal file
5
changelogs/unreleased/btn-confirm-admin-hooks.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move to btn-confirm from btn-success in admin/hooks directory
|
||||
merge_request: 55272
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
|
@ -2215,6 +2215,7 @@ Autogenerated return type of GitlabSubscriptionActivate.
|
|||
| `milestones` | MilestoneConnection | Milestones of the group. |
|
||||
| `name` | String! | Name of the namespace. |
|
||||
| `packageSettings` | PackageSettings | The package settings for the namespace. |
|
||||
| `packages` | PackageConnection | Packages of the group. |
|
||||
| `parent` | Group | Parent group. |
|
||||
| `path` | String! | Path of the namespace. |
|
||||
| `projectCreationLevel` | String | The permission level required to create projects in the group. |
|
||||
|
|
|
@ -22283,6 +22283,9 @@ msgstr ""
|
|||
msgid "Pipelines|parent"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Actions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Branch name"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -14,11 +14,11 @@ module QA
|
|||
end
|
||||
|
||||
def has_registry_repository?(name)
|
||||
find('a[data-testid="details-link"]', text: name)
|
||||
find_element(:registry_image_content, text: name)
|
||||
end
|
||||
|
||||
def click_on_image(name)
|
||||
find('a[data-testid="details-link"]', text: name).click
|
||||
click_element(:registry_image_content, text: name)
|
||||
end
|
||||
|
||||
def has_tag?(tag_name)
|
||||
|
@ -30,7 +30,7 @@ module QA
|
|||
end
|
||||
|
||||
def click_delete
|
||||
find('[data-testid="single-delete-button"]').click
|
||||
click_element(:tag_delete_button)
|
||||
find_button('Confirm').click
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,12 +6,13 @@ import ProjectsTokenSelector from '~/access_tokens/components/projects_token_sel
|
|||
describe('ProjectsField', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
const createComponent = ({ inputAttrsValue = '' } = {}) => {
|
||||
wrapper = mount(ProjectsField, {
|
||||
propsData: {
|
||||
inputAttrs: {
|
||||
id: 'projects',
|
||||
name: 'projects',
|
||||
value: inputAttrsValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -24,39 +25,63 @@ describe('ProjectsField', () => {
|
|||
const findProjectsTokenSelector = () => wrapper.findComponent(ProjectsTokenSelector);
|
||||
const findHiddenInput = () => wrapper.find('input[type="hidden"]');
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
it('renders label and sub-label', () => {
|
||||
createComponent();
|
||||
|
||||
expect(queryByText('Projects')).not.toBe(null);
|
||||
expect(queryByText('Set access permissions for this token.')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('renders "All projects" radio selected by default', () => {
|
||||
const allProjectsRadio = findAllProjectsRadio();
|
||||
describe('when `inputAttrs.value` is empty', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
expect(allProjectsRadio).not.toBe(null);
|
||||
expect(allProjectsRadio.checked).toBe(true);
|
||||
it('renders "All projects" radio as checked', () => {
|
||||
expect(findAllProjectsRadio().checked).toBe(true);
|
||||
});
|
||||
|
||||
it('renders "Selected projects" radio as unchecked', () => {
|
||||
expect(findSelectedProjectsRadio().checked).toBe(false);
|
||||
});
|
||||
|
||||
it('sets `projects-token-selector` `initialProjectIds` prop to an empty array', () => {
|
||||
expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders "Selected projects" radio unchecked by default', () => {
|
||||
const selectedProjectsRadio = findSelectedProjectsRadio();
|
||||
describe('when `inputAttrs.value` is a comma separated list of project IDs', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ inputAttrsValue: '1,2' });
|
||||
});
|
||||
|
||||
expect(selectedProjectsRadio).not.toBe(null);
|
||||
expect(selectedProjectsRadio.checked).toBe(false);
|
||||
it('renders "All projects" radio as unchecked', () => {
|
||||
expect(findAllProjectsRadio().checked).toBe(false);
|
||||
});
|
||||
|
||||
it('renders "Selected projects" radio as checked', () => {
|
||||
expect(findSelectedProjectsRadio().checked).toBe(true);
|
||||
});
|
||||
|
||||
it('sets `projects-token-selector` `initialProjectIds` prop to an array of project IDs', () => {
|
||||
expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual(['1', '2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders `projects-token-selector` component', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findProjectsTokenSelector().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders hidden input with correct `name` and `id` attributes', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findHiddenInput().attributes()).toEqual(
|
||||
expect.objectContaining({
|
||||
id: 'projects',
|
||||
|
@ -67,6 +92,8 @@ describe('ProjectsField', () => {
|
|||
|
||||
describe('when `projects-token-selector` is focused', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
||||
findProjectsTokenSelector().vm.$emit('focus');
|
||||
});
|
||||
|
||||
|
|
|
@ -44,10 +44,15 @@ describe('ProjectsTokenSelector', () => {
|
|||
let wrapper;
|
||||
|
||||
let resolveGetProjectsQuery;
|
||||
let resolveGetInitialProjectsQuery;
|
||||
const getProjectsQueryRequestHandler = jest.fn(
|
||||
() =>
|
||||
({ ids }) =>
|
||||
new Promise((resolve) => {
|
||||
resolveGetProjectsQuery = resolve;
|
||||
if (ids) {
|
||||
resolveGetInitialProjectsQuery = resolve;
|
||||
} else {
|
||||
resolveGetProjectsQuery = resolve;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -63,6 +68,7 @@ describe('ProjectsTokenSelector', () => {
|
|||
apolloProvider,
|
||||
propsData: {
|
||||
selectedProjects: [],
|
||||
initialProjectIds: [],
|
||||
...propsData,
|
||||
},
|
||||
stubs: ['gl-intersection-observer'],
|
||||
|
@ -156,6 +162,7 @@ describe('ProjectsTokenSelector', () => {
|
|||
search: searchTerm,
|
||||
after: null,
|
||||
first: 20,
|
||||
ids: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -181,6 +188,7 @@ describe('ProjectsTokenSelector', () => {
|
|||
after: pageInfo.endCursor,
|
||||
first: 20,
|
||||
search: '',
|
||||
ids: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -221,4 +229,41 @@ describe('ProjectsTokenSelector', () => {
|
|||
expect(wrapper.emitted('focus')[0]).toEqual([event]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `initialProjectIds` is an empty array', () => {
|
||||
it('does not request initial projects', async () => {
|
||||
await createComponent();
|
||||
|
||||
expect(getProjectsQueryRequestHandler).toHaveBeenCalledTimes(1);
|
||||
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
ids: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `initialProjectIds` is an array of project IDs', () => {
|
||||
it('requests those projects and emits `input` event with result', async () => {
|
||||
await createComponent({
|
||||
propsData: {
|
||||
initialProjectIds: [getIdFromGraphQLId(project1.id), getIdFromGraphQLId(project2.id)],
|
||||
},
|
||||
});
|
||||
|
||||
resolveGetInitialProjectsQuery(getProjectsQueryResponse);
|
||||
await waitForPromises();
|
||||
|
||||
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith({
|
||||
after: '',
|
||||
first: null,
|
||||
search: '',
|
||||
ids: [project1.id, project2.id],
|
||||
});
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual([
|
||||
{ ...project1, id: getIdFromGraphQLId(project1.id) },
|
||||
{ ...project2, id: getIdFromGraphQLId(project2.id) },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,6 +38,7 @@ describe('access tokens', () => {
|
|||
input.setAttribute('name', 'foo-bar');
|
||||
input.setAttribute('id', 'foo-bar');
|
||||
input.setAttribute('placeholder', 'Foo bar');
|
||||
input.setAttribute('value', '1,2');
|
||||
|
||||
mountEl.appendChild(input);
|
||||
|
||||
|
@ -58,6 +59,7 @@ describe('access tokens', () => {
|
|||
expect(component.props('inputAttrs')).toEqual({
|
||||
name: 'foo-bar',
|
||||
id: 'foo-bar',
|
||||
value: '1,2',
|
||||
placeholder: 'Foo bar',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import component from '~/jobs/components/jobs_container.vue';
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import JobsContainer from '~/jobs/components/jobs_container.vue';
|
||||
|
||||
describe('Jobs List block', () => {
|
||||
const Component = Vue.extend(component);
|
||||
let vm;
|
||||
let wrapper;
|
||||
|
||||
const retried = {
|
||||
status: {
|
||||
|
@ -52,80 +52,96 @@ describe('Jobs List block', () => {
|
|||
tooltip: 'build - passed',
|
||||
};
|
||||
|
||||
const findAllJobs = () => wrapper.findAllComponents(GlLink);
|
||||
const findJob = () => findAllJobs().at(0);
|
||||
|
||||
const findArrowIcon = () => wrapper.findByTestId('arrow-right-icon');
|
||||
const findRetryIcon = () => wrapper.findByTestId('retry-icon');
|
||||
|
||||
const createComponent = (props) => {
|
||||
wrapper = extendedWrapper(
|
||||
mount(JobsContainer, {
|
||||
propsData: {
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders list of jobs', () => {
|
||||
vm = mountComponent(Component, {
|
||||
it('renders a list of jobs', () => {
|
||||
createComponent({
|
||||
jobs: [job, retried, active],
|
||||
jobId: 12313,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelectorAll('a').length).toEqual(3);
|
||||
expect(findAllJobs()).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('renders arrow right when job id matches `jobId`', () => {
|
||||
vm = mountComponent(Component, {
|
||||
it('renders the arrow right icon when job id matches `jobId`', () => {
|
||||
createComponent({
|
||||
jobs: [active],
|
||||
jobId: active.id,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('a .js-arrow-right')).not.toBeNull();
|
||||
expect(findArrowIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render arrow right when job is not active', () => {
|
||||
vm = mountComponent(Component, {
|
||||
it('does not render the arrow right icon when the job is not active', () => {
|
||||
createComponent({
|
||||
jobs: [job],
|
||||
jobId: active.id,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('a .js-arrow-right')).toBeNull();
|
||||
expect(findArrowIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders job name when present', () => {
|
||||
vm = mountComponent(Component, {
|
||||
it('renders the job name when present', () => {
|
||||
createComponent({
|
||||
jobs: [job],
|
||||
jobId: active.id,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('a').textContent.trim()).toContain(job.name);
|
||||
expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(job.id);
|
||||
expect(findJob().text()).toBe(job.name);
|
||||
expect(findJob().text()).not.toContain(job.id);
|
||||
});
|
||||
|
||||
it('renders job id when job name is not available', () => {
|
||||
vm = mountComponent(Component, {
|
||||
createComponent({
|
||||
jobs: [retried],
|
||||
jobId: active.id,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('a').textContent.trim()).toContain(retried.id);
|
||||
expect(findJob().text()).toBe(retried.id.toString());
|
||||
});
|
||||
|
||||
it('links to the job page', () => {
|
||||
vm = mountComponent(Component, {
|
||||
createComponent({
|
||||
jobs: [job],
|
||||
jobId: active.id,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.status.details_path);
|
||||
expect(findJob().attributes('href')).toBe(job.status.details_path);
|
||||
});
|
||||
|
||||
it('renders retry icon when job was retried', () => {
|
||||
vm = mountComponent(Component, {
|
||||
createComponent({
|
||||
jobs: [retried],
|
||||
jobId: active.id,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('.js-retry-icon')).not.toBeNull();
|
||||
expect(findRetryIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render retry icon when job was not retried', () => {
|
||||
vm = mountComponent(Component, {
|
||||
createComponent({
|
||||
jobs: [job],
|
||||
jobId: active.id,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('.js-retry-icon')).toBeNull();
|
||||
expect(findRetryIcon().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -117,9 +117,7 @@ describe('Pipelines Table', () => {
|
|||
expect(findCommitTh().text()).toBe('Commit');
|
||||
expect(findStagesTh().text()).toBe('Stages');
|
||||
expect(findTimeAgoTh().text()).toBe('Duration');
|
||||
|
||||
// last column should have no text in th
|
||||
expect(findActionsTh().text()).toBe('');
|
||||
expect(findActionsTh().text()).toBe('Actions');
|
||||
});
|
||||
|
||||
it('should display a table row', () => {
|
||||
|
|
18
spec/graphql/resolvers/group_packages_resolver_spec.rb
Normal file
18
spec/graphql/resolvers/group_packages_resolver_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::GroupPackagesResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be(:project) { create(:project, :public, group: group) }
|
||||
let_it_be(:package) { create(:package, project: project) }
|
||||
|
||||
describe '#resolve' do
|
||||
subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: group) }
|
||||
|
||||
it { is_expected.to contain_exactly(package) }
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::PackagesResolver do
|
||||
RSpec.describe Resolvers::ProjectPackagesResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
|
@ -18,6 +18,7 @@ RSpec.describe GitlabSchema.types['Group'] do
|
|||
two_factor_grace_period auto_devops_enabled emails_disabled
|
||||
mentions_disabled parent boards milestones group_members
|
||||
merge_requests container_repositories container_repositories_count
|
||||
packages
|
||||
]
|
||||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
|
|
78
spec/requests/api/graphql/group/packages_spec.rb
Normal file
78
spec/requests/api/graphql/group/packages_spec.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'getting a package list for a group' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:resource) { create(:group, :private) }
|
||||
let_it_be(:group_two) { create(:group, :private) }
|
||||
let_it_be(:project) { create(:project, :repository, group: resource) }
|
||||
let_it_be(:another_project) { create(:project, :repository, group: resource) }
|
||||
let_it_be(:group_two_project) { create(:project, :repository, group: group_two) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
|
||||
let_it_be(:package) { create(:package, project: project) }
|
||||
let_it_be(:npm_package) { create(:npm_package, project: group_two_project) }
|
||||
let_it_be(:maven_package) { create(:maven_package, project: project) }
|
||||
let_it_be(:debian_package) { create(:debian_package, project: another_project) }
|
||||
let_it_be(:composer_package) { create(:composer_package, project: another_project) }
|
||||
let_it_be(:composer_metadatum) do
|
||||
create(:composer_metadatum, package: composer_package,
|
||||
target_sha: 'afdeh',
|
||||
composer_json: { name: 'x', type: 'y', license: 'z', version: 1 })
|
||||
end
|
||||
|
||||
let(:package_names) { graphql_data_at(:group, :packages, :nodes, :name) }
|
||||
let(:target_shas) { graphql_data_at(:group, :packages, :nodes, :metadata, :target_sha) }
|
||||
let(:packages) { graphql_data_at(:group, :packages, :nodes) }
|
||||
|
||||
let(:fields) do
|
||||
<<~QUERY
|
||||
nodes {
|
||||
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
|
||||
metadata { #{query_graphql_fragment('ComposerMetadata')} }
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
let(:query) do
|
||||
graphql_query_for(
|
||||
'group',
|
||||
{ 'fullPath' => resource.full_path },
|
||||
query_graphql_field('packages', {}, fields)
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'group and project packages query'
|
||||
|
||||
context 'with a batched query' do
|
||||
let(:batch_query) do
|
||||
<<~QUERY
|
||||
{
|
||||
a: group(fullPath: "#{resource.full_path}") { packages { nodes { name } } }
|
||||
b: group(fullPath: "#{group_two.full_path}") { packages { nodes { name } } }
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
let(:a_packages_names) { graphql_data_at(:a, :packages, :nodes, :name) }
|
||||
|
||||
before do
|
||||
resource.add_reporter(current_user)
|
||||
group_two.add_reporter(current_user)
|
||||
post_graphql(batch_query, current_user: current_user)
|
||||
end
|
||||
|
||||
it 'returns an error for the second group and data for the first' do
|
||||
expect(a_packages_names).to contain_exactly(
|
||||
package.name,
|
||||
maven_package.name,
|
||||
debian_package.name,
|
||||
composer_package.name
|
||||
)
|
||||
expect_graphql_errors_to_include [/Packages can be requested only for one group at a time/]
|
||||
expect(graphql_data_at(:b, :packages)).to be(nil)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,28 +5,28 @@ require 'spec_helper'
|
|||
RSpec.describe 'getting a package list for a project' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:resource) { create(:project, :repository) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
|
||||
let_it_be(:package) { create(:package, project: project) }
|
||||
let_it_be(:maven_package) { create(:maven_package, project: project) }
|
||||
let_it_be(:debian_package) { create(:debian_package, project: project) }
|
||||
let_it_be(:composer_package) { create(:composer_package, project: project) }
|
||||
let_it_be(:package) { create(:package, project: resource) }
|
||||
let_it_be(:maven_package) { create(:maven_package, project: resource) }
|
||||
let_it_be(:debian_package) { create(:debian_package, project: resource) }
|
||||
let_it_be(:composer_package) { create(:composer_package, project: resource) }
|
||||
let_it_be(:composer_metadatum) do
|
||||
create(:composer_metadatum, package: composer_package,
|
||||
target_sha: 'afdeh',
|
||||
composer_json: { name: 'x', type: 'y', license: 'z', version: 1 })
|
||||
end
|
||||
|
||||
let(:package_names) { graphql_data_at(:project, :packages, :edges, :node, :name) }
|
||||
let(:package_names) { graphql_data_at(:project, :packages, :nodes, :name) }
|
||||
let(:target_shas) { graphql_data_at(:project, :packages, :nodes, :metadata, :target_sha) }
|
||||
let(:packages) { graphql_data_at(:project, :packages, :nodes) }
|
||||
|
||||
let(:fields) do
|
||||
<<~QUERY
|
||||
edges {
|
||||
node {
|
||||
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
|
||||
metadata { #{query_graphql_fragment('ComposerMetadata')} }
|
||||
}
|
||||
nodes {
|
||||
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
|
||||
metadata { #{query_graphql_fragment('ComposerMetadata')} }
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
@ -34,55 +34,10 @@ RSpec.describe 'getting a package list for a project' do
|
|||
let(:query) do
|
||||
graphql_query_for(
|
||||
'project',
|
||||
{ 'fullPath' => project.full_path },
|
||||
{ 'fullPath' => resource.full_path },
|
||||
query_graphql_field('packages', {}, fields)
|
||||
)
|
||||
end
|
||||
|
||||
context 'when user has access to the project' do
|
||||
before do
|
||||
project.add_reporter(current_user)
|
||||
post_graphql(query, current_user: current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns packages successfully' do
|
||||
expect(package_names).to contain_exactly(
|
||||
package.name,
|
||||
maven_package.name,
|
||||
debian_package.name,
|
||||
composer_package.name
|
||||
)
|
||||
end
|
||||
|
||||
it 'deals with metadata' do
|
||||
target_shas = graphql_data_at(:project, :packages, :edges, :node, :metadata, :target_sha)
|
||||
expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user does not have access to the project/packages' do
|
||||
before do
|
||||
post_graphql(query, current_user: current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns nil' do
|
||||
expect(graphql_data['project']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is not authenticated' do
|
||||
before do
|
||||
post_graphql(query)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns nil' do
|
||||
expect(graphql_data['project']).to be_nil
|
||||
end
|
||||
end
|
||||
it_behaves_like 'group and project packages query'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'group and project packages query' do
|
||||
include GraphqlHelpers
|
||||
|
||||
context 'when user has access to the resource' do
|
||||
before do
|
||||
resource.add_reporter(current_user)
|
||||
post_graphql(query, current_user: current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns packages successfully' do
|
||||
expect(package_names).to contain_exactly(
|
||||
package.name,
|
||||
maven_package.name,
|
||||
debian_package.name,
|
||||
composer_package.name
|
||||
)
|
||||
end
|
||||
|
||||
it 'deals with metadata' do
|
||||
expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user does not have access to the resource' do
|
||||
before do
|
||||
post_graphql(query, current_user: current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns nil' do
|
||||
expect(packages).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is not authenticated' do
|
||||
before do
|
||||
post_graphql(query)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns nil' do
|
||||
expect(packages).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue