Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-11-05 03:07:57 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-11-05 03:07:57 +0300
commiteaa9a0adf963293c678f30e73b7150e89f921a99 (patch)
tree8c6a16e5d477347a073a406a0b6b0549bbe06126 /app/assets/javascripts/ci
parent8fc2555ccce63aa5641b123c154389cff593e0d7 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/ci')
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/delete_pipeline_schedule_modal.vue45
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules.vue256
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue18
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions.vue68
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue32
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_next_run.vue32
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_owner.vue29
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_target.vue36
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue102
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/take_ownership_modal.vue54
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/components/take_ownership_modal_legacy.vue52
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/graphql/mutations/delete_pipeline_schedule.mutation.graphql6
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/graphql/mutations/take_ownership.mutation.graphql12
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql41
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_app.js34
-rw-r--r--app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_form_app.js32
16 files changed, 849 insertions, 0 deletions
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/delete_pipeline_schedule_modal.vue b/app/assets/javascripts/ci/pipeline_schedules/components/delete_pipeline_schedule_modal.vue
new file mode 100644
index 00000000000..16bfc7f3abe
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/delete_pipeline_schedule_modal.vue
@@ -0,0 +1,45 @@
+<script>
+import { GlModal } from '@gitlab/ui';
+import { s__, __ } from '~/locale';
+
+export default {
+ modal: {
+ id: 'delete-pipeline-schedule-modal',
+ deleteConfirmation: s__(
+ 'PipelineSchedules|Are you sure you want to delete this pipeline schedule?',
+ ),
+ actionPrimary: {
+ text: s__('PipelineSchedules|Delete pipeline schedule'),
+ attributes: [{ variant: 'danger' }],
+ },
+ actionCancel: {
+ text: __('Cancel'),
+ attributes: [],
+ },
+ },
+ components: {
+ GlModal,
+ },
+ props: {
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ :visible="visible"
+ :title="$options.modal.actionPrimary.text"
+ :modal-id="$options.modal.id"
+ :action-primary="$options.modal.actionPrimary"
+ :action-cancel="$options.modal.actionCancel"
+ size="sm"
+ @primary="$emit('deleteSchedule')"
+ @hide="$emit('hideModal')"
+ >
+ {{ $options.modal.deleteConfirmation }}
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules.vue b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules.vue
new file mode 100644
index 00000000000..fe16cb7a92e
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules.vue
@@ -0,0 +1,256 @@
+<script>
+import { GlAlert, GlBadge, GlButton, GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+import { limitedCounterWithDelimiter } from '~/lib/utils/text_utility';
+import { queryToObject } from '~/lib/utils/url_utility';
+import deletePipelineScheduleMutation from '../graphql/mutations/delete_pipeline_schedule.mutation.graphql';
+import takeOwnershipMutation from '../graphql/mutations/take_ownership.mutation.graphql';
+import getPipelineSchedulesQuery from '../graphql/queries/get_pipeline_schedules.query.graphql';
+import PipelineSchedulesTable from './table/pipeline_schedules_table.vue';
+import TakeOwnershipModal from './take_ownership_modal.vue';
+import DeletePipelineScheduleModal from './delete_pipeline_schedule_modal.vue';
+
+export default {
+ i18n: {
+ schedulesFetchError: s__('PipelineSchedules|There was a problem fetching pipeline schedules.'),
+ scheduleDeleteError: s__(
+ 'PipelineSchedules|There was a problem deleting the pipeline schedule.',
+ ),
+ takeOwnershipError: s__(
+ 'PipelineSchedules|There was a problem taking ownership of the pipeline schedule.',
+ ),
+ newSchedule: s__('PipelineSchedules|New schedule'),
+ deleteSuccess: s__('PipelineSchedules|Pipeline schedule successfully deleted.'),
+ },
+ components: {
+ DeletePipelineScheduleModal,
+ GlAlert,
+ GlBadge,
+ GlButton,
+ GlLoadingIcon,
+ GlTabs,
+ GlTab,
+ PipelineSchedulesTable,
+ TakeOwnershipModal,
+ },
+ inject: {
+ fullPath: {
+ default: '',
+ },
+ },
+ apollo: {
+ schedules: {
+ query: getPipelineSchedulesQuery,
+ variables() {
+ return {
+ projectPath: this.fullPath,
+ status: this.scope,
+ };
+ },
+ update(data) {
+ const { pipelineSchedules: { nodes: list = [], count } = {} } = data.project || {};
+
+ return {
+ list,
+ count,
+ };
+ },
+ error() {
+ this.reportError(this.$options.i18n.schedulesFetchError);
+ },
+ },
+ },
+ data() {
+ const { scope } = queryToObject(window.location.search);
+ return {
+ schedules: {
+ list: [],
+ },
+ scope,
+ hasError: false,
+ errorMessage: '',
+ scheduleId: null,
+ showDeleteModal: false,
+ showTakeOwnershipModal: false,
+ count: 0,
+ };
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.schedules.loading;
+ },
+ schedulesCount() {
+ return this.schedules.count;
+ },
+ tabs() {
+ return [
+ {
+ text: s__('PipelineSchedules|All'),
+ count: limitedCounterWithDelimiter(this.count),
+ scope: null,
+ showBadge: true,
+ attrs: { 'data-testid': 'pipeline-schedules-all-tab' },
+ },
+ {
+ text: s__('PipelineSchedules|Active'),
+ scope: 'ACTIVE',
+ showBadge: false,
+ attrs: { 'data-testid': 'pipeline-schedules-active-tab' },
+ },
+ {
+ text: s__('PipelineSchedules|Inactive'),
+ scope: 'INACTIVE',
+ showBadge: false,
+ attrs: { 'data-testid': 'pipeline-schedules-inactive-tab' },
+ },
+ ];
+ },
+ },
+ watch: {
+ // this watcher ensures that the count on the all tab
+ // is not updated when switching to other tabs
+ schedulesCount(newCount) {
+ if (!this.scope) {
+ this.count = newCount;
+ }
+ },
+ },
+ methods: {
+ reportError(error) {
+ this.hasError = true;
+ this.errorMessage = error;
+ },
+ setDeleteModal(id) {
+ this.showDeleteModal = true;
+ this.scheduleId = id;
+ },
+ setTakeOwnershipModal(id) {
+ this.showTakeOwnershipModal = true;
+ this.scheduleId = id;
+ },
+ hideModal() {
+ this.showDeleteModal = false;
+ this.showTakeOwnershipModal = false;
+ this.scheduleId = null;
+ },
+ async deleteSchedule() {
+ try {
+ const {
+ data: {
+ pipelineScheduleDelete: { errors },
+ },
+ } = await this.$apollo.mutate({
+ mutation: deletePipelineScheduleMutation,
+ variables: { id: this.scheduleId },
+ });
+
+ if (errors.length > 0) {
+ throw new Error();
+ } else {
+ this.$apollo.queries.schedules.refetch();
+ this.$toast.show(this.$options.i18n.deleteSuccess);
+ }
+ } catch {
+ this.reportError(this.$options.i18n.scheduleDeleteError);
+ }
+ },
+ async takeOwnership() {
+ try {
+ const {
+ data: {
+ pipelineScheduleTakeOwnership: { pipelineSchedule, errors },
+ },
+ } = await this.$apollo.mutate({
+ mutation: takeOwnershipMutation,
+ variables: { id: this.scheduleId },
+ });
+
+ if (errors.length > 0) {
+ throw new Error();
+ } else {
+ this.$apollo.queries.schedules.refetch();
+
+ if (pipelineSchedule?.owner?.name) {
+ const toastMsg = sprintf(
+ s__('PipelineSchedules|Successfully taken ownership from %{owner}.'),
+ {
+ owner: pipelineSchedule.owner.name,
+ },
+ );
+
+ this.$toast.show(toastMsg);
+ }
+ }
+ } catch {
+ this.reportError(this.$options.i18n.takeOwnershipError);
+ }
+ },
+ fetchPipelineSchedulesByStatus(scope) {
+ this.scope = scope;
+ this.$apollo.queries.schedules.refetch();
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-alert v-if="hasError" class="gl-mb-2" variant="danger" @dismiss="hasError = false">
+ {{ errorMessage }}
+ </gl-alert>
+
+ <template v-else>
+ <gl-tabs
+ sync-active-tab-with-query-params
+ query-param-name="scope"
+ nav-class="gl-flex-grow-1 gl-align-items-center"
+ >
+ <gl-tab
+ v-for="tab in tabs"
+ :key="tab.text"
+ :title-link-attributes="tab.attrs"
+ :query-param-value="tab.scope"
+ @click="fetchPipelineSchedulesByStatus(tab.scope)"
+ >
+ <template #title>
+ <span>{{ tab.text }}</span>
+
+ <template v-if="tab.showBadge">
+ <gl-loading-icon v-if="tab.scope === scope && isLoading" class="gl-ml-2" />
+
+ <gl-badge v-else-if="tab.count" size="sm" class="gl-tab-counter-badge">
+ {{ tab.count }}
+ </gl-badge>
+ </template>
+ </template>
+
+ <gl-loading-icon v-if="isLoading" size="lg" />
+ <pipeline-schedules-table
+ v-else
+ :schedules="schedules.list"
+ @showTakeOwnershipModal="setTakeOwnershipModal"
+ @showDeleteModal="setDeleteModal"
+ />
+ </gl-tab>
+
+ <template #tabs-end>
+ <gl-button variant="confirm" class="gl-ml-auto" data-testid="new-schedule-button">
+ {{ $options.i18n.newSchedule }}
+ </gl-button>
+ </template>
+ </gl-tabs>
+
+ <take-ownership-modal
+ :visible="showTakeOwnershipModal"
+ @takeOwnership="takeOwnership"
+ @hideModal="hideModal"
+ />
+
+ <delete-pipeline-schedule-modal
+ :visible="showDeleteModal"
+ @deleteSchedule="deleteSchedule"
+ @hideModal="hideModal"
+ />
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue
new file mode 100644
index 00000000000..6e24ac6b8d4
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue
@@ -0,0 +1,18 @@
+<script>
+import { GlForm } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlForm,
+ },
+ inject: {
+ fullPath: {
+ default: '',
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form />
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions.vue b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions.vue
new file mode 100644
index 00000000000..8656e5d3536
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions.vue
@@ -0,0 +1,68 @@
+<script>
+import { GlButton, GlButtonGroup, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export const i18n = {
+ playTooltip: s__('PipelineSchedules|Run pipeline schedule'),
+ editTooltip: s__('PipelineSchedules|Edit pipeline schedule'),
+ deleteTooltip: s__('PipelineSchedules|Delete pipeline schedule'),
+ takeOwnershipTooltip: s__('PipelineSchedules|Take ownership of pipeline schedule'),
+};
+
+export default {
+ i18n,
+ components: {
+ GlButton,
+ GlButtonGroup,
+ },
+ directives: {
+ GlTooltip,
+ },
+ props: {
+ schedule: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ canPlay() {
+ return this.schedule.userPermissions.playPipelineSchedule;
+ },
+ canTakeOwnership() {
+ return this.schedule.userPermissions.takeOwnershipPipelineSchedule;
+ },
+ canUpdate() {
+ return this.schedule.userPermissions.updatePipelineSchedule;
+ },
+ canRemove() {
+ return this.schedule.userPermissions.adminPipelineSchedule;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-justify-content-end">
+ <gl-button-group>
+ <gl-button v-if="canPlay" v-gl-tooltip :title="$options.i18n.playTooltip" icon="play" />
+ <gl-button
+ v-if="canTakeOwnership"
+ v-gl-tooltip
+ :title="$options.i18n.takeOwnershipTooltip"
+ icon="user"
+ data-testid="take-ownership-pipeline-schedule-btn"
+ @click="$emit('showTakeOwnershipModal', schedule.id)"
+ />
+ <gl-button v-if="canUpdate" v-gl-tooltip :title="$options.i18n.editTooltip" icon="pencil" />
+ <gl-button
+ v-if="canRemove"
+ v-gl-tooltip
+ :title="$options.i18n.deleteTooltip"
+ icon="remove"
+ variant="danger"
+ data-testid="delete-pipeline-schedule-btn"
+ @click="$emit('showDeleteModal', schedule.id)"
+ />
+ </gl-button-group>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue
new file mode 100644
index 00000000000..216796b357c
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue
@@ -0,0 +1,32 @@
+<script>
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+
+export default {
+ components: {
+ CiBadge,
+ },
+ props: {
+ schedule: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ hasPipeline() {
+ return this.schedule.lastPipeline;
+ },
+ lastPipelineStatus() {
+ return this.schedule?.lastPipeline?.detailedStatus;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <ci-badge v-if="hasPipeline" :status="lastPipelineStatus" class="gl-vertical-align-middle" />
+ <span v-else data-testid="pipeline-schedule-status-text">
+ {{ s__('PipelineSchedules|None') }}
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_next_run.vue b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_next_run.vue
new file mode 100644
index 00000000000..48d59bf6e7c
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_next_run.vue
@@ -0,0 +1,32 @@
+<script>
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ components: {
+ TimeAgoTooltip,
+ },
+ props: {
+ schedule: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ showTimeAgo() {
+ return this.schedule.active && this.schedule.nextRunAt;
+ },
+ realNextRunTime() {
+ return this.schedule.realNextRun;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <time-ago-tooltip v-if="showTimeAgo" :time="realNextRunTime" />
+ <span v-else data-testid="pipeline-schedule-inactive">
+ {{ s__('PipelineSchedules|Inactive') }}
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_owner.vue b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_owner.vue
new file mode 100644
index 00000000000..e7fa94eb7fc
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_owner.vue
@@ -0,0 +1,29 @@
+<script>
+import { GlAvatar, GlAvatarLink } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlAvatar,
+ GlAvatarLink,
+ },
+ props: {
+ schedule: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ owner() {
+ return this.schedule.owner;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-avatar-link :href="owner.webPath" :title="owner.name" class="gl-ml-3">
+ <gl-avatar :size="32" :src="owner.avatarUrl" />
+ </gl-avatar-link>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_target.vue b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_target.vue
new file mode 100644
index 00000000000..08efa794bcc
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_target.vue
@@ -0,0 +1,36 @@
+<script>
+import { GlIcon, GlLink } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlIcon,
+ GlLink,
+ },
+ props: {
+ schedule: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ iconName() {
+ return this.schedule.forTag ? 'tag' : 'fork';
+ },
+ refPath() {
+ return this.schedule.refPath;
+ },
+ refDisplay() {
+ return this.schedule.refForDisplay;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-icon :name="iconName" />
+ <span v-if="refPath">
+ <gl-link :href="refPath" class="gl-text-gray-900">{{ refDisplay }}</gl-link>
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue b/app/assets/javascripts/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue
new file mode 100644
index 00000000000..1b97a35a51e
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue
@@ -0,0 +1,102 @@
+<script>
+import { GlTableLite } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import PipelineScheduleActions from './cells/pipeline_schedule_actions.vue';
+import PipelineScheduleLastPipeline from './cells/pipeline_schedule_last_pipeline.vue';
+import PipelineScheduleNextRun from './cells/pipeline_schedule_next_run.vue';
+import PipelineScheduleOwner from './cells/pipeline_schedule_owner.vue';
+import PipelineScheduleTarget from './cells/pipeline_schedule_target.vue';
+
+export default {
+ fields: [
+ {
+ key: 'description',
+ label: s__('PipelineSchedules|Description'),
+ thClass: 'gl-border-t-none!',
+ columnClass: 'gl-w-40p',
+ },
+ {
+ key: 'target',
+ label: s__('PipelineSchedules|Target'),
+ thClass: 'gl-border-t-none!',
+ columnClass: 'gl-w-10p',
+ },
+ {
+ key: 'pipeline',
+ label: s__('PipelineSchedules|Last Pipeline'),
+ thClass: 'gl-border-t-none!',
+ columnClass: 'gl-w-10p',
+ },
+ {
+ key: 'next',
+ label: s__('PipelineSchedules|Next Run'),
+ thClass: 'gl-border-t-none!',
+ columnClass: 'gl-w-15p',
+ },
+ {
+ key: 'owner',
+ label: s__('PipelineSchedules|Owner'),
+ thClass: 'gl-border-t-none!',
+ columnClass: 'gl-w-10p',
+ },
+ {
+ key: 'actions',
+ label: '',
+ thClass: 'gl-border-t-none!',
+ columnClass: 'gl-w-15p',
+ },
+ ],
+ components: {
+ GlTableLite,
+ PipelineScheduleActions,
+ PipelineScheduleLastPipeline,
+ PipelineScheduleNextRun,
+ PipelineScheduleOwner,
+ PipelineScheduleTarget,
+ },
+ props: {
+ schedules: {
+ type: Array,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-table-lite :fields="$options.fields" :items="schedules" stacked="md">
+ <template #table-colgroup="{ fields }">
+ <col v-for="field in fields" :key="field.key" :class="field.columnClass" />
+ </template>
+
+ <template #cell(description)="{ item }">
+ <span data-testid="pipeline-schedule-description">
+ {{ item.description }}
+ </span>
+ </template>
+
+ <template #cell(target)="{ item }">
+ <pipeline-schedule-target :schedule="item" />
+ </template>
+
+ <template #cell(pipeline)="{ item }">
+ <pipeline-schedule-last-pipeline :schedule="item" />
+ </template>
+
+ <template #cell(next)="{ item }">
+ <pipeline-schedule-next-run :schedule="item" />
+ </template>
+
+ <template #cell(owner)="{ item }">
+ <pipeline-schedule-owner :schedule="item" />
+ </template>
+
+ <template #cell(actions)="{ item }">
+ <pipeline-schedule-actions
+ :schedule="item"
+ @showTakeOwnershipModal="$emit('showTakeOwnershipModal', $event)"
+ @showDeleteModal="$emit('showDeleteModal', $event)"
+ />
+ </template>
+ </gl-table-lite>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/take_ownership_modal.vue b/app/assets/javascripts/ci/pipeline_schedules/components/take_ownership_modal.vue
new file mode 100644
index 00000000000..3ac52d4735d
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/take_ownership_modal.vue
@@ -0,0 +1,54 @@
+<script>
+import { GlModal } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+
+export default {
+ modalId: 'pipeline-take-ownership-modal',
+ i18n: {
+ takeOwnership: s__('PipelineSchedules|Take ownership'),
+ ownershipMessage: s__(
+ 'PipelineSchedules|Only the owner of a pipeline schedule can make changes to it. Do you want to take ownership of this schedule?',
+ ),
+ cancelLabel: __('Cancel'),
+ },
+ components: {
+ GlModal,
+ },
+ props: {
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ actionCancel() {
+ return { text: this.$options.i18n.cancelLabel };
+ },
+ actionPrimary() {
+ return {
+ text: this.$options.i18n.takeOwnership,
+ attributes: [
+ {
+ variant: 'confirm',
+ category: 'primary',
+ },
+ ],
+ };
+ },
+ },
+};
+</script>
+<template>
+ <gl-modal
+ :visible="visible"
+ :modal-id="$options.modalId"
+ :action-primary="actionPrimary"
+ :action-cancel="actionCancel"
+ :title="$options.i18n.takeOwnership"
+ size="sm"
+ @primary="$emit('takeOwnership')"
+ @hide="$emit('hideModal')"
+ >
+ <p>{{ $options.i18n.ownershipMessage }}</p>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/take_ownership_modal_legacy.vue b/app/assets/javascripts/ci/pipeline_schedules/components/take_ownership_modal_legacy.vue
new file mode 100644
index 00000000000..7ded3945a32
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/components/take_ownership_modal_legacy.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlModal } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+
+export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ ownershipUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ modalId: 'pipeline-take-ownership-modal',
+ i18n: {
+ takeOwnership: s__('PipelineSchedules|Take ownership'),
+ ownershipMessage: s__(
+ 'PipelineSchedules|Only the owner of a pipeline schedule can make changes to it. Do you want to take ownership of this schedule?',
+ ),
+ cancelLabel: __('Cancel'),
+ },
+ computed: {
+ actionCancel() {
+ return { text: this.$options.i18n.cancelLabel };
+ },
+ actionPrimary() {
+ return {
+ text: this.$options.i18n.takeOwnership,
+ attributes: [
+ {
+ variant: 'confirm',
+ category: 'primary',
+ href: this.ownershipUrl,
+ 'data-method': 'post',
+ },
+ ],
+ };
+ },
+ },
+};
+</script>
+<template>
+ <gl-modal
+ :modal-id="$options.modalId"
+ :action-primary="actionPrimary"
+ :action-cancel="actionCancel"
+ :title="$options.i18n.takeOwnership"
+ >
+ <p>{{ $options.i18n.ownershipMessage }}</p>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_schedules/graphql/mutations/delete_pipeline_schedule.mutation.graphql b/app/assets/javascripts/ci/pipeline_schedules/graphql/mutations/delete_pipeline_schedule.mutation.graphql
new file mode 100644
index 00000000000..8aab0b3fbde
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/graphql/mutations/delete_pipeline_schedule.mutation.graphql
@@ -0,0 +1,6 @@
+mutation deletePipelineSchedule($id: CiPipelineScheduleID!) {
+ pipelineScheduleDelete(input: { id: $id }) {
+ clientMutationId
+ errors
+ }
+}
diff --git a/app/assets/javascripts/ci/pipeline_schedules/graphql/mutations/take_ownership.mutation.graphql b/app/assets/javascripts/ci/pipeline_schedules/graphql/mutations/take_ownership.mutation.graphql
new file mode 100644
index 00000000000..e410ef91d8b
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/graphql/mutations/take_ownership.mutation.graphql
@@ -0,0 +1,12 @@
+mutation takeOwnership($id: CiPipelineScheduleID!) {
+ pipelineScheduleTakeOwnership(input: { id: $id }) {
+ pipelineSchedule {
+ id
+ owner {
+ id
+ name
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/ci/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql b/app/assets/javascripts/ci/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql
new file mode 100644
index 00000000000..9f6cb429cca
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql
@@ -0,0 +1,41 @@
+query getPipelineSchedulesQuery($projectPath: ID!, $status: PipelineScheduleStatus) {
+ project(fullPath: $projectPath) {
+ id
+ pipelineSchedules(status: $status) {
+ count
+ nodes {
+ id
+ description
+ forTag
+ refPath
+ refForDisplay
+ lastPipeline {
+ id
+ detailedStatus {
+ id
+ group
+ icon
+ label
+ text
+ detailsPath
+ }
+ }
+ active
+ nextRunAt
+ realNextRun
+ owner {
+ id
+ avatarUrl
+ name
+ webPath
+ }
+ userPermissions {
+ playPipelineSchedule
+ takeOwnershipPipelineSchedule
+ updatePipelineSchedule
+ adminPipelineSchedule
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_app.js b/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_app.js
new file mode 100644
index 00000000000..4c06fa321e5
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_app.js
@@ -0,0 +1,34 @@
+import { GlToast } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import PipelineSchedules from './components/pipeline_schedules.vue';
+
+Vue.use(VueApollo);
+Vue.use(GlToast);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+export default () => {
+ const containerEl = document.querySelector('#pipeline-schedules-app');
+
+ if (!containerEl) {
+ return false;
+ }
+
+ const { fullPath } = containerEl.dataset;
+
+ return new Vue({
+ el: containerEl,
+ name: 'PipelineSchedulesRoot',
+ apolloProvider,
+ provide: {
+ fullPath,
+ },
+ render(createElement) {
+ return createElement(PipelineSchedules);
+ },
+ });
+};
diff --git a/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_form_app.js b/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_form_app.js
new file mode 100644
index 00000000000..d83417ab84a
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_form_app.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import PipelineSchedulesForm from './components/pipeline_schedules_form.vue';
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+export default (selector) => {
+ const containerEl = document.querySelector(selector);
+
+ if (!containerEl) {
+ return false;
+ }
+
+ const { fullPath } = containerEl.dataset;
+
+ return new Vue({
+ el: containerEl,
+ name: 'PipelineSchedulesFormRoot',
+ apolloProvider,
+ provide: {
+ fullPath,
+ },
+ render(createElement) {
+ return createElement(PipelineSchedulesForm);
+ },
+ });
+};