Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
df8f194428
commit
f8eba0f714
|
|
@ -328,11 +328,21 @@ jest predictive:
|
|||
extends:
|
||||
- jest
|
||||
- .frontend:rules:jest:predictive
|
||||
needs:
|
||||
- !reference [jest, needs]
|
||||
- "detect-tests"
|
||||
script:
|
||||
- if [[ -s "$RSPEC_CHANGED_FILES_PATH" ]] || [[ -s "$RSPEC_MATCHING_JS_FILES_PATH" ]]; then run_timed_command "yarn jest:ci:predictive-without-fixtures"; fi
|
||||
|
||||
jest-with-fixtures predictive:
|
||||
extends:
|
||||
- jest-with-fixtures
|
||||
- .frontend:rules:jest:predictive
|
||||
needs:
|
||||
- !reference [jest-with-fixtures, needs]
|
||||
- "detect-tests"
|
||||
script:
|
||||
- if [[ -s "$RSPEC_CHANGED_FILES_PATH" ]] || [[ -s "$RSPEC_MATCHING_JS_FILES_PATH" ]]; then run_timed_command "yarn jest:ci:predictive"; fi
|
||||
- if [[ -s "$RSPEC_CHANGED_FILES_PATH" ]] || [[ -s "$RSPEC_MATCHING_JS_FILES_PATH" ]]; then run_timed_command "yarn jest:ci:predictive-with-fixtures"; fi
|
||||
|
||||
jest-integration:
|
||||
extends:
|
||||
|
|
|
|||
|
|
@ -25,3 +25,10 @@ export const IID_FAILURE = 'missing_iid';
|
|||
|
||||
export const RETRY_ACTION_TITLE = 'Retry';
|
||||
export const MANUAL_ACTION_TITLE = 'Run';
|
||||
|
||||
/*
|
||||
this poll interval is shared between the graph,
|
||||
pipeline header, jobs tab and failed jobs tab to
|
||||
keep all the data relatively in sync
|
||||
*/
|
||||
export const POLL_INTERVAL = 10000;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
SKIP_RETRY_MODAL_KEY,
|
||||
STAGE_VIEW,
|
||||
VIEW_TYPE_KEY,
|
||||
POLL_INTERVAL,
|
||||
} from './constants';
|
||||
import PipelineGraph from './components/graph_component.vue';
|
||||
import GraphViewSelector from './components/graph_view_selector.vue';
|
||||
|
|
@ -129,7 +130,7 @@ export default {
|
|||
return getQueryHeaders(this.graphqlResourceEtag);
|
||||
},
|
||||
query: getPipelineDetails,
|
||||
pollInterval: 10000,
|
||||
pollInterval: POLL_INTERVAL,
|
||||
variables() {
|
||||
return {
|
||||
projectPath: this.pipelineProjectPath,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
export const DELETE_MODAL_ID = 'pipeline-delete-modal';
|
||||
|
||||
export const POLL_INTERVAL = 10000;
|
||||
|
||||
export const SCHEDULE_SOURCE = 'schedule';
|
||||
export const AUTO_DEVOPS_SOURCE = 'AUTO_DEVOPS_SOURCE';
|
||||
export const DETACHED_EVENT_TYPE = 'DETACHED';
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import cancelPipelineMutation from '../graphql/mutations/cancel_pipeline.mutatio
|
|||
import deletePipelineMutation from '../graphql/mutations/delete_pipeline.mutation.graphql';
|
||||
import retryPipelineMutation from '../graphql/mutations/retry_pipeline.mutation.graphql';
|
||||
import { getQueryHeaders } from '../graph/utils';
|
||||
import { POLL_INTERVAL } from '../graph/constants';
|
||||
import HeaderActions from './components/header_actions.vue';
|
||||
import HeaderBadges from './components/header_badges.vue';
|
||||
import getPipelineQuery from './graphql/queries/get_pipeline_header_data.query.graphql';
|
||||
import { POLL_INTERVAL } from './constants';
|
||||
|
||||
export default {
|
||||
name: 'PipelineHeader',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { createAlert } from '~/alert';
|
||||
import { getQueryHeaders } from '../graph/utils';
|
||||
import { POLL_INTERVAL } from '../graph/constants';
|
||||
import GetFailedJobsQuery from './graphql/queries/get_failed_jobs.query.graphql';
|
||||
import FailedJobsTable from './components/failed_jobs_table.vue';
|
||||
|
||||
|
|
@ -17,10 +19,17 @@ export default {
|
|||
pipelineIid: {
|
||||
default: '',
|
||||
},
|
||||
graphqlResourceEtag: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
failedJobs: {
|
||||
context() {
|
||||
return getQueryHeaders(this.graphqlResourceEtag);
|
||||
},
|
||||
query: GetFailedJobsQuery,
|
||||
pollInterval: POLL_INTERVAL,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.projectPath,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import { __ } from '~/locale';
|
|||
import eventHub from '~/ci/jobs_page/event_hub';
|
||||
import JobsTable from '~/ci/jobs_page/components/jobs_table.vue';
|
||||
import { JOBS_TAB_FIELDS } from '~/ci/jobs_page/constants';
|
||||
import { getQueryHeaders } from '../graph/utils';
|
||||
import { POLL_INTERVAL } from '../graph/constants';
|
||||
import getPipelineJobs from './graphql/queries/get_pipeline_jobs.query.graphql';
|
||||
|
||||
export default {
|
||||
|
|
@ -23,10 +25,17 @@ export default {
|
|||
pipelineIid: {
|
||||
default: '',
|
||||
},
|
||||
graphqlResourceEtag: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
jobs: {
|
||||
context() {
|
||||
return getQueryHeaders(this.graphqlResourceEtag);
|
||||
},
|
||||
query: getPipelineJobs,
|
||||
pollInterval: POLL_INTERVAL,
|
||||
variables() {
|
||||
return {
|
||||
...this.queryVariables,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -61,7 +66,7 @@ export default {
|
|||
},
|
||||
created() {
|
||||
const validatePath = async () => {
|
||||
if (this.isEditingGroup && this.value === this.initialValue) return;
|
||||
if (this.isEditing && this.value === this.initialValue) return;
|
||||
|
||||
this.suggestedPath = '';
|
||||
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ export default {
|
|||
};
|
||||
},
|
||||
isEditing() {
|
||||
return this.initialFormValues[FORM_FIELD_ID];
|
||||
return Boolean(this.initialFormValues[FORM_FIELD_ID]);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -210,6 +210,7 @@ export default {
|
|||
:value="value"
|
||||
:state="validation.state"
|
||||
:base-path="basePath"
|
||||
:is-editing="isEditing"
|
||||
@input="onPathInput($event, input)"
|
||||
@input-suggested-path="input"
|
||||
@blur="blur"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
<script>
|
||||
import { GlSprintf } from '@gitlab/ui';
|
||||
import NewEditForm from '~/groups/components/new_edit_form.vue';
|
||||
import { __ } from '~/locale';
|
||||
import { FORM_FIELD_NAME, FORM_FIELD_PATH, FORM_FIELD_VISIBILITY_LEVEL } from '~/groups/constants';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { VISIBILITY_LEVELS_INTEGER_TO_STRING } from '~/visibility_level/constants';
|
||||
import { visitUrlWithAlerts } from '~/lib/utils/url_utility';
|
||||
import { createAlert } from '~/alert';
|
||||
import FormErrorsAlert from '~/vue_shared/components/form/errors_alert.vue';
|
||||
import groupUpdateMutation from '../graphql/mutations/group_update.mutation.graphql';
|
||||
|
||||
export default {
|
||||
name: 'OrganizationGroupsEditApp',
|
||||
components: { GlSprintf, NewEditForm },
|
||||
components: { GlSprintf, FormErrorsAlert, NewEditForm },
|
||||
i18n: {
|
||||
pageTitle: __('Edit group: %{group_name}'),
|
||||
submitButtonText: __('Save changes'),
|
||||
errorMessage: s__('Groups|An error occurred updating this group. Please try again.'),
|
||||
successMessage: __('Group was successfully updated.'),
|
||||
},
|
||||
inject: [
|
||||
'group',
|
||||
|
|
@ -21,6 +29,57 @@ export default {
|
|||
'pathMaxlength',
|
||||
'pathPattern',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
errors: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async onSubmit({
|
||||
[FORM_FIELD_NAME]: name,
|
||||
[FORM_FIELD_PATH]: path,
|
||||
[FORM_FIELD_VISIBILITY_LEVEL]: visibility,
|
||||
}) {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
const {
|
||||
data: {
|
||||
groupUpdate: { group, errors },
|
||||
},
|
||||
} = await this.$apollo.mutate({
|
||||
mutation: groupUpdateMutation,
|
||||
variables: {
|
||||
input: {
|
||||
fullPath: this.group.fullPath,
|
||||
name,
|
||||
path,
|
||||
visibility: VISIBILITY_LEVELS_INTEGER_TO_STRING[visibility],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
this.errors = errors;
|
||||
this.loading = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
visitUrlWithAlerts(group.organizationEditPath, [
|
||||
{
|
||||
id: 'organization-group-successfully-updated',
|
||||
message: this.$options.i18n.successMessage,
|
||||
variant: 'info',
|
||||
},
|
||||
]);
|
||||
} catch (error) {
|
||||
this.loading = false;
|
||||
createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -31,8 +90,9 @@ export default {
|
|||
<template #group_name>{{ group.fullName }}</template>
|
||||
</gl-sprintf>
|
||||
</h1>
|
||||
<form-errors-alert v-model="errors" :scroll-on-error="true" />
|
||||
<new-edit-form
|
||||
:loading="false"
|
||||
:loading="loading"
|
||||
:base-path="basePath"
|
||||
:path-maxlength="pathMaxlength"
|
||||
:path-pattern="pathPattern"
|
||||
|
|
@ -41,6 +101,7 @@ export default {
|
|||
:available-visibility-levels="availableVisibilityLevels"
|
||||
:restricted-visibility-levels="restrictedVisibilityLevels"
|
||||
:initial-form-values="group"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
mutation groupUpdate($input: GroupUpdateInput!) {
|
||||
groupUpdate(input: $input) {
|
||||
group {
|
||||
id
|
||||
organizationEditPath
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import App from './components/app.vue';
|
||||
|
||||
export const initOrganizationsGroupsEdit = () => {
|
||||
|
|
@ -23,9 +25,14 @@ export const initOrganizationsGroupsEdit = () => {
|
|||
pathPattern,
|
||||
} = convertObjectPropsToCamelCase(JSON.parse(appData), { deep: true });
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'OrganizationGroupsEditRoot',
|
||||
apolloProvider,
|
||||
provide: {
|
||||
group,
|
||||
basePath,
|
||||
|
|
|
|||
|
|
@ -22,9 +22,18 @@ module Mutations
|
|||
argument :math_rendering_limits_enabled, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: copy_field_description(Types::GroupType, :math_rendering_limits_enabled)
|
||||
argument :name, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: copy_field_description(Types::GroupType, :name)
|
||||
argument :path, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: copy_field_description(Types::GroupType, :path)
|
||||
argument :shared_runners_setting, Types::Namespace::SharedRunnersSettingEnum,
|
||||
required: false,
|
||||
description: copy_field_description(Types::GroupType, :shared_runners_setting)
|
||||
argument :visibility, Types::VisibilityLevelsEnum,
|
||||
required: false,
|
||||
description: copy_field_description(Types::GroupType, :visibility)
|
||||
|
||||
def resolve(full_path:, **args)
|
||||
group = authorized_find!(full_path: full_path)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ module Organizations
|
|||
|
||||
def organization_groups_edit_app_data(organization, group)
|
||||
{
|
||||
group: group.slice(:id, :full_name, :name, :visibility_level, :path)
|
||||
group: group.slice(:id, :full_name, :name, :visibility_level, :path, :full_path)
|
||||
}.merge(shared_organization_groups_app_data(organization)).to_json
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2392,6 +2392,8 @@ class Project < ApplicationRecord
|
|||
:started
|
||||
elsif export_file_exists?
|
||||
:finished
|
||||
elsif export_failed?
|
||||
:failed
|
||||
else
|
||||
:none
|
||||
end
|
||||
|
|
@ -2409,6 +2411,12 @@ class Project < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def export_failed?
|
||||
strong_memoize(:export_failed) do
|
||||
::Projects::ExportJobFinder.new(self, { status: :failed }).execute.present?
|
||||
end
|
||||
end
|
||||
|
||||
def regeneration_in_progress?
|
||||
(export_enqueued? || export_in_progress?) && export_file_exists?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ module Projects
|
|||
next if Gitlab::SidekiqStatus.running?(relation_export.jid)
|
||||
next if relation_export.reset.finished?
|
||||
|
||||
relation_export.mark_as_failed("Exausted number of retries to export: #{relation_export.relation}")
|
||||
relation_export.mark_as_failed("Exhausted number of retries to export: #{relation_export.relation}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ class StuckExportJobsWorker
|
|||
)
|
||||
|
||||
completed_jobs.each do |job|
|
||||
# Parallel export job completes and keeps 'started' state because it has
|
||||
# multiple relation exports running in parallel. Don't mark it as failed
|
||||
# until 6 hours mark
|
||||
next if job.relation_exports.any? && job.created_at > EXPORT_JOBS_EXPIRATION.seconds.ago
|
||||
|
||||
job.fail_op
|
||||
end.count
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5223,7 +5223,10 @@ Input type: `GroupUpdateInput`
|
|||
| <a id="mutationgroupupdatelockduofeaturesenabled"></a>`lockDuoFeaturesEnabled` | [`Boolean`](#boolean) | Indicates if the GitLab Duo features enabled setting is enforced for all subgroups. Introduced in GitLab 16.10: **Status**: Experiment. |
|
||||
| <a id="mutationgroupupdatelockmathrenderinglimitsenabled"></a>`lockMathRenderingLimitsEnabled` | [`Boolean`](#boolean) | Indicates if math rendering limits are locked for all descendant groups. |
|
||||
| <a id="mutationgroupupdatemathrenderinglimitsenabled"></a>`mathRenderingLimitsEnabled` | [`Boolean`](#boolean) | Indicates if math rendering limits are used for this group. |
|
||||
| <a id="mutationgroupupdatename"></a>`name` | [`String`](#string) | Name of the namespace. |
|
||||
| <a id="mutationgroupupdatepath"></a>`path` | [`String`](#string) | Path of the namespace. |
|
||||
| <a id="mutationgroupupdatesharedrunnerssetting"></a>`sharedRunnersSetting` | [`SharedRunnersSetting`](#sharedrunnerssetting) | Shared runners availability for the namespace and its descendants. |
|
||||
| <a id="mutationgroupupdatevisibility"></a>`visibility` | [`VisibilityLevelsEnum`](#visibilitylevelsenum) | Visibility of the namespace. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ The month-over-month comparison of the AI Usage unique users rate gives a more a
|
|||
The baseline for the AI Usage trend is the total number of code contributors, not just users with GitLab Duo seats. This baseline gives a more accurate representation of AI usage by team members.
|
||||
|
||||
NOTE:
|
||||
Usage rate for Code Suggestions is calculated with data starting on 2024-04-04.
|
||||
Usage rate for Code Suggestions is calculated with data starting from GitLab 16.11.
|
||||
For more information, see [epic 12978](https://gitlab.com/groups/gitlab-org/-/epics/12978).
|
||||
|
||||
## Enable or disable overview background aggregation
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ A deploy token is a pair of values:
|
|||
`gitlab+deploy-token-{n}`. You can specify a custom username when you create the deploy token.
|
||||
- **token**: `password` in the HTTP authentication framework.
|
||||
|
||||
Deploy tokens do not support [SSH authentication](../../ssh.md).
|
||||
|
||||
You can use a deploy token for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication)
|
||||
to the following endpoints:
|
||||
|
||||
|
|
|
|||
|
|
@ -25636,6 +25636,9 @@ msgstr ""
|
|||
msgid "GroupsTree|Search by name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Groups|An error occurred updating this group. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Groups|Avatar will be removed. Are you sure?"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
"jest:ci": "jest --config jest.config.js --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js --shard \"${CI_NODE_INDEX:-1}/${CI_NODE_TOTAL:-1}\" --logHeapUsage",
|
||||
"jest:ci:without-fixtures": "jest --config jest.config.js --ci --coverage --testSequencer ./scripts/frontend/fixture_ci_sequencer.js --shard \"${CI_NODE_INDEX:-1}/${CI_NODE_TOTAL:-1}\" --logHeapUsage",
|
||||
"jest:ci:with-fixtures": "JEST_FIXTURE_JOBS_ONLY=1 jest --config jest.config.js --ci --coverage --testSequencer ./scripts/frontend/fixture_ci_sequencer.js --shard \"${CI_NODE_INDEX:-1}/${CI_NODE_TOTAL:-1}\" --logHeapUsage",
|
||||
"jest:ci:predictive": "jest --config jest.config.js --ci --coverage --findRelatedTests $(cat $RSPEC_CHANGED_FILES_PATH) $(cat $RSPEC_MATCHING_JS_FILES_PATH) --passWithNoTests --testSequencer ./scripts/frontend/parallel_ci_sequencer.js --shard \"${CI_NODE_INDEX:-1}/${CI_NODE_TOTAL:-1}\" --logHeapUsage",
|
||||
"jest:ci:predictive-without-fixtures": "jest --config jest.config.js --ci --coverage --findRelatedTests $(cat $RSPEC_CHANGED_FILES_PATH) $(cat $RSPEC_MATCHING_JS_FILES_PATH) --passWithNoTests --testSequencer ./scripts/frontend/fixture_ci_sequencer.js --shard \"${CI_NODE_INDEX:-1}/${CI_NODE_TOTAL:-1}\" --logHeapUsage",
|
||||
"jest:ci:predictive-with-fixtures": "JEST_FIXTURE_JOBS_ONLY=1 jest --config jest.config.js --ci --coverage --findRelatedTests $(cat $RSPEC_CHANGED_FILES_PATH) $(cat $RSPEC_MATCHING_JS_FILES_PATH) --passWithNoTests --testSequencer ./scripts/frontend/fixture_ci_sequencer.js --shard \"${CI_NODE_INDEX:-1}/${CI_NODE_TOTAL:-1}\" --logHeapUsage",
|
||||
"jest:contract": "PACT_DO_NOT_TRACK=true jest --config jest.config.contract.js --runInBand",
|
||||
"jest:integration": "jest --config jest.config.integration.js",
|
||||
"jest:scripts": "jest --config jest.config.scripts.js",
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ RSpec.describe PersonalAccessTokensFinder, :enable_admin_mode, feature_category:
|
|||
with_them do
|
||||
let(:params) { { sort: sort } }
|
||||
|
||||
it 'returns ordered tokens' do
|
||||
it 'returns ordered tokens', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/446283' do
|
||||
expect(subject.map(&:id)).to eq(tokens.values_at(*expected_tokens).map(&:id))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { createAlert } from '~/alert';
|
|||
import FailedJobsApp from '~/ci/pipeline_details/jobs/failed_jobs_app.vue';
|
||||
import FailedJobsTable from '~/ci/pipeline_details/jobs/components/failed_jobs_table.vue';
|
||||
import GetFailedJobsQuery from '~/ci/pipeline_details/jobs/graphql/queries/get_failed_jobs.query.graphql';
|
||||
import { POLL_INTERVAL } from '~/ci/pipeline_details/graph/constants';
|
||||
import { mockFailedJobsQueryResponse } from 'jest/ci/pipeline_details/mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
|
@ -27,11 +28,14 @@ describe('Failed Jobs App', () => {
|
|||
return createMockApollo(requestHandlers);
|
||||
};
|
||||
|
||||
const graphqlResourceEtag = '/api/graphql:pipelines/id/1';
|
||||
|
||||
const createComponent = (resolver) => {
|
||||
wrapper = shallowMount(FailedJobsApp, {
|
||||
provide: {
|
||||
fullPath: 'root/ci-project',
|
||||
pipelineIid: 1,
|
||||
graphqlResourceEtag,
|
||||
},
|
||||
apolloProvider: createMockApolloProvider(resolver),
|
||||
});
|
||||
|
|
@ -77,4 +81,18 @@ describe('Failed Jobs App', () => {
|
|||
message: 'There was a problem fetching the failed jobs.',
|
||||
});
|
||||
});
|
||||
|
||||
describe('polling', () => {
|
||||
beforeEach(() => {
|
||||
createComponent(resolverSpy);
|
||||
});
|
||||
|
||||
it('polls for query data', () => {
|
||||
expect(resolverSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
jest.advanceTimersByTime(POLL_INTERVAL);
|
||||
|
||||
expect(resolverSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { createAlert } from '~/alert';
|
|||
import JobsApp from '~/ci/pipeline_details/jobs/jobs_app.vue';
|
||||
import JobsTable from '~/ci/jobs_page/components/jobs_table.vue';
|
||||
import getPipelineJobsQuery from '~/ci/pipeline_details/jobs/graphql/queries/get_pipeline_jobs.query.graphql';
|
||||
import { POLL_INTERVAL } from '~/ci/pipeline_details/graph/constants';
|
||||
import { mockPipelineJobsQueryResponse } from '../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
|
@ -31,11 +32,14 @@ describe('Jobs app', () => {
|
|||
return createMockApollo(requestHandlers);
|
||||
};
|
||||
|
||||
const graphqlResourceEtag = '/api/graphql:pipelines/id/1';
|
||||
|
||||
const createComponent = (resolver) => {
|
||||
wrapper = shallowMount(JobsApp, {
|
||||
provide: {
|
||||
projectPath: 'root/ci-project',
|
||||
pipelineIid: 1,
|
||||
graphqlResourceEtag,
|
||||
},
|
||||
apolloProvider: createMockApolloProvider(resolver),
|
||||
});
|
||||
|
|
@ -124,4 +128,18 @@ describe('Jobs app', () => {
|
|||
|
||||
expect(findSkeletonLoader().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('polling', () => {
|
||||
beforeEach(() => {
|
||||
createComponent(resolverSpy);
|
||||
});
|
||||
|
||||
it('polls for query data', () => {
|
||||
expect(resolverSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
jest.advanceTimersByTime(POLL_INTERVAL);
|
||||
|
||||
expect(resolverSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -93,6 +93,49 @@ RSpec.describe 'Organizations (GraphQL fixtures)', feature_category: :cell do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'organization update group' do
|
||||
base_input_path = 'organizations/groups/edit/graphql/mutations/'
|
||||
base_output_path = 'graphql/organizations/'
|
||||
mutation_name = 'group_update.mutation.graphql'
|
||||
|
||||
it "#{base_output_path}#{mutation_name}.json" do
|
||||
mutation = get_graphql_query_as_string("#{base_input_path}#{mutation_name}")
|
||||
|
||||
post_graphql(
|
||||
mutation,
|
||||
current_user: current_user,
|
||||
variables: {
|
||||
input: {
|
||||
full_path: group.full_path,
|
||||
name: "#{group.name} updated",
|
||||
path: "#{group.path}-updated"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
expect_graphql_errors_to_be_empty
|
||||
end
|
||||
|
||||
it "#{base_output_path}#{mutation_name}_with_errors.json" do
|
||||
mutation = get_graphql_query_as_string("#{base_input_path}#{mutation_name}")
|
||||
|
||||
post_graphql(
|
||||
mutation,
|
||||
current_user: current_user,
|
||||
variables: {
|
||||
input: {
|
||||
full_path: group.full_path,
|
||||
name: "#{group.name} updated",
|
||||
path: "#{group.path}-updated",
|
||||
visibility: 'private'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
expect_graphql_errors_to_be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'organization projects' do
|
||||
base_input_path = 'organizations/shared/graphql/queries/'
|
||||
base_output_path = 'graphql/organizations/'
|
||||
|
|
|
|||
|
|
@ -62,6 +62,21 @@ describe('GroupPathField', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when editing a group and path is set to initial path', () => {
|
||||
beforeEach(async () => {
|
||||
apiMockUnavailablePath();
|
||||
|
||||
createComponent({ propsData: { isEditing: true, value: 'foo' } });
|
||||
await wrapper.setProps({ value: 'foo bar' });
|
||||
await waitForPromises();
|
||||
await wrapper.setProps({ value: 'foo' });
|
||||
});
|
||||
|
||||
it('does not call API', () => {
|
||||
expect(getGroupPathAvailability).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when value is not the suggested path', () => {
|
||||
describe('when value is an unavailable path', () => {
|
||||
beforeEach(async () => {
|
||||
|
|
|
|||
|
|
@ -136,6 +136,10 @@ describe('NewEditForm', () => {
|
|||
|
||||
expect(findPathField().props('value')).toBe('foo-bar');
|
||||
});
|
||||
|
||||
it('sets `isEditing` prop to `true`', () => {
|
||||
expect(findPathField().props('isEditing')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when form is submitted without filling in required fields', () => {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,34 @@
|
|||
import { GlSprintf } from '@gitlab/ui';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
|
||||
import groupUpdateResponse from 'test_fixtures/graphql/organizations/group_update.mutation.graphql.json';
|
||||
import groupUpdateResponseWithErrors from 'test_fixtures/graphql/organizations/group_update.mutation.graphql_with_errors.json';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import App from '~/organizations/groups/edit/components/app.vue';
|
||||
import groupUpdateMutation from '~/organizations/groups/edit/graphql/mutations/group_update.mutation.graphql';
|
||||
import {
|
||||
VISIBILITY_LEVEL_INTERNAL_INTEGER,
|
||||
VISIBILITY_LEVEL_PRIVATE_INTEGER,
|
||||
VISIBILITY_LEVEL_PUBLIC_INTEGER,
|
||||
VISIBILITY_LEVEL_PRIVATE_STRING,
|
||||
} from '~/visibility_level/constants';
|
||||
import NewEditForm from '~/groups/components/new_edit_form.vue';
|
||||
import { createAlert } from '~/alert';
|
||||
import { visitUrlWithAlerts } from '~/lib/utils/url_utility';
|
||||
import { FORM_FIELD_NAME, FORM_FIELD_PATH, FORM_FIELD_VISIBILITY_LEVEL } from '~/groups/constants';
|
||||
import FormErrorsAlert from '~/vue_shared/components/form/errors_alert.vue';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
jest.mock('~/lib/utils/url_utility');
|
||||
jest.mock('~/alert');
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('OrganizationGroupsEditApp', () => {
|
||||
let wrapper;
|
||||
let mockApollo;
|
||||
|
||||
const defaultProvide = {
|
||||
group: {
|
||||
|
|
@ -17,6 +36,7 @@ describe('OrganizationGroupsEditApp', () => {
|
|||
fullName: 'Mock namespace / Foo bar',
|
||||
name: 'Foo bar',
|
||||
path: 'foo-bar',
|
||||
fullPath: 'mock-namespace/foo-bar',
|
||||
},
|
||||
basePath: 'https://gitlab.com',
|
||||
groupsAndProjectsOrganizationPath: '/-/organizations/carrot/groups_and_projects?display=groups',
|
||||
|
|
@ -32,8 +52,15 @@ describe('OrganizationGroupsEditApp', () => {
|
|||
pathPattern: 'mockPattern',
|
||||
};
|
||||
|
||||
const createComponent = () => {
|
||||
const successfulResponseHandler = jest.fn().mockResolvedValue(groupUpdateResponse);
|
||||
|
||||
const createComponent = ({
|
||||
handlers = [[groupUpdateMutation, successfulResponseHandler]],
|
||||
} = {}) => {
|
||||
mockApollo = createMockApollo(handlers);
|
||||
|
||||
wrapper = shallowMountExtended(App, {
|
||||
apolloProvider: mockApollo,
|
||||
provide: defaultProvide,
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
|
|
@ -42,6 +69,18 @@ describe('OrganizationGroupsEditApp', () => {
|
|||
};
|
||||
|
||||
const findForm = () => wrapper.findComponent(NewEditForm);
|
||||
const submitForm = async () => {
|
||||
findForm().vm.$emit('submit', {
|
||||
[FORM_FIELD_NAME]: 'Foo bar',
|
||||
[FORM_FIELD_PATH]: 'foo-bar',
|
||||
[FORM_FIELD_VISIBILITY_LEVEL]: VISIBILITY_LEVEL_PRIVATE_INTEGER,
|
||||
});
|
||||
await nextTick();
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
mockApollo = null;
|
||||
});
|
||||
|
||||
it('renders page title', () => {
|
||||
createComponent();
|
||||
|
|
@ -66,4 +105,88 @@ describe('OrganizationGroupsEditApp', () => {
|
|||
submitButtonText: 'Save changes',
|
||||
});
|
||||
});
|
||||
|
||||
describe('when form is submitted', () => {
|
||||
describe('when API is loading', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
|
||||
await submitForm();
|
||||
});
|
||||
|
||||
it('sets `NewEditForm` `loading` prop to `true`', () => {
|
||||
expect(findForm().props('loading')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when API request is successful', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
await submitForm();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('calls mutation with correct variables and redirects user to organization web url', () => {
|
||||
expect(successfulResponseHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
fullPath: defaultProvide.group.fullPath,
|
||||
name: 'Foo bar',
|
||||
path: 'foo-bar',
|
||||
visibility: VISIBILITY_LEVEL_PRIVATE_STRING,
|
||||
},
|
||||
});
|
||||
expect(visitUrlWithAlerts).toHaveBeenCalledWith(
|
||||
groupUpdateResponse.data.groupUpdate.group.organizationEditPath,
|
||||
[
|
||||
{
|
||||
id: 'organization-group-successfully-updated',
|
||||
message: 'Group was successfully updated.',
|
||||
variant: 'info',
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when API request is not successful', () => {
|
||||
describe('when there is a network error', () => {
|
||||
const error = new Error();
|
||||
|
||||
beforeEach(async () => {
|
||||
createComponent({
|
||||
handlers: [[groupUpdateMutation, jest.fn().mockRejectedValue(error)]],
|
||||
});
|
||||
await submitForm();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('displays error alert', () => {
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: 'An error occurred updating this group. Please try again.',
|
||||
error,
|
||||
captureError: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are GraphQL errors', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent({
|
||||
handlers: [
|
||||
[groupUpdateMutation, jest.fn().mockResolvedValue(groupUpdateResponseWithErrors)],
|
||||
],
|
||||
});
|
||||
await submitForm();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('displays form errors alert', () => {
|
||||
expect(wrapper.findComponent(FormErrorsAlert).props()).toStrictEqual({
|
||||
errors: groupUpdateResponseWithErrors.data.groupUpdate.errors,
|
||||
scrollOnError: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
|||
'full_name' => group.full_name,
|
||||
'name' => group.name,
|
||||
'path' => group.path,
|
||||
'full_path' => group.full_path,
|
||||
"visibility_level" => group.visibility_level
|
||||
},
|
||||
'base_path' => root_url,
|
||||
|
|
|
|||
|
|
@ -7890,6 +7890,14 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
|
|||
it { expect(project.export_status).to eq :queued }
|
||||
end
|
||||
|
||||
context 'when project export is failed' do
|
||||
before do
|
||||
project_export_job.fail_op!
|
||||
end
|
||||
|
||||
it { expect(project.export_status).to eq :failed }
|
||||
end
|
||||
|
||||
context 'when project export is in progress' do
|
||||
before do
|
||||
project_export_job.start!
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ RSpec.describe 'GroupUpdate', feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context 'when authorized' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
before do
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
|
@ -65,6 +67,22 @@ RSpec.describe 'GroupUpdate', feature_category: :groups_and_projects do
|
|||
expect(group.reload.shared_runners_setting).to eq(variables[:shared_runners_setting].downcase)
|
||||
end
|
||||
|
||||
where(:field, :value) do
|
||||
'name' | 'foo bar'
|
||||
'path' | 'foo-bar'
|
||||
'visibility' | 'private'
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:variables) { { full_path: group.full_path, field => value } }
|
||||
|
||||
it "updates #{params[:field]} field" do
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
|
||||
expect(graphql_data_at(:group_update, :group, field.to_sym)).to eq(value)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when bad arguments are provided' do
|
||||
let(:variables) { { full_path: '', shared_runners_setting: 'INVALID' } }
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,30 @@ RSpec.describe StuckExportJobsWorker, feature_category: :importers do
|
|||
|
||||
expect(project_export_job.reload.failed?).to be true
|
||||
end
|
||||
|
||||
context 'when export job has relation exports' do
|
||||
before do
|
||||
create(:project_relation_export, project_export_job: project_export_job)
|
||||
end
|
||||
|
||||
context 'when export job updated at is less than expiration time' do
|
||||
it 'does not mark export job as failed' do
|
||||
worker.perform
|
||||
|
||||
expect(project_export_job.reload.failed?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when export job updated at is greater than expiration time' do
|
||||
it 'marks export job as failed' do
|
||||
project_export_job.update!(created_at: 12.hours.ago)
|
||||
|
||||
worker.perform
|
||||
|
||||
expect(project_export_job.reload.failed?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the job is not in queue and db record in queued state' do
|
||||
|
|
|
|||
Loading…
Reference in New Issue