-
+
{{ repo.importSource.fullName }}
@@ -156,10 +160,10 @@ export default {
{{ displayFullPath }}
-
-
+
+
-
+
{
const repo = state.repositories.find((p) => p.importedProject?.id === updatedProject.id);
if (repo?.importedProject) {
- repo.importedProject.importStatus = updatedProject.importStatus;
+ repo.importedProject = {
+ ...repo.importedProject,
+ stats: updatedProject.stats,
+ importStatus: updatedProject.importStatus,
+ };
}
});
},
diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue
index 324797ad64..bfc5bd823a 100644
--- a/app/assets/javascripts/incidents/components/incidents_list.vue
+++ b/app/assets/javascripts/incidents/components/incidents_list.vue
@@ -33,6 +33,7 @@ import {
TH_CREATED_AT_TEST_ID,
TH_INCIDENT_SLA_TEST_ID,
TH_SEVERITY_TEST_ID,
+ TH_ESCALATION_STATUS_TEST_ID,
TH_PUBLISHED_TEST_ID,
INCIDENT_DETAILS_PATH,
trackIncidentCreateNewOptions,
@@ -67,8 +68,11 @@ export default {
{
key: 'escalationStatus',
label: s__('IncidentManagement|Status'),
- thClass: `${thClass} gl-w-eighth gl-pointer-events-none`,
- tdClass,
+ thClass: `${thClass} gl-w-eighth`,
+ tdClass: `${tdClass} sortable-cell`,
+ actualSortKey: 'ESCALATION_STATUS',
+ sortable: true,
+ thAttr: TH_ESCALATION_STATUS_TEST_ID,
},
{
key: 'createdAt',
@@ -354,7 +358,7 @@ export default {
:loading="redirecting"
:disabled="redirecting"
category="primary"
- variant="success"
+ variant="confirm"
:href="newIncidentPath"
@click="navigateToCreateNewIncident"
>
@@ -388,19 +392,24 @@ export default {
-
+
- {{ item.title }}
+
+ {{ item.title }}
+
diff --git a/app/assets/javascripts/incidents/constants.js b/app/assets/javascripts/incidents/constants.js
index 21cdbef05a..ee3f30de88 100644
--- a/app/assets/javascripts/incidents/constants.js
+++ b/app/assets/javascripts/incidents/constants.js
@@ -47,6 +47,7 @@ export const ESCALATION_STATUSES = {
export const DEFAULT_PAGE_SIZE = 20;
export const TH_CREATED_AT_TEST_ID = { 'data-testid': 'incident-management-created-at-sort' };
export const TH_SEVERITY_TEST_ID = { 'data-testid': 'incident-management-severity-sort' };
+export const TH_ESCALATION_STATUS_TEST_ID = { 'data-testid': 'incident-management-status-sort' };
export const TH_INCIDENT_SLA_TEST_ID = { 'data-testid': 'incident-management-sla' };
export const TH_PUBLISHED_TEST_ID = { 'data-testid': 'incident-management-published-sort' };
export const INCIDENT_DETAILS_PATH = 'incident';
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index 6e89872ff6..661299920c 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -4,7 +4,6 @@ import axios from 'axios';
import * as Sentry from '@sentry/browser';
import { mapState, mapActions, mapGetters } from 'vuex';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
@@ -18,8 +17,6 @@ import { testIntegrationSettings } from '../api';
import ActiveCheckbox from './active_checkbox.vue';
import ConfirmationModal from './confirmation_modal.vue';
import DynamicField from './dynamic_field.vue';
-import JiraIssuesFields from './jira_issues_fields.vue';
-import JiraTriggerFields from './jira_trigger_fields.vue';
import OverrideDropdown from './override_dropdown.vue';
import ResetConfirmationModal from './reset_confirmation_modal.vue';
import TriggerFields from './trigger_fields.vue';
@@ -29,8 +26,6 @@ export default {
components: {
OverrideDropdown,
ActiveCheckbox,
- JiraTriggerFields,
- JiraIssuesFields,
TriggerFields,
DynamicField,
ConfirmationModal,
@@ -54,12 +49,6 @@ export default {
GlModal: GlModalDirective,
SafeHtml,
},
- mixins: [glFeatureFlagsMixin()],
- provide() {
- return {
- hasSections: this.hasSections,
- };
- },
inject: {
helpHtml: {
default: '',
@@ -80,9 +69,6 @@ export default {
isEditable() {
return this.propsSource.editable;
},
- isJira() {
- return this.propsSource.type === 'jira';
- },
isInstanceOrGroupLevel() {
return (
this.customState.integrationLevel === integrationLevels.INSTANCE ||
@@ -98,14 +84,11 @@ export default {
disableButtons() {
return Boolean(this.isSaving || this.isResetting || this.isTesting);
},
- sectionsEnabled() {
- return this.glFeatures.integrationFormSections;
- },
hasSections() {
- return this.sectionsEnabled && this.customState.sections.length !== 0;
+ return this.customState.sections.length !== 0;
},
fieldsWithoutSection() {
- return this.sectionsEnabled
+ return this.hasSections
? this.propsSource.fields.filter((field) => !field.section)
: this.propsSource.fields;
},
@@ -184,6 +167,9 @@ export default {
this.integrationActive = integrationActive;
},
},
+ descriptionHtmlConfig: {
+ ADD_ATTR: ['target'], // allow external links, can be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1427 is implemented
+ },
helpHtmlConfig: {
ADD_ATTR: ['target'], // allow external links, can be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1427 is implemented
ADD_TAGS: ['use'], // to support icon SVGs
@@ -229,7 +215,7 @@ export default {
@@ -257,14 +243,8 @@ export default {
:key="`${currentKey}-active-checkbox`"
@toggle-integration-active="onToggleIntegrationState"
/>
-
-
diff --git a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
index 7cf8e11f16..f00339c92f 100644
--- a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
+++ b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
@@ -1,5 +1,5 @@
-
-
-
- {{ $options.i18n.sectionDescription }}
-
-
-
-
- {{ $options.i18n.enableCheckboxLabel }}
-
- {{ $options.i18n.enableCheckboxHelp }}
-
-
-
-
-
-
+
+
+
+ {{ $options.i18n.enableCheckboxLabel }}
+
+ {{ $options.i18n.enableCheckboxHelp }}
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- {{ content }}
-
-
-
+
+
diff --git a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue
index 3c06660e7c..c7cbdff72e 100644
--- a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue
+++ b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue
@@ -62,11 +62,6 @@ export default {
GlLink,
GlSprintf,
},
- inject: {
- hasSections: {
- default: false,
- },
- },
props: {
initialTriggerCommit: {
type: Boolean,
@@ -138,17 +133,7 @@ export default {
-
+
{{ __('Commit') }}
@@ -162,7 +147,7 @@ export default {
{{ __('Merge request') }}
-
+
{
@@ -19,7 +22,6 @@ function parseDatasetToProps(data) {
commentDetail,
projectKey,
upgradePlanPath,
- editProjectPath,
learnMorePath,
triggerEvents,
sections,
@@ -49,7 +51,6 @@ function parseDatasetToProps(data) {
showJiraVulnerabilitiesIntegration,
enableJiraIssues,
enableJiraVulnerabilities,
- gitlabIssuesEnabled,
} = parseBooleanInData(booleanAttributes);
return {
@@ -78,9 +79,7 @@ function parseDatasetToProps(data) {
initialEnableJiraVulnerabilities: enableJiraVulnerabilities,
initialVulnerabilitiesIssuetype: vulnerabilitiesIssuetype,
initialProjectKey: projectKey,
- gitlabIssuesEnabled,
upgradePlanPath,
- editProjectPath,
},
learnMorePath,
triggerEvents: JSON.parse(triggerEvents),
diff --git a/app/assets/javascripts/invite_members/components/group_select.vue b/app/assets/javascripts/invite_members/components/group_select.vue
index 04a8ec3400..fc14b2eba6 100644
--- a/app/assets/javascripts/invite_members/components/group_select.vue
+++ b/app/assets/javascripts/invite_members/components/group_select.vue
@@ -24,10 +24,6 @@ export default {
prop: 'selectedGroup',
},
props: {
- accessLevels: {
- type: Object,
- required: true,
- },
groupsFilter: {
type: String,
required: false,
@@ -58,13 +54,6 @@ export default {
isFetchResultEmpty() {
return this.groups.length === 0;
},
- defaultFetchOptions() {
- return {
- exclude_internal: true,
- active: true,
- min_access_level: this.accessLevels.Guest,
- };
- },
},
watch: {
searchTerm() {
@@ -107,9 +96,13 @@ export default {
fetchGroups() {
switch (this.groupsFilter) {
case GROUP_FILTERS.DESCENDANT_GROUPS:
- return getDescendentGroups(this.parentGroupId, this.searchTerm, this.defaultFetchOptions);
+ return getDescendentGroups(
+ this.parentGroupId,
+ this.searchTerm,
+ this.$options.defaultFetchOptions,
+ );
default:
- return getGroups(this.searchTerm, this.defaultFetchOptions);
+ return getGroups(this.searchTerm, this.$options.defaultFetchOptions);
}
},
},
@@ -118,6 +111,10 @@ export default {
searchPlaceholder: s__('GroupSelect|Search groups'),
emptySearchResult: s__('GroupSelect|No matching results'),
},
+ defaultFetchOptions: {
+ exclude_internal: true,
+ active: true,
+ },
};
diff --git a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
index f266d978ff..2ad4bb1a11 100644
--- a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
@@ -2,11 +2,11 @@
import { uniqueId } from 'lodash';
import Api from '~/api';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
+import InviteModalBase from 'ee_else_ce/invite_members/components/invite_modal_base.vue';
import { GROUP_FILTERS, GROUP_MODAL_LABELS } from '../constants';
import eventHub from '../event_hub';
import { getInvalidFeedbackMessage } from '../utils/get_invalid_feedback_message';
import GroupSelect from './group_select.vue';
-import InviteModalBase from './invite_modal_base.vue';
export default {
name: 'InviteMembersModal',
@@ -19,6 +19,10 @@ export default {
type: String,
required: true,
},
+ rootId: {
+ type: String,
+ required: true,
+ },
isProject: {
type: Boolean,
required: true,
@@ -147,6 +151,8 @@ export default {
:label-intro-text="labelIntroText"
:label-search-field="$options.labels.searchField"
:submit-disabled="inviteDisabled"
+ :new-group-to-invite="groupToBeSharedWith.id"
+ :root-group-id="rootId"
:invalid-feedback-message="invalidFeedbackMessage"
:is-loading="isLoading"
@reset="resetFields"
@@ -155,7 +161,6 @@ export default {
{
- const message = responseMessageFromSuccess(responses);
+ ...email,
+ ...userId,
+ })
+ .then((response) => {
+ const message = responseMessageFromSuccess(response);
if (message) {
this.showInvalidFeedbackMessage({
@@ -290,6 +278,8 @@ export default {
:submit-disabled="inviteDisabled"
:invalid-feedback-message="invalidFeedbackMessage"
:is-loading="isLoading"
+ :new-users-to-invite="newUsersToInvite"
+ :root-group-id="rootId"
@reset="resetFields"
@submit="sendInvite"
@access-level="onAccessLevelUpdate"
@@ -302,6 +292,11 @@ export default {
{{ $options.labels.modal.celebrate.intro }}
+
+
+
+
+
[],
},
+ preventCancelDefault: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
// Be sure to check out reset!
@@ -141,6 +149,22 @@ export default {
contentSlots() {
return [...DEFAULT_SLOTS, ...(this.extraSlots || [])];
},
+ actionPrimary() {
+ return {
+ text: this.submitButtonText,
+ attributes: {
+ variant: 'confirm',
+ disabled: this.submitDisabled,
+ loading: this.isLoading,
+ 'data-qa-selector': 'invite_button',
+ },
+ };
+ },
+ actionCancel() {
+ return {
+ text: this.cancelButtonText,
+ };
+ },
},
watch: {
selectedAccessLevel: {
@@ -151,7 +175,7 @@ export default {
},
},
methods: {
- reset() {
+ onReset() {
// This component isn't necessarily disposed,
// so we might need to reset it's state.
this.selectedAccessLevel = this.defaultAccessLevel;
@@ -159,14 +183,23 @@ export default {
this.$emit('reset');
},
- closeModal() {
- this.reset();
- this.$refs.modal.hide();
+ onCloseModal(e) {
+ if (this.preventCancelDefault) {
+ e.preventDefault();
+ } else {
+ this.onReset();
+ this.$refs.modal.hide();
+ }
+
+ this.$emit('cancel');
},
changeSelectedItem(item) {
this.selectedAccessLevel = item;
},
- submit() {
+ onSubmit(e) {
+ // We never want to hide when submitting
+ e.preventDefault();
+
this.$emit('submit', {
accessLevel: this.selectedAccessLevel,
expiresAt: this.selectedDate,
@@ -192,9 +225,11 @@ export default {
size="sm"
:title="modalTitle"
:header-close-label="$options.HEADER_CLOSE_LABEL"
- @hidden="reset"
- @close="reset"
- @hide="reset"
+ :action-primary="actionPrimary"
+ :action-cancel="actionCancel"
+ @primary="onSubmit"
+ @cancel="onCloseModal"
+ @hidden="onReset"
>
+
+
-
-
-
- {{ $options.CANCEL_BUTTON_TEXT }}
-
-
-
- {{ submitButtonText }}
-
-
diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue
index e299e3f27b..0a191f6d40 100644
--- a/app/assets/javascripts/invite_members/components/members_token_select.vue
+++ b/app/assets/javascripts/invite_members/components/members_token_select.vue
@@ -117,7 +117,7 @@ export default {
this.$emit('clear');
},
},
- defaultQueryOptions: { exclude_internal: true, active: true },
+ defaultQueryOptions: { without_project_bots: true, active: true },
i18n: {
inviteTextMessage: __('Invite "%{email}" by email'),
},
diff --git a/app/assets/javascripts/invite_members/components/user_limit_notification.vue b/app/assets/javascripts/invite_members/components/user_limit_notification.vue
new file mode 100644
index 0000000000..beef1aef8a
--- /dev/null
+++ b/app/assets/javascripts/invite_members/components/user_limit_notification.vue
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+ {{ content }}
+
+
+ {{ content }}
+
+
+
+
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index cf2ee50818..3cd0bfc018 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -1,4 +1,4 @@
-import { __, s__ } from '~/locale';
+import { s__ } from '~/locale';
export const SEARCH_DELAY = 200;
@@ -14,9 +14,6 @@ export const GROUP_FILTERS = {
DESCENDANT_GROUPS: 'descendant_groups',
};
-export const API_MESSAGES = {
- EMAIL_ALREADY_INVITED: __('Invite email has already been taken'),
-};
export const USERS_FILTER_ALL = 'all';
export const USERS_FILTER_SAML_PROVIDER_ID = 'saml_provider_id';
export const TRIGGER_ELEMENT_BUTTON = 'button';
diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js
index e9d620cedf..958121ad73 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -5,45 +5,47 @@ import { parseBoolean } from '~/lib/utils/common_utils';
Vue.use(GlToast);
-let initedInviteMembersModal;
+export default (function initInviteMembersModal() {
+ let inviteMembersModal;
-export default function initInviteMembersModal() {
- if (initedInviteMembersModal) {
- // if we already loaded this in another part of the dom, we don't want to do it again
- // else we will stack the modals
- return false;
- }
+ return () => {
+ if (!inviteMembersModal) {
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/344955
+ // bug lying in wait here for someone to put group and project invite in same screen
+ // once that happens we'll need to mount these differently, perhaps split
+ // group/project to each mount one, with many ways to open it.
+ const el = document.querySelector('.js-invite-members-modal');
- // https://gitlab.com/gitlab-org/gitlab/-/issues/344955
- // bug lying in wait here for someone to put group and project invite in same screen
- // once that happens we'll need to mount these differently, perhaps split
- // group/project to each mount one, with many ways to open it.
- const el = document.querySelector('.js-invite-members-modal');
+ if (!el) {
+ return false;
+ }
- if (!el) {
- return false;
- }
-
- initedInviteMembersModal = true;
-
- return new Vue({
- el,
- name: 'InviteMembersModalRoot',
- provide: {
- newProjectPath: el.dataset.newProjectPath,
- },
- render: (createElement) =>
- createElement(InviteMembersModal, {
- props: {
- ...el.dataset,
- isProject: parseBoolean(el.dataset.isProject),
- accessLevels: JSON.parse(el.dataset.accessLevels),
- defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10),
- tasksToBeDoneOptions: JSON.parse(el.dataset.tasksToBeDoneOptions || '[]'),
- projects: JSON.parse(el.dataset.projects || '[]'),
- usersFilter: el.dataset.usersFilter,
- filterId: parseInt(el.dataset.filterId, 10),
+ inviteMembersModal = new Vue({
+ el,
+ name: 'InviteMembersModalRoot',
+ provide: {
+ name: el.dataset.name,
+ newProjectPath: el.dataset.newProjectPath,
+ newTrialRegistrationPath: el.dataset.newTrialRegistrationPath,
+ purchasePath: el.dataset.purchasePath,
+ freeUsersLimit: el.dataset.freeUsersLimit && parseInt(el.dataset.freeUsersLimit, 10),
+ membersCount: el.dataset.membersCount && parseInt(el.dataset.membersCount, 10),
},
- }),
- });
-}
+ render: (createElement) =>
+ createElement(InviteMembersModal, {
+ props: {
+ ...el.dataset,
+ isProject: parseBoolean(el.dataset.isProject),
+ accessLevels: JSON.parse(el.dataset.accessLevels),
+ defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10),
+ tasksToBeDoneOptions: JSON.parse(el.dataset.tasksToBeDoneOptions || '[]'),
+ projects: JSON.parse(el.dataset.projects || '[]'),
+ usersFilter: el.dataset.usersFilter,
+ filterId: parseInt(el.dataset.filterId, 10),
+ },
+ }),
+ });
+ }
+ return inviteMembersModal;
+ };
+})();
diff --git a/app/assets/javascripts/invite_members/utils/response_message_parser.js b/app/assets/javascripts/invite_members/utils/response_message_parser.js
index 52ec3be320..db8ac303dc 100644
--- a/app/assets/javascripts/invite_members/utils/response_message_parser.js
+++ b/app/assets/javascripts/invite_members/utils/response_message_parser.js
@@ -1,28 +1,15 @@
import { isString } from 'lodash';
-import { API_MESSAGES } from '~/invite_members/constants';
function responseKeyedMessageParsed(keyedMessage) {
try {
const keys = Object.keys(keyedMessage);
const msg = keyedMessage[keys[0]];
- if (msg === API_MESSAGES.EMAIL_ALREADY_INVITED) {
- return '';
- }
return msg;
} catch {
return '';
}
}
-function responseMessageStringForMultiple(message) {
- return message.includes(':');
-}
-function responseMessageStringFirstPart(message) {
- const firstPart = message.split(':')[1];
- const firstMsg = firstPart.split(/ and [\w-]*$/)[0].trim();
-
- return firstMsg;
-}
export function responseMessageFromError(response) {
if (!response?.response?.data) {
@@ -33,36 +20,25 @@ export function responseMessageFromError(response) {
response: { data },
} = response;
- return (
- data.error ||
- data.message?.user?.[0] ||
- data.message?.access_level?.[0] ||
- data.message?.error ||
- data.message ||
- ''
- );
+ return data.error || data.message?.error || data.message || '';
}
export function responseMessageFromSuccess(response) {
- if (!response?.[0]?.data) {
+ if (!response?.data) {
return '';
}
- const { data } = response[0];
+ const { data } = response;
- if (data.message && !data.message.user) {
+ if (data.message) {
const { message } = data;
if (isString(message)) {
- if (responseMessageStringForMultiple(message)) {
- return responseMessageStringFirstPart(message);
- }
-
return message;
}
return responseKeyedMessageParsed(message);
}
- return data.message || data.message?.user || data.error || '';
+ return data.error || '';
}
diff --git a/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue b/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue
index 269f720bac..dadb141964 100644
--- a/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue
+++ b/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue
@@ -102,6 +102,7 @@ export default {
:text="$options.i18n.importIssuesText"
:text-sr-only="!showLabel"
:icon="importButtonIcon"
+ class="gl-w-full gl-md-w-auto"
>
{{ $options.i18n.importCsvText }}
diff --git a/app/assets/javascripts/issuable/components/issue_milestone.vue b/app/assets/javascripts/issuable/components/issue_milestone.vue
index 6a0c21602b..11fc032f34 100644
--- a/app/assets/javascripts/issuable/components/issue_milestone.vue
+++ b/app/assets/javascripts/issuable/components/issue_milestone.vue
@@ -72,7 +72,7 @@ export default {
-
+
{{ milestone.title }}
{{ __('Milestone') }}
diff --git a/app/assets/javascripts/issuable/issuable_form.js b/app/assets/javascripts/issuable/issuable_form.js
index 88c1748db0..018cadad50 100644
--- a/app/assets/javascripts/issuable/issuable_form.js
+++ b/app/assets/javascripts/issuable/issuable_form.js
@@ -12,6 +12,7 @@ import ZenMode from '~/zen_mode';
const MR_SOURCE_BRANCH = 'merge_request[source_branch]';
const MR_TARGET_BRANCH = 'merge_request[target_branch]';
+const DATA_ISSUES_NEW_PATH = 'data-new-issue-path';
function organizeQuery(obj, isFallbackKey = false) {
if (!obj[MR_SOURCE_BRANCH] && !obj[MR_TARGET_BRANCH]) {
@@ -68,6 +69,7 @@ export default class IssuableForm {
this.reviewersSelect = new UsersSelect(undefined, '.js-reviewer-search');
this.zenMode = new ZenMode();
+ this.newIssuePath = form[0].getAttribute(DATA_ISSUES_NEW_PATH);
this.titleField = this.form.find('input[name*="[title]"]');
this.descriptionField = this.form.find('textarea[name*="[description]"]');
if (!(this.titleField.length && this.descriptionField.length)) {
@@ -104,8 +106,8 @@ export default class IssuableForm {
}
initAutosave() {
- const { search } = document.location;
- const searchTerm = format(search);
+ const { search, pathname } = document.location;
+ const searchTerm = this.newIssuePath === pathname ? '' : format(search);
const fallbackKey = getFallbackKey();
this.autosave = new Autosave(
diff --git a/app/assets/javascripts/issues/create_merge_request_dropdown.js b/app/assets/javascripts/issues/create_merge_request_dropdown.js
index 247f8dd0bd..c96af6da72 100644
--- a/app/assets/javascripts/issues/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/issues/create_merge_request_dropdown.js
@@ -43,7 +43,7 @@ export default class CreateMergeRequestDropdown {
this.refInput = this.wrapperEl.querySelector('.js-ref');
this.refMessage = this.wrapperEl.querySelector('.js-ref-message');
this.unavailableButton = this.wrapperEl.querySelector('.unavailable');
- this.unavailableButtonSpinner = this.unavailableButton.querySelector('.gl-spinner');
+ this.unavailableButtonSpinner = this.unavailableButton.querySelector('.js-create-mr-spinner');
this.unavailableButtonText = this.unavailableButton.querySelector('.text');
this.branchCreated = false;
@@ -453,7 +453,7 @@ export default class CreateMergeRequestDropdown {
removeMessage(target) {
const { input, message } = this.getTargetData(target);
const inputClasses = ['gl-field-error-outline', 'gl-field-success-outline'];
- const messageClasses = ['text-muted', 'text-danger', 'text-success'];
+ const messageClasses = ['gl-text-gray-600', 'gl-text-red-500', 'gl-text-green-500'];
inputClasses.forEach((cssClass) => input.classList.remove(cssClass));
messageClasses.forEach((cssClass) => message.classList.remove(cssClass));
@@ -462,10 +462,10 @@ export default class CreateMergeRequestDropdown {
setUnavailableButtonState(isLoading = true) {
if (isLoading) {
- this.unavailableButtonSpinner.classList.remove('hide');
+ this.unavailableButtonSpinner.classList.remove('gl-display-none');
this.unavailableButtonText.textContent = __('Checking branch availability...');
} else {
- this.unavailableButtonSpinner.classList.add('hide');
+ this.unavailableButtonSpinner.classList.add('gl-display-none');
this.unavailableButtonText.textContent = __('New branch unavailable');
}
}
@@ -476,7 +476,7 @@ export default class CreateMergeRequestDropdown {
this.removeMessage(target);
input.classList.add('gl-field-success-outline');
- message.classList.add('text-success');
+ message.classList.add('gl-text-green-500');
message.textContent = sprintf(__('%{text} is available'), { text });
message.style.display = 'inline-block';
}
@@ -486,7 +486,7 @@ export default class CreateMergeRequestDropdown {
const text = target === 'branch' ? __('branch name') : __('source');
this.removeMessage(target);
- message.classList.add('text-muted');
+ message.classList.add('gl-text-gray-600');
message.textContent = sprintf(__('Checking %{text} availability…'), { text });
message.style.display = 'inline-block';
}
@@ -498,7 +498,7 @@ export default class CreateMergeRequestDropdown {
this.removeMessage(target);
input.classList.add('gl-field-error-outline');
- message.classList.add('text-danger');
+ message.classList.add('gl-text-red-500');
message.textContent = text;
message.style.display = 'inline-block';
}
diff --git a/app/assets/javascripts/issues/index.js b/app/assets/javascripts/issues/index.js
index 2ee9ac2a68..bcd729785b 100644
--- a/app/assets/javascripts/issues/index.js
+++ b/app/assets/javascripts/issues/index.js
@@ -1,6 +1,5 @@
import $ from 'jquery';
import IssuableForm from 'ee_else_ce/issuable/issuable_form';
-import loadAwardsHandler from '~/awards_handler';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GLForm from '~/gl_form';
@@ -22,6 +21,7 @@ import MilestoneSelect from '~/milestones/milestone_select';
import initNotesApp from '~/notes';
import { store } from '~/notes/stores';
import ZenMode from '~/zen_mode';
+import initAwardsApp from '~/emoji/awards_app';
import FilteredSearchServiceDesk from './filtered_search_service_desk';
export function initFilteredSearchServiceDesk() {
@@ -72,15 +72,7 @@ export function initShow() {
initRelatedMergeRequests();
initSentryErrorStackTrace();
- const awardEmojiEl = document.getElementById('js-vue-awards-block');
-
- if (awardEmojiEl) {
- import('~/emoji/awards_app')
- .then((m) => m.default(awardEmojiEl))
- .catch(() => {});
- } else {
- loadAwardsHandler();
- }
+ initAwardsApp(document.getElementById('js-vue-awards-block'));
import(/* webpackChunkName: 'design_management' */ '~/design_management')
.then((module) => module.default())
diff --git a/app/assets/javascripts/issues/list/components/issue_card_time_info.vue b/app/assets/javascripts/issues/list/components/issue_card_time_info.vue
index aece737218..1139861ae7 100644
--- a/app/assets/javascripts/issues/list/components/issue_card_time_info.vue
+++ b/app/assets/javascripts/issues/list/components/issue_card_time_info.vue
@@ -1,11 +1,13 @@
-
+
-
-
-
-
-
-
-
- {{ $options.i18n.newIssueLabel }}
-
-
-
-
-
-
-
- {{ $options.i18n.jiraIntegrationTitle }}
-
-
-
-
- {{ content }}
+
+
+
+
+ {{ $options.i18n.newIssueLabel }}
+
+
+
-
-
-
- {{ $options.i18n.jiraIntegrationSecondaryMessage }}
-
-
+
+
+
+ {{ $options.i18n.jiraIntegrationTitle }}
+
+
+
+
+ {{ content }}
+
+
+
+
+ {{ $options.i18n.jiraIntegrationSecondaryMessage }}
+
+
-
+
+
+
+
diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js
index 4b07a07851..9a97c84f08 100644
--- a/app/assets/javascripts/issues/list/constants.js
+++ b/app/assets/javascripts/issues/list/constants.js
@@ -56,6 +56,7 @@ export const ISSUE_REFERENCE = /^#\d+$/;
export const MAX_LIST_SIZE = 10;
export const PAGE_SIZE = 20;
export const PAGE_SIZE_MANUAL = 100;
+export const PARAM_ASSIGNEE_ID = 'assignee_id';
export const PARAM_PAGE_AFTER = 'page_after';
export const PARAM_PAGE_BEFORE = 'page_before';
export const PARAM_STATE = 'state';
@@ -112,7 +113,8 @@ export const URL_PARAM = 'urlParam';
export const NORMAL_FILTER = 'normalFilter';
export const SPECIAL_FILTER = 'specialFilter';
export const ALTERNATIVE_FILTER = 'alternativeFilter';
-export const SPECIAL_FILTER_VALUES = [
+
+export const specialFilterValues = [
FILTER_NONE,
FILTER_ANY,
FILTER_CURRENT,
diff --git a/app/assets/javascripts/issues/list/queries/get_issues.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql
index 529262d216..ec24ea7c56 100644
--- a/app/assets/javascripts/issues/list/queries/get_issues.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql
@@ -1,4 +1,4 @@
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "./issue.fragment.graphql"
query getIssues(
diff --git a/app/assets/javascripts/issues/list/queries/issue.fragment.graphql b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
index 430d494dea..d09e4d9df2 100644
--- a/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
+++ b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
@@ -2,7 +2,6 @@ fragment IssueFragment on Issue {
__typename
id
iid
- closedAt
confidential
createdAt
downvotes
@@ -11,6 +10,7 @@ fragment IssueFragment on Issue {
humanTimeEstimate
mergeRequestsCount
moved
+ state
title
updatedAt
upvotes
diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js
index 4b77bd9bc5..6d6b4ded01 100644
--- a/app/assets/javascripts/issues/list/utils.js
+++ b/app/assets/javascripts/issues/list/utils.js
@@ -1,4 +1,5 @@
import { isPositiveInteger } from '~/lib/utils/number_utils';
+import { getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import {
FILTERED_SEARCH_TERM,
@@ -20,13 +21,14 @@ import {
NORMAL_FILTER,
PAGE_SIZE,
PAGE_SIZE_MANUAL,
+ PARAM_ASSIGNEE_ID,
POPULARITY_ASC,
POPULARITY_DESC,
PRIORITY_ASC,
PRIORITY_DESC,
RELATIVE_POSITION_ASC,
SPECIAL_FILTER,
- SPECIAL_FILTER_VALUES,
+ specialFilterValues,
TITLE_ASC,
TITLE_DESC,
TOKEN_TYPE_ASSIGNEE,
@@ -202,16 +204,19 @@ export const getFilterTokens = (locationSearch) => {
return filterTokens.concat(searchTokens);
};
-const getFilterType = (data, tokenType = '') =>
- SPECIAL_FILTER_VALUES.includes(data) ||
- (tokenType === TOKEN_TYPE_ASSIGNEE && isPositiveInteger(data))
- ? SPECIAL_FILTER
- : NORMAL_FILTER;
+const getFilterType = (data, tokenType = '') => {
+ const isAssigneeIdParam =
+ tokenType === TOKEN_TYPE_ASSIGNEE &&
+ isPositiveInteger(data) &&
+ getParameterByName(PARAM_ASSIGNEE_ID) === data;
+
+ return specialFilterValues.includes(data) || isAssigneeIdParam ? SPECIAL_FILTER : NORMAL_FILTER;
+};
const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_RELEASE];
const isWildcardValue = (tokenType, value) =>
- wildcardTokens.includes(tokenType) && SPECIAL_FILTER_VALUES.includes(value);
+ wildcardTokens.includes(tokenType) && specialFilterValues.includes(value);
const requiresUpperCaseValue = (tokenType, value) =>
tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value);
diff --git a/app/assets/javascripts/issues/manual_ordering.js b/app/assets/javascripts/issues/manual_ordering.js
index 8fb891f62f..bc1cffef94 100644
--- a/app/assets/javascripts/issues/manual_ordering.js
+++ b/app/assets/javascripts/issues/manual_ordering.js
@@ -1,11 +1,8 @@
import Sortable from 'sortablejs';
-import {
- getBoardSortableDefaultOptions,
- sortableStart,
-} from '~/boards/mixins/sortable_default_options';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
+import { getSortableDefaultOptions, sortableStart } from '~/sortable/utils';
const updateIssue = (url, { move_before_id, move_after_id }) =>
axios
@@ -28,7 +25,7 @@ const initManualOrdering = () => {
Sortable.create(
issueList,
- getBoardSortableDefaultOptions({
+ getSortableDefaultOptions({
scroll: true,
fallbackTolerance: 1,
dataIdAttr: 'data-id',
diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue
index 0490728c6b..456a202970 100644
--- a/app/assets/javascripts/issues/show/components/app.vue
+++ b/app/assets/javascripts/issues/show/components/app.vue
@@ -185,6 +185,11 @@ export default {
required: false,
default: false,
},
+ issueId: {
+ type: Number,
+ required: false,
+ default: null,
+ },
},
data() {
const store = new Store({
@@ -322,9 +327,12 @@ export default {
});
},
+ updateFormState(state) {
+ this.store.setFormState(state);
+ },
+
updateAndShowForm(templates = {}) {
if (!this.showForm) {
- this.showForm = true;
this.store.setFormState({
title: this.state.titleText,
description: this.state.descriptionText,
@@ -333,6 +341,7 @@ export default {
updateLoading: false,
issuableTemplates: templates,
});
+ this.showForm = true;
}
},
@@ -364,6 +373,10 @@ export default {
},
updateIssuable() {
+ this.store.setFormState({
+ updateLoading: true,
+ });
+
const {
store: { formState },
issueState,
@@ -371,7 +384,9 @@ export default {
const issuablePayload = issueState.isDirty
? { ...formState, issue_type: issueState.issueType }
: formState;
+
this.clearFlash();
+
return this.service
.updateIssuable(issuablePayload)
.then((res) => res.data)
@@ -426,7 +441,7 @@ export default {
clearFlash() {
if (this.flashContainer) {
- this.flashContainer.style.display = 'none';
+ this.flashContainer.close();
this.flashContainer = null;
}
},
@@ -468,6 +483,7 @@ export default {
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
:issuable-type="issuableType"
+ @updateForm="updateFormState"
/>
@@ -534,6 +550,7 @@ export default {
{
this.renderGFM();
+ if (this.workItemsEnabled) {
+ this.renderTaskActions();
+ }
});
},
taskStatus() {
@@ -168,9 +189,25 @@ export default {
return;
}
+ this.taskButtons = [];
const taskListFields = this.$el.querySelectorAll('.task-list-item');
taskListFields.forEach((item, index) => {
+ const taskLink = item.querySelector('.gfm-issue');
+ if (taskLink) {
+ const { issue, referenceType } = taskLink.dataset;
+ taskLink.addEventListener('click', (e) => {
+ e.preventDefault();
+ this.workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue);
+ this.updateWorkItemIdUrlQuery(issue);
+ this.track('viewed_work_item_from_modal', {
+ category: 'workItems:show',
+ label: 'work_item_view',
+ property: `type_${referenceType}`,
+ });
+ });
+ return;
+ }
const button = document.createElement('button');
button.classList.add(
'btn',
@@ -188,59 +225,44 @@ export default {
this.taskButtons.push(button.id);
button.innerHTML = `
-
+
`;
+ button.setAttribute('aria-label', s__('WorkItem|Convert to work item'));
+ button.addEventListener('click', () => this.openCreateTaskModal(button.id));
item.prepend(button);
});
},
openCreateTaskModal(id) {
- this.activeTask = { id, title: this.$el.querySelector(`#${id}`).parentElement.innerText };
+ const { parentElement } = this.$el.querySelector(`#${id}`);
+ const lineNumbers = parentElement.getAttribute('data-sourcepos').match(/\b\d+(?=:)/g);
+ this.activeTask = {
+ id,
+ title: parentElement.innerText,
+ lineNumberStart: lineNumbers[0],
+ lineNumberEnd: lineNumbers[1],
+ };
this.$refs.modal.show();
},
closeCreateTaskModal() {
this.$refs.modal.hide();
},
closeWorkItemDetailModal() {
- this.workItemId = null;
+ this.workItemId = undefined;
+ this.updateWorkItemIdUrlQuery(undefined);
},
- handleWorkItemDetailModalError(message) {
- createFlash({ message });
- },
- handleCreateTask({ id, title, type }) {
- const listItem = this.$el.querySelector(`#${this.activeTask.id}`).parentElement;
- const taskBadge = document.createElement('span');
- taskBadge.innerHTML = `
-
-
-
-
- ${__('Task')}
-
- `;
- const button = this.createWorkItemDetailButton(id, title, type);
- taskBadge.append(button);
-
- listItem.insertBefore(taskBadge, listItem.lastChild);
- listItem.removeChild(listItem.lastChild);
+ handleCreateTask(description) {
+ this.$emit('updateDescription', description);
this.closeCreateTaskModal();
},
- createWorkItemDetailButton(id, title, type) {
- const button = document.createElement('button');
- button.addEventListener('click', () => {
- this.workItemId = id;
- this.track('viewed_work_item_from_modal', {
- category: 'workItems:show',
- label: 'work_item_view',
- property: `type_${type}`,
- });
- });
- button.classList.add('btn-link');
- button.innerText = title;
- return button;
+ handleDeleteTask() {
+ this.$toast.show(s__('WorkItem|Work item deleted'));
},
- focusButton() {
- this.$refs.convertButton[0].$el.focus();
+ updateWorkItemIdUrlQuery(workItemId) {
+ updateHistory({
+ url: setUrlParams({ work_item_id: workItemId }),
+ replace: true,
+ });
},
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji', 'copy-code'] },
@@ -266,17 +288,17 @@ export default {
}"
class="md"
>
-
+
-
+
-
- {{ s__('WorkItem|Convert to work item') }}
-
+
+ {{ s__('WorkItem|Convert to work item') }}
+
diff --git a/app/assets/javascripts/issues/show/components/edited.vue b/app/assets/javascripts/issues/show/components/edited.vue
index 0da1900a6d..41cc396405 100644
--- a/app/assets/javascripts/issues/show/components/edited.vue
+++ b/app/assets/javascripts/issues/show/components/edited.vue
@@ -32,7 +32,7 @@ export default {
-
+
Edited
diff --git a/app/assets/javascripts/issues/show/components/fields/description.vue b/app/assets/javascripts/issues/show/components/fields/description.vue
index d5ac7b28af..0bb5e7cb2e 100644
--- a/app/assets/javascripts/issues/show/components/fields/description.vue
+++ b/app/assets/javascripts/issues/show/components/fields/description.vue
@@ -1,5 +1,6 @@
-
+
-
+
-import { GlSprintf, GlLink } from '@gitlab/ui';
+import { GlSprintf, GlLink, GlAlert } from '@gitlab/ui';
import { __ } from '~/locale';
const alertMessage = __(
@@ -11,6 +11,7 @@ export default {
components: {
GlSprintf,
GlLink,
+ GlAlert,
},
computed: {
currentPath() {
@@ -21,7 +22,7 @@ export default {
-
+
@@ -29,5 +30,5 @@ export default {
-
+
diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js
index c9af5d9b4a..4a5ebf9615 100644
--- a/app/assets/javascripts/issues/show/index.js
+++ b/app/assets/javascripts/issues/show/index.js
@@ -102,7 +102,7 @@ export function initIssueApp(issueData, store) {
isConfidential: this.getNoteableData?.confidential,
isLocked: this.getNoteableData?.discussion_locked,
issuableStatus: this.getNoteableData?.state,
- id: this.getNoteableData?.id,
+ issueId: this.getNoteableData?.id,
},
});
},
diff --git a/app/assets/javascripts/issues/show/mixins/update.js b/app/assets/javascripts/issues/show/mixins/update.js
index 72be65b426..31b29de580 100644
--- a/app/assets/javascripts/issues/show/mixins/update.js
+++ b/app/assets/javascripts/issues/show/mixins/update.js
@@ -3,7 +3,6 @@ import eventHub from '../event_hub';
export default {
methods: {
updateIssuable() {
- this.formState.updateLoading = true;
eventHub.$emit('update.issuable');
},
},
diff --git a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue
index 88005cccd8..9b36642feb 100644
--- a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue
+++ b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue
@@ -7,6 +7,7 @@ import {
GlAvatarLabeled,
} from '@gitlab/ui';
import { __ } from '~/locale';
+import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import { PROJECTS_PER_PAGE } from '../constants';
import getProjectsQuery from '../graphql/queries/get_projects.query.graphql';
@@ -80,6 +81,7 @@ export default {
i18n: {
selectProjectText: __('Select a project'),
},
+ AVATAR_SHAPE_OPTION_RECT,
};
@@ -107,7 +109,7 @@ export default {
>
-
-
+
+
+
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/browser_support_alert.vue b/app/assets/javascripts/jira_connect/subscriptions/components/browser_support_alert.vue
new file mode 100644
index 0000000000..ea7db5be0c
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/browser_support_alert.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+ {{ content }}
+
+
+
+
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue b/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue
index 3cfbd87ac5..c5b5653524 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue
@@ -46,16 +46,13 @@ export default {
>
- {{
- content
- }}
+ {{ content }}
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/group_item_name.vue b/app/assets/javascripts/jira_connect/subscriptions/components/group_item_name.vue
index e6c172dae9..509a32460b 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/group_item_name.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/group_item_name.vue
@@ -1,5 +1,6 @@
@@ -19,7 +21,12 @@ export default {
-
+
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
index d7ec909cb2..dfed57df7d 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
@@ -24,7 +24,7 @@ export default {
canUseCrypto: AccessorUtilities.canUseCrypto(),
};
},
- mounted() {
+ created() {
window.addEventListener('message', this.handleWindowMessage);
},
beforeDestroy() {
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
index 33126040c1..0251728c89 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
@@ -1,5 +1,5 @@
-
+
@@ -95,5 +95,5 @@ export default {
>{{ __('Unlink') }}
-
+
diff --git a/app/assets/javascripts/jira_connect/subscriptions/index.js b/app/assets/javascripts/jira_connect/subscriptions/index.js
index 320f0f8aa6..3b584b5fe9 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/index.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/index.js
@@ -1,4 +1,4 @@
-import '../../webpack';
+import '~/webpack';
import setConfigs from '@gitlab/ui/dist/config';
import Vue from 'vue';
@@ -48,4 +48,4 @@ export function initJiraConnect() {
});
}
-document.addEventListener('DOMContentLoaded', initJiraConnect);
+initJiraConnect();
diff --git a/app/assets/javascripts/jira_import/components/jira_import_form.vue b/app/assets/javascripts/jira_import/components/jira_import_form.vue
index 1b6e365fdb..af4a26a735 100644
--- a/app/assets/javascripts/jira_import/components/jira_import_form.vue
+++ b/app/assets/javascripts/jira_import/components/jira_import_form.vue
@@ -12,7 +12,7 @@ import {
GlLoadingIcon,
GlSearchBoxByType,
GlSprintf,
- GlTable,
+ GlTableLite,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
@@ -45,7 +45,7 @@ export default {
GlLoadingIcon,
GlSearchBoxByType,
GlSprintf,
- GlTable,
+ GlTableLite,
},
currentUsername: gon.current_username,
dropdownLabel,
@@ -295,7 +295,7 @@ export default {
{{ $options.userMappingMessage }}
-
+
@@ -326,9 +326,9 @@ export default {
-
+
-
+
+import { GlFilteredSearch } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
+import JobStatusToken from './tokens/job_status_token.vue';
+
+export default {
+ tokenTypes: {
+ status: 'status',
+ },
+ components: {
+ GlFilteredSearch,
+ },
+ computed: {
+ tokens() {
+ return [
+ {
+ type: this.$options.tokenTypes.status,
+ icon: 'status',
+ title: s__('Jobs|Status'),
+ unique: true,
+ token: JobStatusToken,
+ operators: OPERATOR_IS_ONLY,
+ },
+ ];
+ },
+ },
+ methods: {
+ onSubmit(filters) {
+ this.$emit('filterJobsBySearch', filters);
+ },
+ },
+};
+
+
+
+
+
diff --git a/app/assets/javascripts/jobs/components/filtered_search/tokens/job_status_token.vue b/app/assets/javascripts/jobs/components/filtered_search/tokens/job_status_token.vue
new file mode 100644
index 0000000000..aad86ded80
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/filtered_search/tokens/job_status_token.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
{{ findActiveStatus.text }}
+
+
+
+
+
+
+ {{ status.text }}
+
+
+
+
+
diff --git a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
index 753a15871a..f16e0287d5 100644
--- a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
@@ -171,6 +171,7 @@ export default {
data-testid="cancel-button"
icon="cancel"
:title="$options.CANCEL"
+ :aria-label="$options.CANCEL"
:disabled="cancelBtnDisabled"
@click="cancelJob()"
/>
@@ -182,6 +183,7 @@ export default {
v-gl-modal-directive="$options.playJobModalId"
icon="play"
:title="$options.ACTIONS_START_NOW"
+ :aria-label="$options.ACTIONS_START_NOW"
data-testid="play-scheduled"
/>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
-import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { formatDate, getTimeago, durationTimeFormatted } from '~/lib/utils/datetime_utility';
export default {
iconSize: 12,
@@ -10,7 +10,6 @@ export default {
components: {
GlIcon,
},
- mixins: [timeagoMixin],
props: {
job: {
type: Object,
@@ -24,6 +23,15 @@ export default {
duration() {
return this.job?.duration;
},
+ timeFormatted() {
+ return getTimeago().format(this.finishedTime);
+ },
+ tooltipTitle() {
+ return formatDate(this.finishedTime);
+ },
+ durationFormatted() {
+ return durationTimeFormatted(this.duration);
+ },
},
};
@@ -32,18 +40,18 @@ export default {
- {{ durationTimeFormatted(duration) }}
+ {{ durationFormatted }}
- {{ timeFormatted(finishedTime) }}
+ {{ timeFormatted }}
diff --git a/app/assets/javascripts/jobs/components/table/constants.js b/app/assets/javascripts/jobs/components/table/constants.js
index 951d932481..853834ed51 100644
--- a/app/assets/javascripts/jobs/components/table/constants.js
+++ b/app/assets/javascripts/jobs/components/table/constants.js
@@ -4,6 +4,9 @@ import { DEFAULT_TH_CLASSES } from '~/lib/utils/constants';
/* Error constants */
export const POST_FAILURE = 'post_failure';
export const DEFAULT = 'default';
+export const RAW_TEXT_WARNING = s__(
+ 'Jobs|Raw text search is not currently supported for the jobs filtered search feature. Please use the available search tokens.',
+);
/* Job Status Constants */
export const JOB_SCHEDULED = 'SCHEDULED';
diff --git a/app/assets/javascripts/jobs/components/table/graphql/cache_config.js b/app/assets/javascripts/jobs/components/table/graphql/cache_config.js
index b9946925c9..8bcd7ffd10 100644
--- a/app/assets/javascripts/jobs/components/table/graphql/cache_config.js
+++ b/app/assets/javascripts/jobs/components/table/graphql/cache_config.js
@@ -13,16 +13,40 @@ export default {
merge(existing = {}, incoming, { args = {} }) {
let nodes;
+ const areNodesEqual = isEqual(existing.nodes, incoming.nodes);
+ const statuses = Array.isArray(args.statuses) ? [...args.statuses] : args.statuses;
+ const { pageInfo } = incoming;
+
if (Object.keys(existing).length !== 0 && isEqual(existing?.statuses, args?.statuses)) {
- nodes = [...existing.nodes, ...incoming.nodes];
+ if (areNodesEqual) {
+ if (incoming.pageInfo.hasNextPage) {
+ nodes = [...existing.nodes, ...incoming.nodes];
+ } else {
+ nodes = [...incoming.nodes];
+ }
+ } else {
+ if (!existing.pageInfo?.hasNextPage) {
+ nodes = [...incoming.nodes];
+
+ return {
+ nodes,
+ statuses,
+ pageInfo,
+ count: incoming.count,
+ };
+ }
+
+ nodes = [...existing.nodes, ...incoming.nodes];
+ }
} else {
nodes = [...incoming.nodes];
}
return {
nodes,
- statuses: Array.isArray(args.statuses) ? [...args.statuses] : args.statuses,
- pageInfo: incoming.pageInfo,
+ statuses,
+ pageInfo,
+ count: incoming.count,
};
},
},
diff --git a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
index 151e49af87..f3ca958b3c 100644
--- a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
+++ b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
@@ -3,6 +3,7 @@ query getJobs($fullPath: ID!, $after: String, $statuses: [CiJobStatus!]) {
id
__typename
jobs(after: $after, first: 30, statuses: $statuses) {
+ count
pageInfo {
endCursor
hasNextPage
diff --git a/app/assets/javascripts/jobs/components/table/index.js b/app/assets/javascripts/jobs/components/table/index.js
index 1b9c7cdcfd..88da1169e0 100644
--- a/app/assets/javascripts/jobs/components/table/index.js
+++ b/app/assets/javascripts/jobs/components/table/index.js
@@ -27,7 +27,6 @@ export default (containerId = 'js-jobs-table') => {
const {
fullPath,
- jobCounts,
jobStatuses,
pipelineEditorPath,
emptyStateSvgPath,
@@ -42,7 +41,6 @@ export default (containerId = 'js-jobs-table') => {
fullPath,
pipelineEditorPath,
jobStatuses: JSON.parse(jobStatuses),
- jobCounts: JSON.parse(jobCounts),
admin: parseBoolean(admin),
},
render(createElement) {
diff --git a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue
index 864e322eec..3ea50dfb7a 100644
--- a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue
+++ b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue
@@ -1,26 +1,34 @@
-
+
{{ tab.text }}
- {{ tab.count }}
+
+
+
+ {{ tab.count }}
+
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index b1ddede8fe..1afc1c9a59 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -1,5 +1,5 @@
-
+
+
+
+
+
+ {{ content }}
+
+
+
+
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
index c1ec523574..484903354e 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
@@ -8,11 +8,13 @@ import ListItem from '~/vue_shared/components/registry/list_item.vue';
import {
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
LIST_DELETE_BUTTON_DISABLED,
+ LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION,
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_FAILED_DELETED_STATUS,
+ IMAGE_MIGRATING_STATE,
ROOT_IMAGE_TEXT,
} from '../../constants/index';
import DeleteButton from '../delete_button.vue';
@@ -32,6 +34,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ inject: ['config'],
props: {
item: {
type: Object,
@@ -44,13 +47,12 @@ export default {
},
},
i18n: {
- LIST_DELETE_BUTTON_DISABLED,
REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION,
},
computed: {
disabledDelete() {
- return !this.item.canDelete || this.deleting;
+ return !this.item.canDelete || this.deleting || this.migrating;
},
id() {
return getIdFromGraphQLId(this.item.id);
@@ -58,6 +60,9 @@ export default {
deleting() {
return this.item.status === IMAGE_DELETE_SCHEDULED_STATUS;
},
+ migrating() {
+ return this.item.migrationState === IMAGE_MIGRATING_STATE;
+ },
failedDelete() {
return this.item.status === IMAGE_FAILED_DELETED_STATUS;
},
@@ -83,6 +88,11 @@ export default {
routerLinkEvent() {
return this.deleting ? '' : 'click';
},
+ deleteButtonTooltipTitle() {
+ return this.migrating
+ ? LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION
+ : LIST_DELETE_BUTTON_DISABLED;
+ },
},
};
@@ -144,8 +154,9 @@ export default {
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue
index 6d2ff9ea7b..154e176dc6 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue
@@ -1,4 +1,5 @@
-
+
+ {{ deleteCacheAlertMessage }}
+
+
+
+
+ {{ $options.i18n.clearCache }}
+
+
+
+
+
+ {{ modalConfirmationMessageWithCount }}
+
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
index 9241dccb2d..5c43b10a5e 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
@@ -1,4 +1,4 @@
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getDependencyProxyDetails(
$fullPath: ID!
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/components/list/harbor_list.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/components/list/harbor_list.vue
new file mode 100644
index 0000000000..c1b5367c96
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/components/list/harbor_list.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/components/list/harbor_list_header.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/components/list/harbor_list_header.vue
new file mode 100644
index 0000000000..086b9c73d7
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/components/list/harbor_list_header.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/components/list/harbor_list_row.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/components/list/harbor_list_row.vue
new file mode 100644
index 0000000000..258472fe16
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/components/list/harbor_list_row.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+ {{ imageName }}
+
+
+
+
+
+
+
+
+
+ {{ item.artifactCount }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/constants/common.js b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/common.js
new file mode 100644
index 0000000000..a789182175
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/common.js
@@ -0,0 +1,29 @@
+import { s__, __ } from '~/locale';
+
+export const ROOT_IMAGE_TEXT = s__('HarborRegistry|Root image');
+export const NAME_SORT_FIELD = { orderBy: 'NAME', label: __('Name') };
+
+export const ASCENDING_ORDER = 'asc';
+export const DESCENDING_ORDER = 'desc';
+
+export const NAME_SORT_FIELD_KEY = 'name';
+export const UPDATED_SORT_FIELD_KEY = 'update_time';
+export const CREATED_SORT_FIELD_KEY = 'creation_time';
+
+export const SORT_FIELD_MAPPING = {
+ NAME: NAME_SORT_FIELD_KEY,
+ UPDATED: UPDATED_SORT_FIELD_KEY,
+ CREATED: CREATED_SORT_FIELD_KEY,
+};
+
+/* eslint-disable @gitlab/require-i18n-strings */
+export const dockerBuildCommand = (repositoryUrl) => {
+ return `docker build -t ${repositoryUrl} .`;
+};
+export const dockerPushCommand = (repositoryUrl) => {
+ return `docker push ${repositoryUrl}`;
+};
+export const dockerLoginCommand = (registryHostUrlWithPort) => {
+ return `docker login ${registryHostUrlWithPort}`;
+};
+/* eslint-enable @gitlab/require-i18n-strings */
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/constants/details.js b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/details.js
new file mode 100644
index 0000000000..2519f6b74a
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/details.js
@@ -0,0 +1,39 @@
+import { s__, __ } from '~/locale';
+
+export const UPDATED_AT = s__('HarborRegistry|Last updated %{time}');
+
+export const MISSING_OR_DELETED_IMAGE_TITLE = s__(
+ 'HarborRegistry|The image repository could not be found.',
+);
+
+export const MISSING_OR_DELETED_IMAGE_MESSAGE = s__(
+ 'HarborRegistry|The requested image repository does not exist or has been deleted. If you think this is an error, try refreshing the page.',
+);
+
+export const NO_TAGS_TITLE = s__('HarborRegistry|This image has no active tags');
+
+export const NO_TAGS_MESSAGE = s__(
+ `HarborRegistry|The last tag related to this image was recently removed.
+This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process.
+If you have any questions, contact your administrator.`,
+);
+
+export const NO_TAGS_MATCHING_FILTERS_TITLE = s__('HarborRegistry|The filter returned no results');
+
+export const NO_TAGS_MATCHING_FILTERS_DESCRIPTION = s__(
+ 'HarborRegistry|Please try different search criteria',
+);
+
+export const DIGEST_LABEL = s__('HarborRegistry|Digest: %{imageId}');
+export const CREATED_AT_LABEL = s__('HarborRegistry|Published %{timeInfo}');
+export const PUBLISHED_DETAILS_ROW_TEXT = s__(
+ 'HarborRegistry|Published to the %{repositoryPath} image repository at %{time} on %{date}',
+);
+export const MANIFEST_DETAILS_ROW_TEST = s__('HarborRegistry|Manifest digest: %{digest}');
+export const CONFIGURATION_DETAILS_ROW_TEST = s__('HarborRegistry|Configuration digest: %{digest}');
+export const MISSING_MANIFEST_WARNING_TOOLTIP = s__(
+ 'HarborRegistry|Invalid tag: missing manifest digest',
+);
+
+export const NOT_AVAILABLE_TEXT = __('N/A');
+export const NOT_AVAILABLE_SIZE = __('0 bytes');
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/constants/index.js b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/index.js
new file mode 100644
index 0000000000..22f462e0b9
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/index.js
@@ -0,0 +1,3 @@
+export * from './common';
+export * from './list';
+export * from './details';
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/constants/list.js b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/list.js
new file mode 100644
index 0000000000..a6cd59918f
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/constants/list.js
@@ -0,0 +1,33 @@
+import { s__, __, n__ } from '~/locale';
+import { NAME_SORT_FIELD } from './common';
+
+// Translations strings
+
+export const HARBOR_REGISTRY_TITLE = s__('HarborRegistry|Harbor Registry');
+
+export const CONNECTION_ERROR_TITLE = s__('HarborRegistry|Harbor connection error');
+export const CONNECTION_ERROR_MESSAGE = s__(
+ `HarborRegistry|We are having trouble connecting to the Harbor Registry. Please try refreshing the page. If this error persists, please review %{docLinkStart}the troubleshooting documentation%{docLinkEnd}.`,
+);
+export const LIST_INTRO_TEXT = s__(
+ `HarborRegistry|With the Harbor Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}`,
+);
+
+export const imagesCountInfoText = (count) => {
+ return n__(
+ 'HarborRegistry|%{count} Image repository',
+ 'HarborRegistry|%{count} Image repositories',
+ count,
+ );
+};
+
+export const EMPTY_RESULT_TITLE = s__('HarborRegistry|Sorry, your filter produced no results.');
+export const EMPTY_RESULT_MESSAGE = s__(
+ 'HarborRegistry|To widen your search, change or remove the filters above.',
+);
+
+export const SORT_FIELDS = [
+ { orderBy: 'UPDATED', label: __('Updated') },
+ { orderBy: 'CREATED', label: __('Created') },
+ NAME_SORT_FIELD,
+];
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/index.js b/app/assets/javascripts/packages_and_registries/harbor_registry/index.js
new file mode 100644
index 0000000000..ecfefead61
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/index.js
@@ -0,0 +1,78 @@
+import { GlToast } from '@gitlab/ui';
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import PerformancePlugin from '~/performance/vue_performance_plugin';
+import Translate from '~/vue_shared/translate';
+import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
+import { renderBreadcrumb } from '~/packages_and_registries/shared/utils';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import {
+ dockerBuildCommand,
+ dockerPushCommand,
+ dockerLoginCommand,
+} from '~/packages_and_registries/harbor_registry/constants';
+import createRouter from './router';
+import HarborRegistryExplorer from './pages/index.vue';
+
+Vue.use(Translate);
+Vue.use(GlToast);
+
+Vue.use(PerformancePlugin, {
+ components: [
+ 'RegistryListPage',
+ 'ListHeader',
+ 'ImageListRow',
+ 'RegistryDetailsPage',
+ 'DetailsHeader',
+ 'TagsList',
+ ],
+});
+
+export default (id) => {
+ const el = document.getElementById(id);
+
+ if (!el) {
+ return null;
+ }
+
+ const { endpoint, connectionError, invalidPathError, isGroupPage, ...config } = el.dataset;
+
+ const breadCrumbState = Vue.observable({
+ name: '',
+ updateName(value) {
+ this.name = value;
+ },
+ });
+
+ const router = createRouter(endpoint, breadCrumbState);
+
+ const attachMainComponent = () => {
+ return new Vue({
+ el,
+ router,
+ provide() {
+ return {
+ breadCrumbState,
+ config: {
+ ...config,
+ connectionError: parseBoolean(connectionError),
+ invalidPathError: parseBoolean(invalidPathError),
+ isGroupPage: parseBoolean(isGroupPage),
+ helpPagePath: helpPagePath('user/packages/container_registry/index'),
+ },
+ dockerBuildCommand: dockerBuildCommand(config.repositoryUrl),
+ dockerPushCommand: dockerPushCommand(config.repositoryUrl),
+ dockerLoginCommand: dockerLoginCommand(config.registryHostUrlWithPort),
+ };
+ },
+ render(createElement) {
+ return createElement(HarborRegistryExplorer);
+ },
+ });
+ };
+
+ return {
+ attachBreadcrumb: renderBreadcrumb(router, null, RegistryBreadcrumb),
+ attachMainComponent,
+ };
+};
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/mock_api.js b/app/assets/javascripts/packages_and_registries/harbor_registry/mock_api.js
new file mode 100644
index 0000000000..50c7df1483
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/mock_api.js
@@ -0,0 +1,200 @@
+const mockRequestFn = (mockData) => {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(mockData);
+ }, 2000);
+ });
+};
+export const harborListResponse = () => {
+ const harborListResponseData = {
+ repositories: [
+ {
+ artifactCount: 1,
+ creationTime: '2022-03-02T06:35:53.205Z',
+ id: 25,
+ name: 'shao/flinkx',
+ projectId: 21,
+ pullCount: 0,
+ updateTime: '2022-03-02T06:35:53.205Z',
+ location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas',
+ },
+ {
+ artifactCount: 1,
+ creationTime: '2022-03-02T06:35:53.205Z',
+ id: 26,
+ name: 'shao/flinkx1',
+ projectId: 21,
+ pullCount: 0,
+ updateTime: '2022-03-02T06:35:53.205Z',
+ location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas',
+ },
+ {
+ artifactCount: 1,
+ creationTime: '2022-03-02T06:35:53.205Z',
+ id: 27,
+ name: 'shao/flinkx2',
+ projectId: 21,
+ pullCount: 0,
+ updateTime: '2022-03-02T06:35:53.205Z',
+ location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas',
+ },
+ ],
+ totalCount: 3,
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ },
+ };
+
+ return mockRequestFn(harborListResponseData);
+};
+
+export const getHarborRegistryImageDetail = () => {
+ const harborRegistryImageDetailData = {
+ artifactCount: 1,
+ creationTime: '2022-03-02T06:35:53.205Z',
+ id: 25,
+ name: 'shao/flinkx',
+ projectId: 21,
+ pullCount: 0,
+ updateTime: '2022-03-02T06:35:53.205Z',
+ location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas',
+ tagsCount: 10,
+ };
+
+ return mockRequestFn(harborRegistryImageDetailData);
+};
+
+export const harborTagsResponse = () => {
+ const harborTagsResponseData = {
+ tags: [
+ {
+ digest: 'sha256:7f386a1844faf341353e1c20f2f39f11f397604fedc475435d13f756eeb235d1',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:02310e655103823920157bc4410ea361dc638bc2cda59667d2cb1f2a988e264c',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:02310e655103823920157bc4410ea361dc638bc2cda59667d2cb1f2a988e264c',
+ name: '02310e655103823920157bc4410ea361dc638bc2cda59667d2cb1f2a988e264c',
+ revision: 'f53bde3d44699e04e11cf15fb415046a0913e2623d878d89bc21adb2cbda5255',
+ shortRevision: 'f53bde3d4',
+ createdAt: '2022-03-02T23:59:05+00:00',
+ totalSize: '6623124',
+ },
+ {
+ digest: 'sha256:4554416b84c4568fe93086620b637064ed029737aabe7308b96d50e3d9d92ed7',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:02deb4dddf177212b50e883d5e4f6c03731fad1a18cd27261736cd9dbba79160',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:02deb4dddf177212b50e883d5e4f6c03731fad1a18cd27261736cd9dbba79160',
+ name: '02deb4dddf177212b50e883d5e4f6c03731fad1a18cd27261736cd9dbba79160',
+ revision: 'e1fe52d8bab66d71bd54a6b8784d3b9edbc68adbd6ea87f5fa44d9974144ef9e',
+ shortRevision: 'e1fe52d8b',
+ createdAt: '2022-02-10T01:09:56+00:00',
+ totalSize: '920760',
+ },
+ {
+ digest: 'sha256:14f37b60e52b9ce0e9f8f7094b311d265384798592f783487c30aaa3d58e6345',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:03bc5971bab1e849ba52a20a31e7273053f22b2ddb1d04bd6b77d53a2635727a',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:03bc5971bab1e849ba52a20a31e7273053f22b2ddb1d04bd6b77d53a2635727a',
+ name: '03bc5971bab1e849ba52a20a31e7273053f22b2ddb1d04bd6b77d53a2635727a',
+ revision: 'c72770c6eb93c421bc496964b4bffc742b1ec2e642cdab876be7afda1856029f',
+ shortRevision: 'c72770c6e',
+ createdAt: '2021-12-22T04:48:48+00:00',
+ totalSize: '48609053',
+ },
+ {
+ digest: 'sha256:e925e3b8277ea23f387ed5fba5e78280cfac7cfb261a78cf046becf7b6a3faae',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:03f495bc5714bff78bb14293320d336afdf47fd47ddff0c3c5f09f8da86d5d19',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:03f495bc5714bff78bb14293320d336afdf47fd47ddff0c3c5f09f8da86d5d19',
+ name: '03f495bc5714bff78bb14293320d336afdf47fd47ddff0c3c5f09f8da86d5d19',
+ revision: '1ac2a43194f4e15166abdf3f26e6ec92215240490b9cac834d63de1a3d87494a',
+ shortRevision: '1ac2a4319',
+ createdAt: '2022-03-09T11:02:27+00:00',
+ totalSize: '35141894',
+ },
+ {
+ digest: 'sha256:7d8303fd5c077787a8c879f8f66b69e2b5605f48ccd3f286e236fb0749fcc1ca',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:05a4e58231e54b70aab2d6f22ba4fbe10e48aa4ddcbfef11c5662241c2ae4fda',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:05a4e58231e54b70aab2d6f22ba4fbe10e48aa4ddcbfef11c5662241c2ae4fda',
+ name: '05a4e58231e54b70aab2d6f22ba4fbe10e48aa4ddcbfef11c5662241c2ae4fda',
+ revision: 'cf8fee086701016e1a84e6824f0c896951fef4cce9d4745459558b87eec3232c',
+ shortRevision: 'cf8fee086',
+ createdAt: '2022-01-21T11:31:43+00:00',
+ totalSize: '48716070',
+ },
+ {
+ digest: 'sha256:b33611cefe20e4a41a6e0dce356a5d7ef3c177ea7536a58652f5b3a4f2f83549',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:093d2746876997723541aec8b88687a4cdb3b5bbb0279c5089b7891317741a9a',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:093d2746876997723541aec8b88687a4cdb3b5bbb0279c5089b7891317741a9a',
+ name: '093d2746876997723541aec8b88687a4cdb3b5bbb0279c5089b7891317741a9a',
+ revision: '1a4b48198b13d55242c5164e64d41c4e9f75b5d9506bc6e0efc1534dd0dd1f15',
+ shortRevision: '1a4b48198',
+ createdAt: '2022-01-21T11:31:51+00:00',
+ totalSize: '6623127',
+ },
+ {
+ digest: 'sha256:d25c3c020e2dbd4711a67b9fe308f4cbb7b0bb21815e722a02f91c570dc5d519',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:09698b3fae81dfd6e02554dbc82930f304a6356c8f541c80e8598a42aed985f7',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:09698b3fae81dfd6e02554dbc82930f304a6356c8f541c80e8598a42aed985f7',
+ name: '09698b3fae81dfd6e02554dbc82930f304a6356c8f541c80e8598a42aed985f7',
+ revision: '03e2e2777dde01c30469ee8c710973dd08a7a4f70494d7dc1583c24b525d7f61',
+ shortRevision: '03e2e2777',
+ createdAt: '2022-03-02T23:58:20+00:00',
+ totalSize: '911377',
+ },
+ {
+ digest: 'sha256:fb760e4d2184e9e8e39d6917534d4610fe01009734698a5653b2de1391ba28f4',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:09b830c3eaf80d547f3b523d8e242a2c411085c349dab86c520f36c7b7644f95',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:09b830c3eaf80d547f3b523d8e242a2c411085c349dab86c520f36c7b7644f95',
+ name: '09b830c3eaf80d547f3b523d8e242a2c411085c349dab86c520f36c7b7644f95',
+ revision: '350e78d60646bf6967244448c6aaa14d21ecb9a0c6cf87e9ff0361cbe59b9012',
+ shortRevision: '350e78d60',
+ createdAt: '2022-01-19T13:49:14+00:00',
+ totalSize: '48710241',
+ },
+ {
+ digest: 'sha256:407250f380cea92729cbc038c420e74900f53b852e11edc6404fe75a0fd2c402',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:0d03504a17b467eafc8c96bde70af26c74bd459a32b7eb2dd189dd6b3c121557',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:0d03504a17b467eafc8c96bde70af26c74bd459a32b7eb2dd189dd6b3c121557',
+ name: '0d03504a17b467eafc8c96bde70af26c74bd459a32b7eb2dd189dd6b3c121557',
+ revision: '76038370b7f3904364891457c4a6a234897255e6b9f45d0a852bf3a7e5257e18',
+ shortRevision: '76038370b',
+ createdAt: '2022-01-24T12:56:22+00:00',
+ totalSize: '280065',
+ },
+ {
+ digest: 'sha256:ada87f25218542951ce6720c27f3d0758e90c2540bd129f5cfb9e15b31e07b07',
+ location:
+ 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:0eb20a4a7cac2ebea821d420b3279654fe550fd8502f1785c1927aa84e5949eb',
+ path:
+ 'gitlab-org/gitlab/gitlab-ee-qa/cache:0eb20a4a7cac2ebea821d420b3279654fe550fd8502f1785c1927aa84e5949eb',
+ name: '0eb20a4a7cac2ebea821d420b3279654fe550fd8502f1785c1927aa84e5949eb',
+ revision: '3d4b49a7bbb36c48bb721f4d0e76e7950bec3878ee29cdfdd6da39f575d6d37f',
+ shortRevision: '3d4b49a7b',
+ createdAt: '2022-02-17T17:37:52+00:00',
+ totalSize: '48655767',
+ },
+ ],
+ totalCount: 10,
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: true,
+ },
+ };
+
+ return mockRequestFn(harborTagsResponseData);
+};
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/details.vue
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/pages/index.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/index.vue
new file mode 100644
index 0000000000..dca63e1a56
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/pages/list.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/list.vue
new file mode 100644
index 0000000000..7aaef2ed57
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/list.vue
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $options.i18n.EMPTY_RESULT_MESSAGE }}
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/router.js b/app/assets/javascripts/packages_and_registries/harbor_registry/router.js
new file mode 100644
index 0000000000..572dd382be
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/router.js
@@ -0,0 +1,35 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import { HARBOR_REGISTRY_TITLE } from './constants/index';
+import List from './pages/list.vue';
+import Details from './pages/details.vue';
+
+Vue.use(VueRouter);
+
+export default function createRouter(base, breadCrumbState) {
+ const router = new VueRouter({
+ base,
+ mode: 'history',
+ routes: [
+ {
+ name: 'list',
+ path: '/',
+ component: List,
+ meta: {
+ nameGenerator: () => HARBOR_REGISTRY_TITLE,
+ root: true,
+ },
+ },
+ {
+ name: 'details',
+ path: '/:id',
+ component: Details,
+ meta: {
+ nameGenerator: () => breadCrumbState.name,
+ },
+ },
+ ],
+ });
+
+ return router;
+}
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
index 488860e5bc..408d34fbe9 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
@@ -26,6 +26,7 @@ export const receivePackagesListSuccess = ({ commit }, { data, headers }) => {
export const requestPackagesList = ({ dispatch, state }, params = {}) => {
dispatch('setLoading', true);
+ // eslint-disable-next-line camelcase
const { page = DEFAULT_PAGE, per_page = DEFAULT_PAGE_SIZE } = params;
const { sort, orderBy } = state.sorting;
const type = state.config.forceTerraform
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
index c27083261b..7a88e04d1f 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
@@ -99,7 +99,6 @@ export default {
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
index 4b91359094..5bde5f08e5 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
@@ -1,5 +1,5 @@
#import "~/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql"
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getPackages(
$fullPath: ID!
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
index 7be3bba7ca..854c88b2ad 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
@@ -9,7 +9,6 @@ import {
UNAVAILABLE_ADMIN_FEATURE_TEXT,
} from '~/packages_and_registries/settings/project/constants';
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
-import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
import SettingsForm from './settings_form.vue';
@@ -18,19 +17,11 @@ export default {
components: {
SettingsBlock,
SettingsForm,
- CleanupPolicyEnabledAlert,
GlAlert,
GlSprintf,
GlLink,
},
- inject: [
- 'projectPath',
- 'isAdmin',
- 'adminSettingsPath',
- 'enableHistoricEntries',
- 'helpPagePath',
- 'showCleanupPolicyOnAlert',
- ],
+ inject: ['projectPath', 'isAdmin', 'adminSettingsPath', 'enableHistoricEntries', 'helpPagePath'],
i18n: {
UNAVAILABLE_FEATURE_TITLE,
UNAVAILABLE_FEATURE_INTRO_TEXT,
@@ -87,7 +78,6 @@ export default {
-
{{ __('Clean up image tags') }}
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js
index 2a3e2c28fa..17c3307366 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js
+++ b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js
@@ -20,7 +20,6 @@ export default () => {
adminSettingsPath,
tagsRegexHelpPagePath,
helpPagePath,
- showCleanupPolicyOnAlert,
} = el.dataset;
return new Vue({
el,
@@ -35,7 +34,6 @@ export default () => {
adminSettingsPath,
tagsRegexHelpPagePath,
helpPagePath,
- showCleanupPolicyOnAlert: parseBoolean(showCleanupPolicyOnAlert),
},
render(createElement) {
return createElement('registry-settings-app', {});
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue b/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue
deleted file mode 100644
index d51c62e062..0000000000
--- a/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
- {{
- content
- }}
-
-
-
-
-
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue
index 79381f8200..cc345fda7e 100644
--- a/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue
+++ b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue
@@ -13,7 +13,8 @@ export default {
props: {
title: {
type: String,
- required: true,
+ default: '',
+ required: false,
},
isLoading: {
type: Boolean,
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
index aa2f539b6e..e15766a783 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -22,7 +22,10 @@ export default {
this.prepareData = prepareData;
this.successCallback = successCallback;
this.errorCallback = errorCallback;
- this.loading = $(`${container} .loading`).first();
+ this.$container = $(container);
+ this.$loading = this.$container.length
+ ? this.$container.find('.loading').first()
+ : $('.loading').first();
if (preload) {
this.offset = 0;
this.getOld();
@@ -31,7 +34,7 @@ export default {
},
getOld() {
- this.loading.show();
+ this.$loading.show();
const url = $('.content_list').data('href') || removeParams(['limit', 'offset']);
axios
@@ -49,11 +52,11 @@ export default {
if (!this.disable && !this.isScrollable()) {
this.getOld();
} else {
- this.loading.hide();
+ this.$loading.hide();
}
})
.catch((err) => this.errorCallback(err))
- .finally(() => this.loading.hide());
+ .finally(() => this.$loading.hide());
},
append(count, html) {
@@ -83,8 +86,12 @@ export default {
fireOnce: true,
ceaseFire: () => this.disable === true,
callback: () => {
- if (!this.loading.is(':visible')) {
- this.loading.show();
+ if (this.$container.length && !this.$container.is(':visible')) {
+ return;
+ }
+
+ if (!this.$loading.is(':visible')) {
+ this.$loading.show();
this.getOld();
}
},
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
index e78b3f9ec9..29e92a8aba 100644
--- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
+++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import { parseBoolean } from '~/lib/utils/common_utils';
-import { truncate } from '../../../lib/utils/text_utility';
+import { truncate } from '~/lib/utils/text_utility';
const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
diff --git a/app/assets/javascripts/pages/admin/admin.js b/app/assets/javascripts/pages/admin/admin.js
index 6b7bfbf217..e6c1d6d147 100644
--- a/app/assets/javascripts/pages/admin/admin.js
+++ b/app/assets/javascripts/pages/admin/admin.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import { refreshCurrentPage } from '../../lib/utils/url_utility';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
export default function adminInit() {
$('input#user_force_random_password').on('change', function randomPasswordClick() {
diff --git a/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js b/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js
index 67eee2c320..7c81cf80dc 100644
--- a/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js
+++ b/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js
@@ -1,6 +1,6 @@
import createFlash from '~/flash';
-import axios from '../../../lib/utils/axios_utils';
-import { __ } from '../../../locale';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
export default class PayloadDownloader {
constructor(trigger) {
diff --git a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js
index c017cf0afa..ae08806fe4 100644
--- a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js
+++ b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js
@@ -1,6 +1,6 @@
import createFlash from '~/flash';
-import axios from '../../../lib/utils/axios_utils';
-import { __ } from '../../../locale';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
export default class PayloadPreviewer {
constructor(trigger) {
diff --git a/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js b/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js
index 70b896f637..a50d8de0e8 100644
--- a/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js
+++ b/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js
@@ -23,6 +23,7 @@ export default function initSignupRestrictions(elementSelector = '#js-signup-for
return new Vue({
el,
+ name: 'SignupRestrictions',
provide: {
...parsedDataset,
},
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index 2a7e6a45cd..18ba89f885 100644
--- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
@@ -2,52 +2,41 @@ import $ from 'jquery';
import { debounce } from 'lodash';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import { textColorForBackground } from '~/lib/utils/color_utils';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { __ } from '~/locale';
export default () => {
- const $broadcastMessageColor = $('.js-broadcast-message-color');
+ const $broadcastMessageTheme = $('.js-broadcast-message-theme');
const $broadcastMessageType = $('.js-broadcast-message-type');
- const $broadcastBannerMessagePreview = $('.js-broadcast-banner-message-preview');
+ const $broadcastBannerMessagePreview = $('.js-broadcast-banner-message-preview [role="alert"]');
const $broadcastMessage = $('.js-broadcast-message-message');
- const $jsBroadcastMessagePreview = $('.js-broadcast-message-preview');
+ const $jsBroadcastMessagePreview = $('#broadcast-message-preview');
const reloadPreview = function reloadPreview() {
const previewPath = $broadcastMessage.data('previewPath');
const message = $broadcastMessage.val();
const type = $broadcastMessageType.val();
+ const theme = $broadcastMessageTheme.val();
- if (message === '') {
- $jsBroadcastMessagePreview.text(__('Your message here'));
- } else {
- axios
- .post(previewPath, {
- broadcast_message: {
- message,
- broadcast_type: type,
- },
- })
- .then(({ data }) => {
- $jsBroadcastMessagePreview.html(data.message);
- })
- .catch(() =>
- createFlash({
- message: __('An error occurred while rendering preview broadcast message'),
- }),
- );
- }
+ axios
+ .post(previewPath, {
+ broadcast_message: {
+ message,
+ broadcast_type: type,
+ theme,
+ },
+ })
+ .then(({ data }) => {
+ $jsBroadcastMessagePreview.html(data);
+ })
+ .catch(() =>
+ createFlash({
+ message: __('An error occurred while rendering preview broadcast message'),
+ }),
+ );
};
- $broadcastMessageColor.on('input', function onMessageColorInput() {
- const previewColor = $(this).val();
- $broadcastBannerMessagePreview.css('background-color', previewColor);
- });
-
- $('input#broadcast_message_font').on('input', function onMessageFontInput() {
- const previewColor = $(this).val();
- $broadcastBannerMessagePreview.css('color', previewColor);
- });
+ $broadcastMessageTheme.on('change', reloadPreview);
$broadcastMessageType.on('change', () => {
const $broadcastMessageColorFormGroup = $('.js-broadcast-message-background-color-form-group');
@@ -68,37 +57,4 @@ export default () => {
reloadPreview();
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
);
-
- const updateColorPreview = () => {
- const selectedBackgroundColor = $broadcastMessageColor.val();
- const contrastTextColor = textColorForBackground(selectedBackgroundColor);
-
- // save contrastTextColor to hidden input field
- $('input.text-font-color').val(contrastTextColor);
-
- // Updates the preview color with the hex-color input
- const selectedColorStyle = {
- backgroundColor: selectedBackgroundColor,
- color: contrastTextColor,
- };
-
- $('.label-color-preview').css(selectedColorStyle);
-
- return $jsBroadcastMessagePreview.css(selectedColorStyle);
- };
-
- const setSuggestedColor = (e) => {
- const color = $(e.currentTarget).data('color');
- $broadcastMessageColor
- .val(color)
- // Notify the form, that color has changed
- .trigger('input');
- // Only banner supports colors
- if ($broadcastMessageType === 'banner') {
- updateColorPreview();
- }
- return e.preventDefault();
- };
-
- $(document).on('click', '.suggest-colors a', setSuggestedColor);
};
diff --git a/app/assets/javascripts/pages/admin/groups/new/index.js b/app/assets/javascripts/pages/admin/groups/new/index.js
index 1630cfb825..710d2d72f4 100644
--- a/app/assets/javascripts/pages/admin/groups/new/index.js
+++ b/app/assets/javascripts/pages/admin/groups/new/index.js
@@ -1,6 +1,6 @@
import initFilePickers from '~/file_pickers';
-import BindInOut from '../../../../behaviors/bind_in_out';
-import Group from '../../../../group';
+import BindInOut from '~/behaviors/bind_in_out';
+import Group from '~/group';
(() => {
BindInOut.initAll();
diff --git a/app/assets/javascripts/pages/admin/index.js b/app/assets/javascripts/pages/admin/index.js
index f0f85b82e2..a249864fa3 100644
--- a/app/assets/javascripts/pages/admin/index.js
+++ b/app/assets/javascripts/pages/admin/index.js
@@ -1,6 +1,6 @@
import initGitlabVersionCheck from '~/gitlab_version_check';
-import initAdminStatisticsPanel from '../../admin/statistics_panel/index';
-import initVueAlerts from '../../vue_alerts';
+import initAdminStatisticsPanel from '~/admin/statistics_panel/index';
+import initVueAlerts from '~/vue_alerts';
import initAdmin from './admin';
initVueAlerts();
diff --git a/app/assets/javascripts/pages/groups/clusters/index/index.js b/app/assets/javascripts/pages/groups/clusters/index/index.js
index a99e0dfa4f..a1ba920b32 100644
--- a/app/assets/javascripts/pages/groups/clusters/index/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/index/index.js
@@ -1,8 +1,6 @@
import initClustersListApp from '~/clusters_list';
import PersistentUserCallout from '~/persistent_user_callout';
-document.addEventListener('DOMContentLoaded', () => {
- const callout = document.querySelector('.gcp-signup-offer');
- PersistentUserCallout.factory(callout);
- initClustersListApp();
-});
+const callout = document.querySelector('.gcp-signup-offer');
+PersistentUserCallout.factory(callout);
+initClustersListApp();
diff --git a/app/assets/javascripts/pages/groups/crm/contacts/index.js b/app/assets/javascripts/pages/groups/crm/contacts/index.js
index a595246957..6af47621c1 100644
--- a/app/assets/javascripts/pages/groups/crm/contacts/index.js
+++ b/app/assets/javascripts/pages/groups/crm/contacts/index.js
@@ -1,3 +1,3 @@
-import initCrmContactsApp from '~/crm/contacts_bundle';
+import initCrmContactsApp from '~/crm/contacts/bundle';
initCrmContactsApp();
diff --git a/app/assets/javascripts/pages/groups/crm/organizations/index.js b/app/assets/javascripts/pages/groups/crm/organizations/index.js
index 16479b43d5..2ad0904688 100644
--- a/app/assets/javascripts/pages/groups/crm/organizations/index.js
+++ b/app/assets/javascripts/pages/groups/crm/organizations/index.js
@@ -1,3 +1,3 @@
-import initCrmOrganizationsApp from '~/crm/organizations_bundle';
+import initCrmOrganizationsApp from '~/crm/organizations/bundle';
initCrmOrganizationsApp();
diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
index 96487e14e3..58ca195d7b 100644
--- a/app/assets/javascripts/pages/groups/edit/index.js
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -10,21 +10,19 @@ import initSearchSettings from '~/search_settings';
import initSettingsPanels from '~/settings_panels';
import initConfirmDanger from '~/init_confirm_danger';
-document.addEventListener('DOMContentLoaded', () => {
- initFilePickers();
- initConfirmDanger();
- initSettingsPanels();
- initTransferGroupForm();
- dirtySubmitFactory(
- document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'),
- );
- mountBadgeSettings(GROUP_BADGE);
+initFilePickers();
+initConfirmDanger();
+initSettingsPanels();
+initTransferGroupForm();
+dirtySubmitFactory(
+ document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'),
+);
+mountBadgeSettings(GROUP_BADGE);
- // Initialize Subgroups selector
- groupsSelect();
+// Initialize Subgroups selector
+groupsSelect();
- projectSelect();
+projectSelect();
- initSearchSettings();
- initCascadingSettingsLockPopovers();
-});
+initSearchSettings();
+initCascadingSettingsLockPopovers();
diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js
index 280b544af3..79ac31f165 100644
--- a/app/assets/javascripts/pages/groups/group_members/index.js
+++ b/app/assets/javascripts/pages/groups/group_members/index.js
@@ -12,9 +12,16 @@ const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions'];
initMembersApp(document.querySelector('.js-group-members-list-app'), {
[MEMBER_TYPES.user]: {
- tableFields: SHARED_FIELDS.concat(['source', 'granted']),
+ tableFields: SHARED_FIELDS.concat(['source', 'granted', 'userCreatedAt', 'lastActivityOn']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
- tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
+ tableSortableFields: [
+ 'account',
+ 'granted',
+ 'maxRole',
+ 'lastSignIn',
+ 'userCreatedAt',
+ 'lastActivityOn',
+ ],
requestFormatter: groupMemberRequestFormatter,
filteredSearchBar: {
show: true,
@@ -25,12 +32,25 @@ initMembersApp(document.querySelector('.js-group-members-list-app'), {
},
},
[MEMBER_TYPES.group]: {
- tableFields: SHARED_FIELDS.concat('granted'),
+ tableFields: gon?.features?.groupMemberInheritedGroup
+ ? SHARED_FIELDS.concat(['source', 'granted'])
+ : SHARED_FIELDS.concat(['granted']),
tableAttrs: {
table: { 'data-qa-selector': 'groups_list' },
tr: { 'data-qa-selector': 'group_row' },
},
requestFormatter: groupLinkRequestFormatter,
+ ...(gon?.features?.groupMemberInheritedGroup
+ ? {
+ filteredSearchBar: {
+ show: true,
+ tokens: ['with_inherited_permissions'],
+ searchParam: 'search_groups',
+ placeholder: s__('Members|Filter groups'),
+ recentSearchesStorageKey: 'group_links_members',
+ },
+ }
+ : {}),
},
[MEMBER_TYPES.invite]: {
tableFields: SHARED_FIELDS.concat('invited'),
diff --git a/app/assets/javascripts/pages/groups/harbor/repositories/index.js b/app/assets/javascripts/pages/groups/harbor/repositories/index.js
new file mode 100644
index 0000000000..0ecce44be5
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/harbor/repositories/index.js
@@ -0,0 +1,8 @@
+import HarborRegistryExplorer from '~/packages_and_registries/harbor_registry/index';
+
+const explorer = HarborRegistryExplorer('js-harbor-registry-list-group');
+
+if (explorer) {
+ explorer.attachBreadcrumb();
+ explorer.attachMainComponent();
+}
diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
index 0ec382983a..9a4054eb11 100644
--- a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
+++ b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
@@ -111,7 +111,7 @@ export default {
},
getFullDestinationUrl(params) {
- return joinPaths(gon.relative_url_root || '', this.getDestinationUrl(params));
+ return joinPaths(gon.relative_url_root || '', '/', this.getDestinationUrl(params));
},
},
@@ -161,7 +161,7 @@ export default {
>
- {{ item.failures }}
+ {{ item.failures }}
+import { GlLoadingIcon } from '@gitlab/ui';
+import API from '~/api';
+import { createAlert } from '~/flash';
+import { DEFAULT_ERROR } from '../utils/error_messages';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ },
+ props: {
+ id: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ loading: true,
+ error: null,
+ };
+ },
+ async mounted() {
+ try {
+ const {
+ data: { import_error: importError },
+ } = await API.project(this.id);
+ this.error = importError;
+ } catch (e) {
+ createAlert({ message: DEFAULT_ERROR });
+ this.error = null;
+ } finally {
+ this.loading = false;
+ }
+ },
+};
+
+
+
+ {{ error || s__('BulkImport|No additional information provided.') }}
+
diff --git a/app/assets/javascripts/pages/import/history/components/import_history_app.vue b/app/assets/javascripts/pages/import/history/components/import_history_app.vue
new file mode 100644
index 0000000000..557e25f66e
--- /dev/null
+++ b/app/assets/javascripts/pages/import/history/components/import_history_app.vue
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+ {{ s__('BulkImport|Project import history') }}
+
+
+
+
+
+
+
+
+
+ {{ item.import_url }}
+
+
+ {{ item.import_url }}
+
+ {{
+ s__('BulkImport|Template / File-based import / GitLab Migration')
+ }}
+
+
+
+ {{ item.path_with_namespace }}
+
+
+
+
+
+
+
+ {{ __('Details') }}
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/pages/import/history/index.js b/app/assets/javascripts/pages/import/history/index.js
new file mode 100644
index 0000000000..d540272c26
--- /dev/null
+++ b/app/assets/javascripts/pages/import/history/index.js
@@ -0,0 +1,21 @@
+import Vue from 'vue';
+import ImportHistoryApp from './components/import_history_app.vue';
+
+function mountImportHistoryApp(mountElement) {
+ if (!mountElement) return undefined;
+
+ return new Vue({
+ el: mountElement,
+ name: 'ImportHistoryRoot',
+ provide: {
+ assets: {
+ gitlabLogo: mountElement.dataset.logo,
+ },
+ },
+ render(createElement) {
+ return createElement(ImportHistoryApp);
+ },
+ });
+}
+
+mountImportHistoryApp(document.querySelector('#import-history-mount-element'));
diff --git a/app/assets/javascripts/pages/import/history/utils/error_messages.js b/app/assets/javascripts/pages/import/history/utils/error_messages.js
new file mode 100644
index 0000000000..24669e22ad
--- /dev/null
+++ b/app/assets/javascripts/pages/import/history/utils/error_messages.js
@@ -0,0 +1,3 @@
+import { __ } from '~/locale';
+
+export const DEFAULT_ERROR = __('Something went wrong on our end.');
diff --git a/app/assets/javascripts/pages/profiles/preferences/show/index.js b/app/assets/javascripts/pages/profiles/preferences/show/index.js
index d489ed80f4..7693943468 100644
--- a/app/assets/javascripts/pages/profiles/preferences/show/index.js
+++ b/app/assets/javascripts/pages/profiles/preferences/show/index.js
@@ -1,3 +1,5 @@
import initProfilePreferences from '~/profile/preferences/profile_preferences_bundle';
+import initProfilePreferencesDiffsColors from '~/profile/preferences/profile_preferences_diffs_colors';
-document.addEventListener('DOMContentLoaded', initProfilePreferences);
+initProfilePreferences();
+initProfilePreferencesDiffsColors();
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index c6a76df7bd..eca3cf7ab1 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -1,5 +1,6 @@
/* eslint-disable no-new */
import $ from 'jquery';
+import Vue from 'vue';
import loadAwardsHandler from '~/awards_handler';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import Diff from '~/diff';
@@ -14,6 +15,7 @@ import { initCommitBoxInfo } from '~/projects/commit_box/info';
import syntaxHighlight from '~/syntax_highlight';
import ZenMode from '~/zen_mode';
import '~/sourcegraph/load';
+import DiffStats from '~/diffs/components/diff_stats.vue';
const hasPerfBar = document.querySelector('.with-performance-bar');
const performanceHeight = hasPerfBar ? 35 : 0;
@@ -25,6 +27,33 @@ initCommitBoxInfo();
initDeprecatedNotes();
+const loadDiffStats = () => {
+ const diffStatsElements = document.querySelectorAll('#js-diff-stats');
+
+ if (diffStatsElements.length) {
+ diffStatsElements.forEach((diffStatsEl) => {
+ const { addedLines, removedLines, oldSize, newSize, viewerName } = diffStatsEl.dataset;
+
+ new Vue({
+ el: diffStatsEl,
+ render(createElement) {
+ return createElement(DiffStats, {
+ props: {
+ diffFile: {
+ old_size: oldSize,
+ new_size: newSize,
+ viewer: { name: viewerName },
+ },
+ addedLines: Number(addedLines),
+ removedLines: Number(removedLines),
+ },
+ });
+ },
+ });
+ });
+ }
+};
+
const filesContainer = $('.js-diffs-batch');
if (filesContainer.length) {
@@ -37,12 +66,15 @@ if (filesContainer.length) {
syntaxHighlight(filesContainer);
handleLocationHash();
new Diff();
+ loadDiffStats();
})
.catch(() => {
createFlash({ message: __('An error occurred while retrieving diff files') });
});
} else {
new Diff();
+ loadDiffStats();
}
+
loadAwardsHandler();
initCommitActions();
diff --git a/app/assets/javascripts/pages/projects/harbor/repositories/index.js b/app/assets/javascripts/pages/projects/harbor/repositories/index.js
new file mode 100644
index 0000000000..efbe24ac34
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/harbor/repositories/index.js
@@ -0,0 +1,8 @@
+import HarborRegistryExplorer from '~/packages_and_registries/harbor_registry/index';
+
+const explorer = HarborRegistryExplorer('js-harbor-registry-list-project');
+
+if (explorer) {
+ explorer.attachBreadcrumb();
+ explorer.attachMainComponent();
+}
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index 8ec6e5e66b..7380055cbb 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,5 +1,5 @@
-import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
-import initTerraformNotification from '../../projects/terraform_notification';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
+import initTerraformNotification from '~/projects/terraform_notification';
import { initSidebarTracking } from '../shared/nav/sidebar_tracking';
import Project from './project';
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
index 67962d69fa..db9ef4df8a 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
@@ -127,8 +127,12 @@ export default {
-
-
+
+
-
-
-
+
+
+
+
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
index 573f996a25..1667f2c357 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
@@ -1,16 +1,25 @@
-
-
- {{ $options.i18n.ACTION_LABELS[action].title }}
-
-
- {{ $options.i18n.ACTION_LABELS[action].title }}
-
-
- {{ $options.i18n.ACTION_LABELS[action].title }}
-
-
- - {{ $options.i18n.trialOnly }}
-
+
+ {{ $options.i18n.trialOnly }}
+
+
+
+
+ {{ linkTitle }}
+
+
+ {{ linkTitle }}
+
+
+ {{ linkTitle }}
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
index 1887c48dd1..9ba5e17237 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
@@ -40,6 +40,7 @@ export const ACTION_LABELS = {
trialRequired: true,
section: 'workspace',
position: 4,
+ videoTutorial: 'https://vimeo.com/670896787',
},
requiredMrApprovalsEnabled: {
title: s__('LearnGitLab|Add merge request approval'),
@@ -48,6 +49,7 @@ export const ACTION_LABELS = {
trialRequired: true,
section: 'workspace',
position: 5,
+ videoTutorial: 'https://vimeo.com/670904904',
},
mergeRequestCreated: {
title: s__('LearnGitLab|Submit a merge request'),
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js
index 63357ea9c7..af4a6f8a0c 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
+import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import LearnGitlab from '../components/learn_gitlab.vue';
@@ -24,5 +25,7 @@ function initLearnGitlab() {
});
}
-initLearnGitlab();
initInviteMembersModal();
+initInviteMembersTrigger();
+
+initLearnGitlab();
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
index c548ea9bb8..0e0c1475ed 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
@@ -1,6 +1,5 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import loadAwardsHandler from '~/awards_handler';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import { initPipelineCountListener } from '~/commit/pipelines/utils';
import { initIssuableSidebar } from '~/issuable';
@@ -8,23 +7,16 @@ import StatusBox from '~/issuable/components/status_box.vue';
import createDefaultClient from '~/lib/graphql';
import initSourcegraph from '~/sourcegraph';
import ZenMode from '~/zen_mode';
+import initAwardsApp from '~/emoji/awards_app';
import getStateQuery from './queries/get_state.query.graphql';
export default function initMergeRequestShow() {
- const awardEmojiEl = document.getElementById('js-vue-awards-block');
-
new ZenMode(); // eslint-disable-line no-new
initPipelineCountListener(document.querySelector('#commit-pipeline-table-view'));
new ShortcutsIssuable(true); // eslint-disable-line no-new
initSourcegraph();
initIssuableSidebar();
- if (awardEmojiEl) {
- import('~/emoji/awards_app')
- .then((m) => m.default(awardEmojiEl))
- .catch(() => {});
- } else {
- loadAwardsHandler();
- }
+ initAwardsApp(document.getElementById('js-vue-awards-block'));
const el = document.querySelector('.js-mr-status-box');
const apolloProvider = new VueApollo({
diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js
index 5f2014f163..b88127384d 100644
--- a/app/assets/javascripts/pages/projects/network/network.js
+++ b/app/assets/javascripts/pages/projects/network/network.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import BranchGraph from '../../../network/branch_graph';
+import BranchGraph from '~/network/branch_graph';
const vph = $(window).height() - 250;
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
index ee70ff858b..37e8a316ee 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
@@ -2,8 +2,7 @@
import { GlButton } from '@gitlab/ui';
import Vue from 'vue';
import { getCookie, setCookie, parseBoolean } from '~/lib/utils/common_utils';
-
-import Translate from '../../../../../vue_shared/translate';
+import Translate from '~/vue_shared/translate';
Vue.use(Translate);
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
index 9c039a6be8..5dae812bbc 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
@@ -3,9 +3,9 @@ import Vue from 'vue';
import { __ } from '~/locale';
import RefSelector from '~/ref/components/ref_selector.vue';
import { REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants';
-import setupNativeFormVariableList from '../../../../ci_variable_list/native_form_variable_list';
-import GlFieldErrors from '../../../../gl_field_errors';
-import Translate from '../../../../vue_shared/translate';
+import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
+import GlFieldErrors from '~/gl_field_errors';
+import Translate from '~/vue_shared/translate';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
@@ -33,13 +33,7 @@ function initIntervalPatternInput() {
}
function getEnabledRefTypes() {
- const refTypes = [REF_TYPE_BRANCHES];
-
- if (gon.features.pipelineSchedulesWithTags) {
- refTypes.push(REF_TYPE_TAGS);
- }
-
- return refTypes;
+ return [REF_TYPE_BRANCHES, REF_TYPE_TAGS];
}
function initTargetRefDropdown() {
@@ -61,9 +55,7 @@ function initTargetRefDropdown() {
value: $refField.value,
useSymbolicRefNames: true,
translations: {
- dropdownHeader: gon.features.pipelineSchedulesWithTags
- ? __('Select target branch or tag')
- : __('Select target branch'),
+ dropdownHeader: __('Select target branch or tag'),
},
},
class: 'gl-w-full',
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index 0c17bf2f34..4f57e1308d 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -9,7 +9,7 @@ import axios from '~/lib/utils/axios_utils';
import { serializeForm } from '~/lib/utils/forms';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
-import projectSelect from '../../project_select';
+import projectSelect from '~/project_select';
export default class Project {
constructor() {
diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js
index 2c0394dc12..bf4fb5f3b7 100644
--- a/app/assets/javascripts/pages/projects/project_members/index.js
+++ b/app/assets/javascripts/pages/projects/project_members/index.js
@@ -18,9 +18,16 @@ initInviteGroupTrigger();
const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions'];
initMembersApp(document.querySelector('.js-project-members-list-app'), {
[MEMBER_TYPES.user]: {
- tableFields: SHARED_FIELDS.concat(['source', 'granted']),
+ tableFields: SHARED_FIELDS.concat(['source', 'granted', 'userCreatedAt', 'lastActivityOn']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
- tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
+ tableSortableFields: [
+ 'account',
+ 'granted',
+ 'maxRole',
+ 'lastSignIn',
+ 'userCreatedAt',
+ 'lastActivityOn',
+ ],
requestFormatter: projectMemberRequestFormatter,
filteredSearchBar: {
show: true,
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index e88dbf20e1..43ab829f5f 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -10,36 +10,34 @@ import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_to
import initSettingsPanels from '~/settings_panels';
import { initTokenAccess } from '~/token_access';
-document.addEventListener('DOMContentLoaded', () => {
- // Initialize expandable settings panels
- initSettingsPanels();
+// Initialize expandable settings panels
+initSettingsPanels();
- const runnerToken = document.querySelector('.js-secret-runner-token');
- if (runnerToken) {
- const runnerTokenSecretValue = new SecretValues({
- container: runnerToken,
- });
- runnerTokenSecretValue.init();
- }
-
- initVariableList();
-
- // hide extra auto devops settings based checkbox state
- const autoDevOpsExtraSettings = document.querySelector('.js-extra-settings');
- const instanceDefaultBadge = document.querySelector('.js-instance-default-badge');
- document.querySelector('.js-toggle-extra-settings').addEventListener('click', (event) => {
- const { target } = event;
- if (instanceDefaultBadge) instanceDefaultBadge.style.display = 'none';
- autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
+const runnerToken = document.querySelector('.js-secret-runner-token');
+if (runnerToken) {
+ const runnerTokenSecretValue = new SecretValues({
+ container: runnerToken,
});
+ runnerTokenSecretValue.init();
+}
- registrySettingsApp();
- initDeployFreeze();
+initVariableList();
- initSettingsPipelinesTriggers();
- initArtifactsSettings();
- initSharedRunnersToggle();
- initInstallRunner();
- initRunnerAwsDeployments();
- initTokenAccess();
+// hide extra auto devops settings based checkbox state
+const autoDevOpsExtraSettings = document.querySelector('.js-extra-settings');
+const instanceDefaultBadge = document.querySelector('.js-instance-default-badge');
+document.querySelector('.js-toggle-extra-settings').addEventListener('click', (event) => {
+ const { target } = event;
+ if (instanceDefaultBadge) instanceDefaultBadge.style.display = 'none';
+ autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
});
+
+registrySettingsApp();
+initDeployFreeze();
+
+initSettingsPipelinesTriggers();
+initArtifactsSettings();
+initSharedRunnersToggle();
+initInstallRunner();
+initRunnerAwsDeployments();
+initTokenAccess();
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
index e90954c14c..d45052d76f 100644
--- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
@@ -1,9 +1,7 @@
import MirrorRepos from '~/mirrors/mirror_repos';
import initForm from '../form';
-document.addEventListener('DOMContentLoaded', () => {
- initForm();
+initForm();
- const mirrorReposContainer = document.querySelector('.js-mirror-settings');
- if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init();
-});
+const mirrorReposContainer = document.querySelector('.js-mirror-settings');
+if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init();
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
index 9fb8be3fdb..b2d32c2c94 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
@@ -44,6 +44,15 @@ export default {
},
},
computed: {
+ internalValue: {
+ get() {
+ return this.value;
+ },
+ set(value) {
+ this.$emit('change', value);
+ },
+ },
+
featureEnabled() {
return this.value !== 0;
},
@@ -68,10 +77,6 @@ export default {
this.$emit('change', firstOptionValue);
}
},
-
- selectOption(e) {
- this.$emit('change', Number(e.target.value));
- },
},
};
@@ -93,15 +98,14 @@ export default {
/>
{{ optionName }}
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 184bda4410..03bab0fa77 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -9,7 +9,6 @@ import {
featureAccessLevelMembers,
featureAccessLevelEveryone,
featureAccessLevel,
- featureAccessLevelNone,
CVE_ID_REQUEST_BUTTON_I18N,
featureAccessLevelDescriptions,
} from '../constants';
@@ -225,8 +224,6 @@ export default {
},
operationsFeatureAccessLevelOptions() {
- if (!this.operationsEnabled) return [featureAccessLevelNone];
-
return this.featureAccessLevelOptions.filter(
([value]) => value <= this.operationsAccessLevel,
);
@@ -251,10 +248,6 @@ export default {
return options;
},
- metricsOptionsDropdownDisabled() {
- return this.operationsFeatureAccessLevelOptions.length < 2 || !this.operationsEnabled;
- },
-
operationsEnabled() {
return this.operationsAccessLevel > featureAccessLevel.NOT_ENABLED;
},
@@ -392,6 +385,15 @@ export default {
else if (oldValue === featureAccessLevel.NOT_ENABLED)
toggleHiddenClassBySelector('.merge-requests-feature', false);
},
+
+ operationsAccessLevel(value, oldValue) {
+ if (value < oldValue) {
+ // sub-features cannot have more permissive access level
+ this.metricsDashboardAccessLevel = Math.min(this.metricsDashboardAccessLevel, value);
+ } else if (oldValue === 0) {
+ this.metricsDashboardAccessLevel = value;
+ }
+ },
},
methods: {
@@ -590,7 +592,9 @@ export default {
:help-path="packagesHelpPath"
:label="$options.i18n.packagesLabel"
:help-text="
- s__('ProjectSettings|Every project can have its own space to store its packages.')
+ s__(
+ 'ProjectSettings|Every project can have its own space to store its packages. Note: The Package Registry is always visible when a project is public.',
+ )
"
>
m.default(awardEmojiEl))
- .catch(() => {});
-}
+initAwardsApp(document.getElementById('js-vue-awards-block'));
diff --git a/app/assets/javascripts/pages/projects/tags/new/index.js b/app/assets/javascripts/pages/projects/tags/new/index.js
index b071e7a45f..9ef1017f9f 100644
--- a/app/assets/javascripts/pages/projects/tags/new/index.js
+++ b/app/assets/javascripts/pages/projects/tags/new/index.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
-import GLForm from '../../../../gl_form';
-import RefSelectDropdown from '../../../../ref_select_dropdown';
-import ZenMode from '../../../../zen_mode';
+import GLForm from '~/gl_form';
+import RefSelectDropdown from '~/ref_select_dropdown';
+import ZenMode from '~/zen_mode';
new ZenMode(); // eslint-disable-line no-new
new GLForm($('.tag-form')); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index 4bb461aada..cf7162f477 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -1,8 +1,8 @@
import $ from 'jquery';
import initTree from 'ee_else_ce/repository';
import initBlob from '~/blob_edit/blob_bundle';
-import ShortcutsNavigation from '../../../../behaviors/shortcuts/shortcuts_navigation';
-import NewCommitForm from '../../../../new_commit_form';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
+import NewCommitForm from '~/new_commit_form';
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
initBlob();
diff --git a/app/assets/javascripts/pages/projects/wikis/show/index.js b/app/assets/javascripts/pages/projects/wikis/show/index.js
index c08a10122b..7ca5f6964c 100644
--- a/app/assets/javascripts/pages/projects/wikis/show/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/show/index.js
@@ -1,3 +1,5 @@
+import { mountApplications } from '~/pages/shared/wikis/show';
import { mountApplications as mountEditApplications } from '~/pages/shared/wikis/async_edit';
+mountApplications();
mountEditApplications();
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index 8c2fd624a8..b62417cf59 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import initVueAlerts from '~/vue_alerts';
-import NoEmojiValidator from '../../../emoji/no_emoji_validator';
+import NoEmojiValidator from '~/emoji/no_emoji_validator';
import LengthValidator from './length_validator';
import OAuthRememberMe from './oauth_remember_me';
import preserveUrlFragment from './preserve_url_fragment';
diff --git a/app/assets/javascripts/pages/sessions/new/length_validator.js b/app/assets/javascripts/pages/sessions/new/length_validator.js
index 17acad10bc..b2074fb1e3 100644
--- a/app/assets/javascripts/pages/sessions/new/length_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/length_validator.js
@@ -1,4 +1,4 @@
-import InputValidator from '../../../validators/input_validator';
+import InputValidator from '~/validators/input_validator';
const errorMessageClass = 'gl-field-error';
diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue
new file mode 100644
index 0000000000..7c23f60954
--- /dev/null
+++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+ {{ $options.i18n.loadingContentFailed }}
+
+
+
diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
index 8ef31b9b98..024b3bc959 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
@@ -1,21 +1,11 @@
@@ -438,10 +360,7 @@ export default {
}}
-
+
{{ toggleEditingModeButtonText }}
-
-
- {{ content }}
-
-
-
- {{ $options.i18n.contentEditor.switchToOldEditor.modal.text }}
-
-
-
-
- {{ content }}
-
-
-
- {{ $options.i18n.contentEditor.switchToOldEditor.helpText }}
- {{
- $options.i18n.contentEditor.switchToOldEditor.label
- }}
-
diff --git a/app/assets/javascripts/pages/shared/wikis/edit.js b/app/assets/javascripts/pages/shared/wikis/edit.js
index beeabfde1a..0287863391 100644
--- a/app/assets/javascripts/pages/shared/wikis/edit.js
+++ b/app/assets/javascripts/pages/shared/wikis/edit.js
@@ -3,8 +3,8 @@ import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import csrf from '~/lib/utils/csrf';
import Translate from '~/vue_shared/translate';
-import GLForm from '../../../gl_form';
-import ZenMode from '../../../zen_mode';
+import GLForm from '~/gl_form';
+import ZenMode from '~/zen_mode';
import deleteWikiModal from './components/delete_wiki_modal.vue';
import wikiAlert from './components/wiki_alert.vue';
import wikiForm from './components/wiki_form.vue';
diff --git a/app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js b/app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js
new file mode 100644
index 0000000000..90cc298315
--- /dev/null
+++ b/app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js
@@ -0,0 +1,5 @@
+import $ from 'jquery';
+
+export const renderGFM = (el) => {
+ return $(el).renderGFM();
+};
diff --git a/app/assets/javascripts/pages/shared/wikis/show.js b/app/assets/javascripts/pages/shared/wikis/show.js
new file mode 100644
index 0000000000..9906cb595f
--- /dev/null
+++ b/app/assets/javascripts/pages/shared/wikis/show.js
@@ -0,0 +1,27 @@
+import Vue from 'vue';
+import Wikis from './wikis';
+import WikiContent from './components/wiki_content.vue';
+
+const mountWikiContentApp = () => {
+ const el = document.querySelector('.js-async-wiki-page-content');
+
+ if (el) {
+ const { getWikiContentUrl } = el.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ render(createElement) {
+ return createElement(WikiContent, {
+ props: { getWikiContentUrl },
+ });
+ },
+ });
+ }
+};
+
+export const mountApplications = () => {
+ // eslint-disable-next-line no-new
+ new Wikis();
+ mountWikiContentApp();
+};
diff --git a/app/assets/javascripts/performance_bar/services/performance_bar_service.js b/app/assets/javascripts/performance_bar/services/performance_bar_service.js
index a614342c85..4c0293f5b7 100644
--- a/app/assets/javascripts/performance_bar/services/performance_bar_service.js
+++ b/app/assets/javascripts/performance_bar/services/performance_bar_service.js
@@ -1,5 +1,5 @@
import { parseBoolean } from '~/lib/utils/common_utils';
-import axios from '../../lib/utils/axios_utils';
+import axios from '~/lib/utils/axios_utils';
export default class PerformanceBarService {
static interceptor = null;
diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js
index f6de21ec0c..dee832c01d 100644
--- a/app/assets/javascripts/persistent_user_callouts.js
+++ b/app/assets/javascripts/persistent_user_callouts.js
@@ -12,6 +12,7 @@ const PERSISTENT_USER_CALLOUTS = [
'.js-security-newsletter-callout',
'.js-approaching-seats-count-threshold',
'.js-storage-enforcement-banner',
+ '.js-user-over-limit-free-plan-alert',
];
const initCallouts = () => {
diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue
index a8ad56ab6a..897bd2dccc 100644
--- a/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue
+++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue
@@ -1,5 +1,5 @@
-
-
- {{ $options.i18n.title }}
- {{ $options.i18n.firstParagraph }}
-
- {{ item }}
-
-
-
-
-
- {{ content }}
-
-
-
-
-
-
+
+
{{ $options.i18n.title }}
+
{{ $options.i18n.firstParagraph }}
+
+ {{ item }}
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue
index 3da535f5f9..d2682cf632 100644
--- a/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue
+++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue
@@ -1,5 +1,5 @@
-
-
- {{ $options.i18n.title }}
- {{ $options.i18n.firstParagraph }}
-
-
-
- {{ content }}
-
-
-
-
-
+
+
{{ $options.i18n.title }}
+
{{ $options.i18n.firstParagraph }}
+
+
+
+ {{ content }}
+
+
+
+
diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue
index f714f6411f..04140434af 100644
--- a/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue
+++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue
@@ -1,5 +1,5 @@
-
-
- {{ $options.i18n.title }}
- {{ $options.i18n.firstParagraph }}
-
-
-
-
-
- {{ content }}
-
-
-
-
-
-
-
-
- {{ content }}
-
-
-
-
-
-
-
-
- {{ content }}
-
-
-
-
-
-
-
-
- {{ content }}
-
-
-
-
-
-
-
+
+
{{ $options.i18n.title }}
+
{{ $options.i18n.firstParagraph }}
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue
index 512414f024..aeeb52319d 100644
--- a/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue
+++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue
@@ -1,5 +1,4 @@
-
-
- {{ $options.i18n.title }}
- {{ $options.i18n.firstParagraph }}
-
-
+
+
{{ $options.i18n.title }}
+
{{ $options.i18n.firstParagraph }}
+
diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue b/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue
index 9cb070a551..375db7f305 100644
--- a/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue
+++ b/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue
@@ -1,101 +1,61 @@
-
-
-
-
- {{ __('Collapse') }}
-
-
-
-
-
-
+
+
+ {{ $options.i18n.title }}
+
+
+
+
+
+
diff --git a/app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue b/app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue
index b4e9ab81d3..9765d669fc 100644
--- a/app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue
+++ b/app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue
@@ -7,13 +7,23 @@ import { pipelineEditorTrackingOptions, TEMPLATE_REPOSITORY_URL } from '../../co
export default {
i18n: {
browseTemplates: __('Browse templates'),
+ help: __('Help'),
},
TEMPLATE_REPOSITORY_URL,
components: {
GlButton,
},
mixins: [Tracking.mixin()],
+ props: {
+ showDrawer: {
+ type: Boolean,
+ required: true,
+ },
+ },
methods: {
+ toggleDrawer() {
+ this.$emit(this.showDrawer ? 'close-drawer' : 'open-drawer');
+ },
trackTemplateBrowsing() {
const { label, actions } = pipelineEditorTrackingOptions;
@@ -30,9 +40,20 @@ export default {
size="small"
icon="external-link"
target="_blank"
+ data-testid="template-repo-link"
+ data-qa-selector="template_repo_link"
@click="trackTemplateBrowsing"
>
{{ $options.i18n.browseTemplates }}
+
+ {{ $options.i18n.help }}
+
diff --git a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
index 5cff93c884..d50e6f9a62 100644
--- a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
+++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
@@ -86,6 +86,10 @@ export default {
type: Boolean,
required: true,
},
+ showDrawer: {
+ type: Boolean,
+ required: true,
+ },
},
apollo: {
appStatus: {
@@ -157,7 +161,7 @@ export default {
@click="setCurrentTab($options.tabConstants.CREATE_TAB)"
>
-
+
-
+
@@ -137,6 +147,10 @@ export default {
@scrolled-to-commit-form="setScrollToCommitForm(false)"
v-on="$listeners"
/>
-
+
diff --git a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
index d74b6e8edf..32e1e18b68 100644
--- a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
+++ b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
@@ -396,6 +396,7 @@ export default {
:key="variable.uniqueId"
class="gl-mb-3 gl-ml-n3 gl-pb-2"
data-testid="ci-variable-row"
+ data-qa-selector="ci_variable_row_container"
>
diff --git a/app/assets/javascripts/pipeline_wizard/components/input.vue b/app/assets/javascripts/pipeline_wizard/components/input.vue
index 9a0c802664..5efae2471e 100644
--- a/app/assets/javascripts/pipeline_wizard/components/input.vue
+++ b/app/assets/javascripts/pipeline_wizard/components/input.vue
@@ -92,6 +92,7 @@ export default {
ref="widget"
:validate="validate"
v-bind="$attrs"
+ :data-input-target="target"
@input="onModelChange"
@update:valid="onValidationStateChange"
/>
diff --git a/app/assets/javascripts/pipeline_wizard/components/wrapper.vue b/app/assets/javascripts/pipeline_wizard/components/wrapper.vue
index b7207576dd..f50cd17551 100644
--- a/app/assets/javascripts/pipeline_wizard/components/wrapper.vue
+++ b/app/assets/javascripts/pipeline_wizard/components/wrapper.vue
@@ -1,6 +1,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
index 0380ba646c..5a9c85a0f1 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
@@ -1,7 +1,7 @@
+
+
+
+
+
+
+
+
+
+ {{ template.name }}
+
+
+
+ {{ template.description }}
+
+
+
+
+ {{ $options.i18n.cta }}
+
+
+
+
+
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue
similarity index 74%
rename from app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue
rename to app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue
index d50229e47c..be46a7f5ce 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue
@@ -1,7 +1,6 @@
-
-
+
+
+
+
+
{{ s__('Pipelines|API') }}
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
index 1dcbd77a92..63c492c8bc 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
@@ -2,6 +2,7 @@
import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { ICONS } from '../../constants';
import PipelineLabels from './pipeline_labels.vue';
@@ -11,6 +12,7 @@ export default {
GlLink,
PipelineLabels,
TooltipOnTruncate,
+ UserAvatarLink,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -169,6 +171,15 @@ export default {
{{
commitShortSha
}}
+
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index 6f0e67e1ae..77b9c2b520 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -127,6 +127,10 @@ export default {
eventHub.$emit('refreshPipelinesTable');
},
},
+ TBODY_TR_ATTR: {
+ 'data-testid': 'pipeline-table-row',
+ 'data-qa-selector': 'pipeline_row_container',
+ },
};
@@ -135,7 +139,7 @@ export default {
:fields="$options.tableFields"
:items="pipelines"
tbody-tr-class="commit"
- :tbody-tr-attr="{ 'data-testid': 'pipeline-table-row' }"
+ :tbody-tr-attr="$options.TBODY_TR_ATTR"
stacked="lg"
fixed
>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index cde963e405..387438fb72 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -1,13 +1,12 @@
@@ -85,12 +73,12 @@ export default {
- {{ timeFormatted(finishedTime) }}
+ {{ timeFormatted }}
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue
index 33115d72b9..746cf23864 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue
@@ -83,13 +83,7 @@ export default {
@input="searchAuthors"
>
-
+
{{ activeUser ? activeUser.name : inputValue }}
diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql
index 5fe47e09d9..641ec7a3cf 100644
--- a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql
@@ -1,4 +1,4 @@
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getPipelineJobs($fullPath: ID!, $iid: ID!, $after: String) {
project(fullPath: $fullPath) {
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 801f71cb36..338de65e79 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -13,6 +13,7 @@ const SELECTORS = {
PIPELINE_GRAPH: '#js-pipeline-graph-vue',
PIPELINE_HEADER: '#js-pipeline-header-vue',
PIPELINE_NOTIFICATION: '#js-pipeline-notification',
+ PIPELINE_TABS: '#js-pipeline-tabs',
PIPELINE_TESTS: '#js-pipeline-tests-detail',
PIPELINE_JOBS: '#js-pipeline-jobs-vue',
};
@@ -28,22 +29,6 @@ export default async function initPipelineDetailsBundle() {
});
}
- try {
- createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, apolloProvider, dataset);
- } catch {
- createFlash({
- message: __('An error occurred while loading the pipeline.'),
- });
- }
-
- try {
- createPipelineHeaderApp(SELECTORS.PIPELINE_HEADER, apolloProvider, dataset.graphqlResourceEtag);
- } catch {
- createFlash({
- message: __('An error occurred while loading a section of this page.'),
- });
- }
-
try {
createPipelineNotificationApp(SELECTORS.PIPELINE_NOTIFICATION, apolloProvider);
} catch {
@@ -52,27 +37,47 @@ export default async function initPipelineDetailsBundle() {
});
}
- try {
- createDagApp(apolloProvider);
- } catch {
- createFlash({
- message: __('An error occurred while loading the Needs tab.'),
- });
- }
+ if (gon.features?.pipelineTabsVue) {
+ const { createPipelineTabs } = await import('./pipeline_tabs');
- try {
- createTestDetails(SELECTORS.PIPELINE_TESTS);
- } catch {
- createFlash({
- message: __('An error occurred while loading the Test Reports tab.'),
- });
- }
+ try {
+ createPipelineTabs(SELECTORS.PIPELINE_TABS, apolloProvider);
+ } catch {
+ createFlash({
+ message: __('An error occurred while loading a section of this page.'),
+ });
+ }
+ } else {
+ try {
+ createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, apolloProvider, dataset);
+ } catch {
+ createFlash({
+ message: __('An error occurred while loading the pipeline.'),
+ });
+ }
- try {
- createPipelineJobsApp(SELECTORS.PIPELINE_JOBS);
- } catch {
- createFlash({
- message: __('An error occurred while loading the Jobs tab.'),
- });
+ try {
+ createDagApp(apolloProvider);
+ } catch {
+ createFlash({
+ message: __('An error occurred while loading the Needs tab.'),
+ });
+ }
+
+ try {
+ createTestDetails(SELECTORS.PIPELINE_TESTS);
+ } catch {
+ createFlash({
+ message: __('An error occurred while loading the Test Reports tab.'),
+ });
+ }
+
+ try {
+ createPipelineJobsApp(SELECTORS.PIPELINE_JOBS);
+ } catch {
+ createFlash({
+ message: __('An error occurred while loading the Jobs tab.'),
+ });
+ }
}
}
diff --git a/app/assets/javascripts/pipelines/pipeline_tabs.js b/app/assets/javascripts/pipelines/pipeline_tabs.js
new file mode 100644
index 0000000000..ff88c6215e
--- /dev/null
+++ b/app/assets/javascripts/pipelines/pipeline_tabs.js
@@ -0,0 +1,44 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import PipelineTabs from 'ee_else_ce/pipelines/components/pipeline_tabs.vue';
+import { reportToSentry } from './utils';
+
+Vue.use(VueApollo);
+
+const createPipelineTabs = (selector, apolloProvider) => {
+ const el = document.querySelector(selector);
+
+ if (!el) return;
+
+ const { dataset } = document.querySelector(selector);
+ const {
+ canGenerateCodequalityReports,
+ codequalityReportDownloadPath,
+ downloadablePathForReportType,
+ exposeSecurityDashboard,
+ exposeLicenseScanningData,
+ } = dataset;
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: selector,
+ components: {
+ PipelineTabs,
+ },
+ apolloProvider,
+ provide: {
+ canGenerateCodequalityReports: JSON.parse(canGenerateCodequalityReports),
+ codequalityReportDownloadPath,
+ downloadablePathForReportType,
+ exposeSecurityDashboard: JSON.parse(exposeSecurityDashboard),
+ exposeLicenseScanningData: JSON.parse(exposeLicenseScanningData),
+ },
+ errorCaptured(err, _vm, info) {
+ reportToSentry('pipeline_tabs', `error: ${err}, info: ${info}`);
+ },
+ render(createElement) {
+ return createElement(PipelineTabs);
+ },
+ });
+};
+
+export { createPipelineTabs };
diff --git a/app/assets/javascripts/pipelines/services/pipelines_service.js b/app/assets/javascripts/pipelines/services/pipelines_service.js
index 523ca13b6c..3ec563c95b 100644
--- a/app/assets/javascripts/pipelines/services/pipelines_service.js
+++ b/app/assets/javascripts/pipelines/services/pipelines_service.js
@@ -1,5 +1,5 @@
import Api from '~/api';
-import axios from '../../lib/utils/axios_utils';
+import axios from '~/lib/utils/axios_utils';
import { validateParams } from '../utils';
export default class PipelinesService {
diff --git a/app/assets/javascripts/pipelines/stores/pipelines_store.js b/app/assets/javascripts/pipelines/stores/pipelines_store.js
index a4bbada89c..765441560d 100644
--- a/app/assets/javascripts/pipelines/stores/pipelines_store.js
+++ b/app/assets/javascripts/pipelines/stores/pipelines_store.js
@@ -1,4 +1,4 @@
-import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils';
+import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
export default class PipelinesStore {
constructor() {
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
index 7b28d48b5b..b7f590a7b3 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/actions.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
@@ -30,6 +30,7 @@ export const fetchTestSuite = ({ state, commit, dispatch }, index) => {
dispatch('toggleLoading');
+ // eslint-disable-next-line camelcase
const { build_ids = [] } = state.testReports?.test_suites?.[index] || {};
// Replacing `/:suite_name.json` with the name of the suite. Including the extra characters
// to ensure that we replace exactly the template part of the URL string
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/utils.js b/app/assets/javascripts/pipelines/stores/test_reports/utils.js
index 63a5879895..6b616601bc 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/utils.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/utils.js
@@ -1,4 +1,4 @@
-import { __, sprintf } from '../../../locale';
+import { __, sprintf } from '~/locale';
import { TestStatus } from '../../constants';
/**
diff --git a/app/assets/javascripts/profile/preferences/components/diffs_colors.vue b/app/assets/javascripts/profile/preferences/components/diffs_colors.vue
new file mode 100644
index 0000000000..1992819ab8
--- /dev/null
+++ b/app/assets/javascripts/profile/preferences/components/diffs_colors.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/profile/preferences/components/diffs_colors_preview.vue b/app/assets/javascripts/profile/preferences/components/diffs_colors_preview.vue
new file mode 100644
index 0000000000..74dd2d5628
--- /dev/null
+++ b/app/assets/javascripts/profile/preferences/components/diffs_colors_preview.vue
@@ -0,0 +1,231 @@
+
+
+
+
diff --git a/app/assets/javascripts/profile/preferences/components/integration_view.vue b/app/assets/javascripts/profile/preferences/components/integration_view.vue
index c2952629a5..9924f248b8 100644
--- a/app/assets/javascripts/profile/preferences/components/integration_view.vue
+++ b/app/assets/javascripts/profile/preferences/components/integration_view.vue
@@ -1,13 +1,14 @@
-
-
+
+
{{ config.title }}
-
-
-
-
-
-
-
-
-
- {{ config.label }}
-
-
+
+
+
+
+
+
+ {{ config.label }}
+
-
-
-
+
+
+
diff --git a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
index 757a66ef14..7542f81a36 100644
--- a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
+++ b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
@@ -45,7 +45,7 @@ export default {
return {
isSubmitEnabled: true,
darkModeOnCreate: null,
- darkModeOnSubmit: null,
+ schemeOnCreate: null,
};
},
computed: {
@@ -61,6 +61,7 @@ export default {
this.formEl.addEventListener('ajax:success', this.handleSuccess);
this.formEl.addEventListener('ajax:error', this.handleError);
this.darkModeOnCreate = this.darkModeSelected();
+ this.schemeOnCreate = this.getSelectedScheme();
},
beforeDestroy() {
this.formEl.removeEventListener('ajax:beforeSend', this.handleLoading);
@@ -76,15 +77,19 @@ export default {
const themeId = new FormData(this.formEl).get('user[theme_id]');
return this.applicationThemes[themeId] ?? null;
},
+ getSelectedScheme() {
+ return new FormData(this.formEl).get('user[color_scheme_id]');
+ },
handleLoading() {
this.isSubmitEnabled = false;
- this.darkModeOnSubmit = this.darkModeSelected();
},
handleSuccess(customEvent) {
// Reload the page if the theme has changed from light to dark mode or vice versa
- // to correctly load all required styles.
- const modeChanged = this.darkModeOnCreate ? !this.darkModeOnSubmit : this.darkModeOnSubmit;
- if (modeChanged) {
+ // or if color scheme has changed to correctly load all required styles.
+ if (
+ this.darkModeOnCreate !== this.darkModeSelected() ||
+ this.schemeOnCreate !== this.getSelectedScheme()
+ ) {
window.location.reload();
return;
}
diff --git a/app/assets/javascripts/profile/preferences/profile_preferences_diffs_colors.js b/app/assets/javascripts/profile/preferences/profile_preferences_diffs_colors.js
new file mode 100644
index 0000000000..1b20018761
--- /dev/null
+++ b/app/assets/javascripts/profile/preferences/profile_preferences_diffs_colors.js
@@ -0,0 +1,21 @@
+import Vue from 'vue';
+import DiffsColors from './components/diffs_colors.vue';
+
+export default () => {
+ const el = document.querySelector('#js-profile-preferences-diffs-colors-app');
+
+ if (!el) return false;
+
+ const { deletion, addition } = el.dataset;
+
+ return new Vue({
+ el,
+ provide: {
+ deletion,
+ addition,
+ },
+ render(createElement) {
+ return createElement(DiffsColors);
+ },
+ });
+};
diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
index da14b1e847..8511f9bdb0 100644
--- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
+++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
@@ -3,11 +3,20 @@ import { GlLoadingIcon } from '@gitlab/ui';
import createFlash from '~/flash';
import { __ } from '~/locale';
import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
+import {
+ getQueryHeaders,
+ toggleQueryPollingByVisibility,
+} from '~/pipelines/components/graph/utils';
+import { formatStages } from '../utils';
import getLinkedPipelinesQuery from '../graphql/queries/get_linked_pipelines.query.graphql';
+import getPipelineStagesQuery from '../graphql/queries/get_pipeline_stages.query.graphql';
+import { COMMIT_BOX_POLL_INTERVAL } from '../constants';
export default {
i18n: {
linkedPipelinesFetchError: __('There was a problem fetching linked pipelines.'),
+ stageConversionError: __('There was a problem handling the pipeline data.'),
+ stagesFetchError: __('There was a problem fetching the pipeline stages.'),
},
components: {
GlLoadingIcon,
@@ -22,6 +31,9 @@ export default {
iid: {
default: '',
},
+ graphqlResourceEtag: {
+ default: '',
+ },
},
props: {
stages: {
@@ -48,10 +60,31 @@ export default {
createFlash({ message: this.$options.i18n.linkedPipelinesFetchError });
},
},
+ pipelineStages: {
+ context() {
+ return getQueryHeaders(this.graphqlResourceEtag);
+ },
+ query: getPipelineStagesQuery,
+ pollInterval: COMMIT_BOX_POLL_INTERVAL,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ iid: this.iid,
+ };
+ },
+ update({ project }) {
+ return project?.pipeline?.stages?.nodes || [];
+ },
+ error() {
+ createFlash({ message: this.$options.i18n.stagesFetchError });
+ },
+ },
},
data() {
return {
+ formattedStages: [],
pipeline: null,
+ pipelineStages: [],
};
},
computed: {
@@ -65,6 +98,25 @@ export default {
return this.pipeline?.upstream;
},
},
+ watch: {
+ pipelineStages() {
+ // pipelineStages are from GraphQL
+ // stages are from REST
+ // we do this to use dropdown_path for fetching jobs on stage click
+ try {
+ this.formattedStages = formatStages(this.pipelineStages, this.stages);
+ } catch (error) {
+ createFlash({
+ message: this.$options.i18n.stageConversionError,
+ captureError: true,
+ error,
+ });
+ }
+ },
+ },
+ mounted() {
+ toggleQueryPollingByVisibility(this.$apollo.queries.pipelineStages);
+ },
};
@@ -79,7 +131,7 @@ export default {
/>
diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue
new file mode 100644
index 0000000000..5a9d312980
--- /dev/null
+++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/projects/commit_box/info/constants.js b/app/assets/javascripts/projects/commit_box/info/constants.js
new file mode 100644
index 0000000000..be0bf71531
--- /dev/null
+++ b/app/assets/javascripts/projects/commit_box/info/constants.js
@@ -0,0 +1,7 @@
+import { __ } from '~/locale';
+
+export const COMMIT_BOX_POLL_INTERVAL = 10000;
+
+export const PIPELINE_STATUS_FETCH_ERROR = __(
+ 'There was a problem fetching the latest pipeline status.',
+);
diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_latest_pipeline_status.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_latest_pipeline_status.query.graphql
new file mode 100644
index 0000000000..cec96f8233
--- /dev/null
+++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_latest_pipeline_status.query.graphql
@@ -0,0 +1,14 @@
+query getLatestPipelineStatus($fullPath: ID!, $iid: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ pipeline(iid: $iid) {
+ id
+ detailedStatus {
+ id
+ detailsPath
+ icon
+ group
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql
new file mode 100644
index 0000000000..69a29947b1
--- /dev/null
+++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql
@@ -0,0 +1,19 @@
+query getPipelineStages($fullPath: ID!, $iid: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ pipeline(iid: $iid) {
+ id
+ stages {
+ nodes {
+ id
+ name
+ detailedStatus {
+ id
+ icon
+ group
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js
index 69fe2d3048..7500c152b6 100644
--- a/app/assets/javascripts/projects/commit_box/info/index.js
+++ b/app/assets/javascripts/projects/commit_box/info/index.js
@@ -2,6 +2,7 @@ import { fetchCommitMergeRequests } from '~/commit_merge_requests';
import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph';
import { initDetailsButton } from './init_details_button';
import { loadBranches } from './load_branches';
+import initCommitPipelineStatus from './init_commit_pipeline_status';
export const initCommitBoxInfo = () => {
// Display commit related branches
@@ -14,4 +15,6 @@ export const initCommitBoxInfo = () => {
initCommitPipelineMiniGraph();
initDetailsButton();
+
+ initCommitPipelineStatus();
};
diff --git a/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js
index 1d4ec4c110..c206e64856 100644
--- a/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js
+++ b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js
@@ -5,7 +5,7 @@ import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
+ defaultClient: createDefaultClient({}, { useGet: true }),
});
export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipeline-mini-graph') => {
@@ -15,7 +15,7 @@ export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipelin
return;
}
- const { stages, fullPath, iid } = el.dataset;
+ const { stages, fullPath, iid, graphqlResourceEtag } = el.dataset;
// Some commits have no pipeline, code splitting to load the pipeline optionally
const { default: CommitBoxPipelineMiniGraph } = await import(
@@ -30,6 +30,7 @@ export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipelin
fullPath,
iid,
dataMethod: 'graphql',
+ graphqlResourceEtag,
},
render(createElement) {
return createElement(CommitBoxPipelineMiniGraph, {
diff --git a/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_status.js b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_status.js
new file mode 100644
index 0000000000..d5e6253128
--- /dev/null
+++ b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_status.js
@@ -0,0 +1,34 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import CommitBoxPipelineStatus from './components/commit_box_pipeline_status.vue';
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient({}, { useGet: true }),
+});
+
+export default (selector = '.js-commit-pipeline-status') => {
+ const el = document.querySelector(selector);
+
+ if (!el) {
+ return;
+ }
+
+ const { fullPath, iid, graphqlResourceEtag } = el.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ apolloProvider,
+ provide: {
+ fullPath,
+ iid,
+ graphqlResourceEtag,
+ },
+ render(createElement) {
+ return createElement(CommitBoxPipelineStatus);
+ },
+ });
+};
diff --git a/app/assets/javascripts/projects/commit_box/info/utils.js b/app/assets/javascripts/projects/commit_box/info/utils.js
new file mode 100644
index 0000000000..ea7eb35cba
--- /dev/null
+++ b/app/assets/javascripts/projects/commit_box/info/utils.js
@@ -0,0 +1,14 @@
+export const formatStages = (graphQLStages = [], restStages = []) => {
+ if (graphQLStages.length !== restStages.length) {
+ throw new Error('Rest stages and graphQl stages must be the same length');
+ }
+
+ return graphQLStages.map((stage, index) => {
+ return {
+ name: stage.name,
+ status: stage.detailedStatus,
+ dropdown_path: restStages[index]?.dropdown_path || '',
+ title: restStages[index].title || '',
+ };
+ });
+};
diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue
index fd71a246a2..277af2f281 100644
--- a/app/assets/javascripts/projects/components/shared/delete_button.vue
+++ b/app/assets/javascripts/projects/components/shared/delete_button.vue
@@ -104,7 +104,6 @@ export default {
-import { GlFormGroup, GlFormSelect } from '@gitlab/ui';
+import { GlFormGroup, GlFormSelect, GlFormText, GlSprintf, GlLink } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import {
DEPLOYMENT_TARGET_SELECTIONS,
DEPLOYMENT_TARGET_LABEL,
DEPLOYMENT_TARGET_EVENT,
+ VISIT_DOCS_EVENT,
NEW_PROJECT_FORM,
+ K8S_OPTION,
} from '../constants';
const trackingMixin = Tracking.mixin({ label: DEPLOYMENT_TARGET_LABEL });
@@ -15,12 +18,21 @@ export default {
i18n: {
deploymentTargetLabel: s__('Deployment Target|Project deployment target (optional)'),
defaultOption: s__('Deployment Target|Select the deployment target'),
+ k8sEducationText: s__(
+ 'Deployment Target|%{linkStart}How to provision or deploy to Kubernetes clusters from GitLab?%{linkEnd}',
+ ),
},
deploymentTargets: DEPLOYMENT_TARGET_SELECTIONS,
+ VISIT_DOCS_EVENT,
+ DEPLOYMENT_TARGET_LABEL,
selectId: 'deployment-target-select',
+ helpPageUrl: helpPagePath('user/clusters/agent/index'),
components: {
GlFormGroup,
GlFormSelect,
+ GlFormText,
+ GlSprintf,
+ GlLink,
},
mixins: [trackingMixin],
data() {
@@ -29,6 +41,11 @@ export default {
formSubmitted: false,
};
},
+ computed: {
+ isK8sOptionSelected() {
+ return this.selectedTarget === K8S_OPTION;
+ },
+ },
mounted() {
const form = document.getElementById(NEW_PROJECT_FORM);
form.addEventListener('submit', () => {
@@ -52,10 +69,24 @@ export default {
:id="$options.selectId"
v-model="selectedTarget"
:options="$options.deploymentTargets"
+ class="input-lg"
>
{{ $options.i18n.defaultOption }}
+
+
+
+
+ {{ content }}
+
+
+
diff --git a/app/assets/javascripts/projects/new/components/new_project_url_select.vue b/app/assets/javascripts/projects/new/components/new_project_url_select.vue
index f4a21c6057..506f1ec5ff 100644
--- a/app/assets/javascripts/projects/new/components/new_project_url_select.vue
+++ b/app/assets/javascripts/projects/new/components/new_project_url_select.vue
@@ -13,6 +13,7 @@ import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import Tracking from '~/tracking';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
+import { s__ } from '~/locale';
import searchNamespacesWhereUserCanCreateProjectsQuery from '../queries/search_namespaces_where_user_can_create_projects.query.graphql';
import eventHub from '../event_hub';
@@ -43,14 +44,7 @@ export default {
debounce: DEBOUNCE_DELAY,
},
},
- inject: [
- 'namespaceFullPath',
- 'namespaceId',
- 'rootUrl',
- 'trackLabel',
- 'userNamespaceFullPath',
- 'userNamespaceId',
- ],
+ inject: ['namespaceFullPath', 'namespaceId', 'rootUrl', 'trackLabel', 'userNamespaceId'],
data() {
return {
currentUser: {},
@@ -62,10 +56,11 @@ export default {
fullPath: this.namespaceFullPath,
}
: {
- id: this.userNamespaceId,
- fullPath: this.userNamespaceFullPath,
+ id: undefined,
+ fullPath: s__('ProjectsNew|Pick a group or namespace'),
},
shouldSkipQuery: true,
+ userNamespaceId: this.userNamespaceId,
};
},
computed: {
@@ -92,6 +87,9 @@ export default {
hasNoMatches() {
return !this.hasGroupMatches && !this.hasNamespaceMatches;
},
+ dropdownPlaceholderClass() {
+ return this.selectedNamespace.id ? '' : 'gl-text-gray-500!';
+ },
},
created() {
eventHub.$on('select-template', this.handleSelectTemplate);
@@ -130,11 +128,18 @@ export default {
-
- {{ rootUrl }}
+
+ {{ rootUrl }}
+
+
+
diff --git a/app/assets/javascripts/projects/new/constants.js b/app/assets/javascripts/projects/new/constants.js
index c5e6722981..e52a84dc07 100644
--- a/app/assets/javascripts/projects/new/constants.js
+++ b/app/assets/javascripts/projects/new/constants.js
@@ -1,7 +1,9 @@
import { s__ } from '~/locale';
+export const K8S_OPTION = s__('DeploymentTarget|Kubernetes (GKE, EKS, OpenShift, and so on)');
+
export const DEPLOYMENT_TARGET_SELECTIONS = [
- s__('DeploymentTarget|Kubernetes (GKE, EKS, OpenShift, and so on)'),
+ K8S_OPTION,
s__('DeploymentTarget|Managed container runtime (Fargate, Cloud Run, DigitalOcean App)'),
s__('DeploymentTarget|Self-managed container runtime (Podman, Docker Swarm, Docker Compose)'),
s__('DeploymentTarget|Heroku'),
@@ -18,3 +20,4 @@ export const DEPLOYMENT_TARGET_SELECTIONS = [
export const NEW_PROJECT_FORM = 'new_project';
export const DEPLOYMENT_TARGET_LABEL = 'new_project_deployment_target';
export const DEPLOYMENT_TARGET_EVENT = 'select_deployment_target';
+export const VISIT_DOCS_EVENT = 'visit_docs';
diff --git a/app/assets/javascripts/projects/new/index.js b/app/assets/javascripts/projects/new/index.js
index 4de9b8a6f4..a72172a4f5 100644
--- a/app/assets/javascripts/projects/new/index.js
+++ b/app/assets/javascripts/projects/new/index.js
@@ -58,7 +58,6 @@ export function initNewProjectUrlSelect() {
namespaceId: el.dataset.namespaceId,
rootUrl: el.dataset.rootUrl,
trackLabel: el.dataset.trackLabel,
- userNamespaceFullPath: el.dataset.userNamespaceFullPath,
userNamespaceId: el.dataset.userNamespaceId,
},
render: (createElement) => createElement(NewProjectUrlSelect),
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index f1b7e3df7d..3e1c471f01 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -3,6 +3,7 @@ import { debounce } from 'lodash';
import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../lib/utils/constants';
+import { ENTER_KEY } from '../lib/utils/keys';
import axios from '../lib/utils/axios_utils';
import {
convertToTitleCase,
@@ -14,6 +15,7 @@ import {
let hasUserDefinedProjectPath = false;
let hasUserDefinedProjectName = false;
const invalidInputClass = 'gl-field-error-outline';
+const invalidDropdownClass = 'gl-inset-border-1-red-400!';
const cancelSource = axios.CancelToken.source();
const endpoint = `${gon.relative_url_root}/import/url/validate`;
@@ -50,6 +52,25 @@ const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingPr
}
};
+const selectedNamespaceId = () => document.querySelector('[name="project[selected_namespace_id]"]');
+const dropdownButton = () => document.querySelector('.js-group-namespace-dropdown > button');
+const namespaceButton = () => document.querySelector('.js-group-namespace-button');
+const namespaceError = () => document.querySelector('.js-group-namespace-error');
+
+const validateGroupNamespaceDropdown = (e) => {
+ if (selectedNamespaceId() && !selectedNamespaceId().attributes.value) {
+ document.querySelector('input[data-qa-selector="project_name"]').reportValidity();
+ e.preventDefault();
+ dropdownButton().classList.add(invalidDropdownClass);
+ namespaceButton().classList.add(invalidDropdownClass);
+ namespaceError().classList.remove('gl-display-none');
+ } else {
+ dropdownButton().classList.remove(invalidDropdownClass);
+ namespaceButton().classList.remove(invalidDropdownClass);
+ namespaceError().classList.add('gl-display-none');
+ }
+};
+
const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
const specialRepo = document.querySelector('.js-user-readme-repo');
@@ -70,6 +91,10 @@ const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
$projectPathInput.val() !== $projectPathInput.data('username'),
);
});
+
+ document.querySelector('.js-create-project-button').addEventListener('click', (e) => {
+ validateGroupNamespaceDropdown(e);
+ });
};
const deriveProjectPathFromUrl = ($projectImportUrl) => {
@@ -158,7 +183,11 @@ const bindEvents = () => {
$projectTemplateButtons.addClass('hidden');
$projectFieldsForm.addClass('selected');
$selectedIcon.empty();
- const value = $(this).val();
+
+ const $selectedTemplate = $(this);
+ $selectedTemplate.prop('checked', true);
+
+ const value = $selectedTemplate.val();
const selectedTemplate = DEFAULT_PROJECT_TEMPLATES[value];
$selectedTemplateText.text(selectedTemplate.text);
@@ -170,7 +199,21 @@ const bindEvents = () => {
setProjectNamePathHandlers($activeTabProjectName, $activeTabProjectPath);
}
- $useTemplateBtn.on('change', chooseTemplate);
+ function toggleActiveClassOnLabel(event) {
+ const $label = $(event.target).parent();
+ $label.toggleClass('active');
+ }
+
+ function chooseTemplateOnEnter(event) {
+ if (event.code === ENTER_KEY) {
+ chooseTemplate.call(this);
+ }
+ }
+
+ $useTemplateBtn.on('click', chooseTemplate);
+
+ $useTemplateBtn.on('focus focusout', toggleActiveClassOnLabel);
+ $useTemplateBtn.on('keypress', chooseTemplateOnEnter);
$changeTemplateBtn.on('click', () => {
$projectTemplateButtons.removeClass('hidden');
diff --git a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue
index e8b0e95b14..d4c97cbf03 100644
--- a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue
+++ b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue
@@ -1,6 +1,7 @@
@@ -85,7 +87,7 @@ export default {
:entity-name="dropdownItem.name"
:label="dropdownItem.name"
:size="32"
- shape="rect"
+ :shape="$options.AVATAR_SHAPE_OPTION_RECT"
/>
diff --git a/app/assets/javascripts/related_issues/components/related_issues_list.vue b/app/assets/javascripts/related_issues/components/related_issues_list.vue
index 174049b15f..9ed895e90f 100644
--- a/app/assets/javascripts/related_issues/components/related_issues_list.vue
+++ b/app/assets/javascripts/related_issues/components/related_issues_list.vue
@@ -1,8 +1,8 @@
-
+
{{ $options.i18n.newRelease }}
- {{ __('New release') }}
-
-
+
-
-
-
- {{ emptyStateText }}
-
- {{ __('More information') }}
-
-
-
-
+
-
-
-
+
-
+
diff --git a/app/assets/javascripts/releases/components/release_block_footer.vue b/app/assets/javascripts/releases/components/release_block_footer.vue
index cb795b3cba..91d6d0911a 100644
--- a/app/assets/javascripts/releases/components/release_block_footer.vue
+++ b/app/assets/javascripts/releases/components/release_block_footer.vue
@@ -104,9 +104,11 @@ export default {
{{ __('by') }}
diff --git a/app/assets/javascripts/releases/components/releases_pagination.vue b/app/assets/javascripts/releases/components/releases_pagination.vue
index fddf85ead1..52ad991d61 100644
--- a/app/assets/javascripts/releases/components/releases_pagination.vue
+++ b/app/assets/javascripts/releases/components/releases_pagination.vue
@@ -1,26 +1,24 @@
-
-
-
-
-
diff --git a/app/assets/javascripts/releases/components/releases_sort.vue b/app/assets/javascripts/releases/components/releases_sort.vue
index d4210dad19..0f14b579da 100644
--- a/app/assets/javascripts/releases/components/releases_sort.vue
+++ b/app/assets/javascripts/releases/components/releases_sort.vue
@@ -1,7 +1,17 @@
-
-
-
-
- {{ item.label }}
-
-
-
diff --git a/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql b/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql
index 7f67f7d11a..bda7ac52a4 100644
--- a/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql
+++ b/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql
@@ -1,12 +1,4 @@
-#import "../fragments/release.fragment.graphql"
-
-# This query is identical to
-# `app/graphql/queries/releases/all_releases.query.graphql`.
-# These two queries should be kept in sync.
-# When the `releases_index_apollo_client` feature flag is
-# removed, this query should be removed entirely.
-
-query allReleasesDeprecated(
+query allReleases(
$fullPath: ID!
$first: Int
$last: Int
@@ -20,7 +12,87 @@ query allReleasesDeprecated(
releases(first: $first, last: $last, before: $before, after: $after, sort: $sort) {
__typename
nodes {
- ...Release
+ __typename
+ name
+ tagName
+ tagPath
+ descriptionHtml
+ releasedAt
+ createdAt
+ upcomingRelease
+ assets {
+ __typename
+ count
+ sources {
+ __typename
+ nodes {
+ __typename
+ format
+ url
+ }
+ }
+ links {
+ __typename
+ nodes {
+ __typename
+ id
+ name
+ url
+ directAssetUrl
+ linkType
+ external
+ }
+ }
+ }
+ evidences {
+ __typename
+ nodes {
+ __typename
+ id
+ filepath
+ collectedAt
+ sha
+ }
+ }
+ links {
+ __typename
+ editUrl
+ selfUrl
+ openedIssuesUrl
+ closedIssuesUrl
+ openedMergeRequestsUrl
+ mergedMergeRequestsUrl
+ closedMergeRequestsUrl
+ }
+ commit {
+ __typename
+ id
+ sha
+ webUrl
+ title
+ }
+ author {
+ __typename
+ id
+ webUrl
+ avatarUrl
+ username
+ }
+ milestones {
+ __typename
+ nodes {
+ __typename
+ id
+ title
+ description
+ webPath
+ stats {
+ __typename
+ totalIssuesCount
+ closedIssuesCount
+ }
+ }
+ }
}
pageInfo {
__typename
diff --git a/app/assets/javascripts/releases/mount_index.js b/app/assets/javascripts/releases/mount_index.js
index 86fa72d149..afb8ab461c 100644
--- a/app/assets/javascripts/releases/mount_index.js
+++ b/app/assets/javascripts/releases/mount_index.js
@@ -1,50 +1,32 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import Vuex from 'vuex';
import createDefaultClient from '~/lib/graphql';
import ReleaseIndexApp from './components/app_index.vue';
-import ReleaseIndexApollopClientApp from './components/app_index_apollo_client.vue';
-import createStore from './stores';
-import createIndexModule from './stores/modules/index';
export default () => {
const el = document.getElementById('js-releases-page');
- if (window.gon?.features?.releasesIndexApolloClient) {
- Vue.use(VueApollo);
+ Vue.use(VueApollo);
- const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(
- {},
- {
- // This page attempts to decrease the perceived loading time
- // by sending two requests: one request for the first item only (which
- // completes relatively quickly), and one for all the items (which is slower).
- // By default, Apollo Client batches these requests together, which defeats
- // the purpose of making separate requests. So we explicitly
- // disable batching on this page.
- batchMax: 1,
- },
- ),
- });
-
- return new Vue({
- el,
- apolloProvider,
- provide: { ...el.dataset },
- render: (h) => h(ReleaseIndexApollopClientApp),
- });
- }
-
- Vue.use(Vuex);
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(
+ {},
+ {
+ // This page attempts to decrease the perceived loading time
+ // by sending two requests: one request for the first item only (which
+ // completes relatively quickly), and one for all the items (which is slower).
+ // By default, Apollo Client batches these requests together, which defeats
+ // the purpose of making separate requests. So we explicitly
+ // disable batching on this page.
+ batchMax: 1,
+ },
+ ),
+ });
return new Vue({
el,
- store: createStore({
- modules: {
- index: createIndexModule(el.dataset),
- },
- }),
+ apolloProvider,
+ provide: { ...el.dataset },
render: (h) => h(ReleaseIndexApp),
});
};
diff --git a/app/assets/javascripts/releases/stores/modules/index/actions.js b/app/assets/javascripts/releases/stores/modules/index/actions.js
deleted file mode 100644
index d3bb11cab3..0000000000
--- a/app/assets/javascripts/releases/stores/modules/index/actions.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import createFlash from '~/flash';
-import { __ } from '~/locale';
-import { PAGE_SIZE } from '~/releases/constants';
-import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql';
-import { gqClient, convertAllReleasesGraphQLResponse } from '~/releases/util';
-import * as types from './mutation_types';
-
-/**
- * Gets a paginated list of releases from the GraphQL endpoint
- *
- * @param {Object} vuexParams
- * @param {Object} actionParams
- * @param {String} [actionParams.before] A GraphQL cursor. If provided,
- * the items returned will proceed the provided cursor.
- * @param {String} [actionParams.after] A GraphQL cursor. If provided,
- * the items returned will follow the provided cursor.
- */
-export const fetchReleases = ({ dispatch, commit, state }, { before, after }) => {
- commit(types.REQUEST_RELEASES);
-
- const { sort, orderBy } = state.sorting;
- const orderByParam = orderBy === 'created_at' ? 'created' : orderBy;
- const sortParams = `${orderByParam}_${sort}`.toUpperCase();
-
- let paginationParams;
- if (!before && !after) {
- paginationParams = { first: PAGE_SIZE };
- } else if (before && !after) {
- paginationParams = { last: PAGE_SIZE, before };
- } else if (!before && after) {
- paginationParams = { first: PAGE_SIZE, after };
- } else {
- throw new Error(
- 'Both a `before` and an `after` parameter were provided to fetchReleases. These parameters cannot be used together.',
- );
- }
-
- gqClient
- .query({
- query: allReleasesQuery,
- variables: {
- fullPath: state.projectPath,
- sort: sortParams,
- ...paginationParams,
- },
- })
- .then((response) => {
- const { data, paginationInfo: pageInfo } = convertAllReleasesGraphQLResponse(response);
-
- commit(types.RECEIVE_RELEASES_SUCCESS, {
- data,
- pageInfo,
- });
- })
- .catch(() => dispatch('receiveReleasesError'));
-};
-
-export const receiveReleasesError = ({ commit }) => {
- commit(types.RECEIVE_RELEASES_ERROR);
- createFlash({
- message: __('An error occurred while fetching the releases. Please try again.'),
- });
-};
-
-export const setSorting = ({ commit }, data) => commit(types.SET_SORTING, data);
diff --git a/app/assets/javascripts/releases/stores/modules/index/index.js b/app/assets/javascripts/releases/stores/modules/index/index.js
deleted file mode 100644
index d5ca191153..0000000000
--- a/app/assets/javascripts/releases/stores/modules/index/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import * as actions from './actions';
-import mutations from './mutations';
-import createState from './state';
-
-export default (initialState) => ({
- namespaced: true,
- actions,
- mutations,
- state: createState(initialState),
-});
diff --git a/app/assets/javascripts/releases/stores/modules/index/mutation_types.js b/app/assets/javascripts/releases/stores/modules/index/mutation_types.js
deleted file mode 100644
index 669168efb8..0000000000
--- a/app/assets/javascripts/releases/stores/modules/index/mutation_types.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export const REQUEST_RELEASES = 'REQUEST_RELEASES';
-export const RECEIVE_RELEASES_SUCCESS = 'RECEIVE_RELEASES_SUCCESS';
-export const RECEIVE_RELEASES_ERROR = 'RECEIVE_RELEASES_ERROR';
-export const SET_SORTING = 'SET_SORTING';
diff --git a/app/assets/javascripts/releases/stores/modules/index/mutations.js b/app/assets/javascripts/releases/stores/modules/index/mutations.js
deleted file mode 100644
index 55a8a488be..0000000000
--- a/app/assets/javascripts/releases/stores/modules/index/mutations.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import * as types from './mutation_types';
-
-export default {
- /**
- * Sets isLoading to true while the request is being made.
- * @param {Object} state
- */
- [types.REQUEST_RELEASES](state) {
- state.isLoading = true;
- },
-
- /**
- * Sets isLoading to false.
- * Sets hasError to false.
- * Sets the received data
- * Sets the received pagination information
- * @param {Object} state
- * @param {Object} resp
- */
- [types.RECEIVE_RELEASES_SUCCESS](state, { data, pageInfo }) {
- state.hasError = false;
- state.isLoading = false;
- state.releases = data;
- state.pageInfo = pageInfo;
- },
-
- /**
- * Sets isLoading to false.
- * Sets hasError to true.
- * Resets the data
- * @param {Object} state
- * @param {Object} data
- */
- [types.RECEIVE_RELEASES_ERROR](state) {
- state.isLoading = false;
- state.releases = [];
- state.hasError = true;
- state.pageInfo = {};
- },
-
- [types.SET_SORTING](state, sorting) {
- state.sorting = { ...state.sorting, ...sorting };
- },
-};
diff --git a/app/assets/javascripts/releases/stores/modules/index/state.js b/app/assets/javascripts/releases/stores/modules/index/state.js
deleted file mode 100644
index 5e1aaab7b5..0000000000
--- a/app/assets/javascripts/releases/stores/modules/index/state.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { DESCENDING_ORDER, RELEASED_AT } from '../../../constants';
-
-export default ({
- projectId,
- projectPath,
- documentationPath,
- illustrationPath,
- newReleasePath = '',
-}) => ({
- projectId,
- projectPath,
- documentationPath,
- illustrationPath,
- newReleasePath,
-
- isLoading: false,
- hasError: false,
- releases: [],
- pageInfo: {},
- sorting: {
- sort: DESCENDING_ORDER,
- orderBy: RELEASED_AT,
- },
-});
diff --git a/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue b/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue
index 59bd54eab6..fb2ef850e4 100644
--- a/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue
+++ b/app/assets/javascripts/reports/codequality_report/components/codequality_issue_body.vue
@@ -34,7 +34,7 @@ export default {
return `${this.severityLabel} - ${this.issue.name}`;
},
issueSeverity() {
- return this.issue.severity.toLowerCase();
+ return this.issue.severity?.toLowerCase();
},
isStatusSuccess() {
return this.status === STATUS_SUCCESS;
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index 7a490210f0..0714d88b39 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -3,7 +3,7 @@ import { GlButton } from '@gitlab/ui';
import api from '~/api';
import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
-import Popover from '~/vue_shared/components/help_popover.vue';
+import HelpPopover from '~/vue_shared/components/help_popover.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { status, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '../constants';
import IssuesList from './issues_list.vue';
@@ -13,7 +13,7 @@ export default {
components: {
GlButton,
IssuesList,
- Popover,
+ HelpPopover,
StatusIcon,
},
mixins: [glFeatureFlagsMixin()],
@@ -172,7 +172,7 @@ export default {
},
methods: {
toggleCollapsed() {
- if (this.trackAction && this.glFeatures.usersExpandingWidgetsUsageData) {
+ if (this.trackAction) {
api.trackRedisHllUserEvent(this.trackAction);
}
@@ -193,7 +193,7 @@ export default {
{{ headerText }}
-
import { GlLoadingIcon } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
-import Popover from '~/vue_shared/components/help_popover.vue';
+import HelpPopover from '~/vue_shared/components/help_popover.vue';
import { ICON_WARNING } from '../constants';
/**
@@ -16,7 +16,7 @@ export default {
name: 'ReportSummaryRow',
components: {
CiIcon,
- Popover,
+ HelpPopover,
GlLoadingIcon,
},
props: {
@@ -79,7 +79,7 @@ export default {
diff --git a/app/assets/javascripts/reports/grouped_test_report/store/actions.js b/app/assets/javascripts/reports/grouped_test_report/store/actions.js
index e3db57ad84..73f8df016b 100644
--- a/app/assets/javascripts/reports/grouped_test_report/store/actions.js
+++ b/app/assets/javascripts/reports/grouped_test_report/store/actions.js
@@ -1,7 +1,7 @@
import Visibility from 'visibilityjs';
-import axios from '../../../lib/utils/axios_utils';
-import httpStatusCodes from '../../../lib/utils/http_status';
-import Poll from '../../../lib/utils/poll';
+import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
+import Poll from '~/lib/utils/poll';
import * as types from './mutation_types';
export const setPaths = ({ commit }, paths) => commit(types.SET_PATHS, paths);
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index 85652301f4..c9e4aab1db 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -12,6 +12,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
import CodeIntelligence from '~/code_navigation/components/app.vue';
+import LineHighlighter from '~/blob/line_highlighter';
import getRefMixin from '../mixins/get_ref';
import blobInfoQuery from '../queries/blob_info.query.graphql';
import userInfoQuery from '../queries/user_info.query.graphql';
@@ -192,6 +193,7 @@ export default {
window.requestIdleCallback(() => {
this.isRenderingLegacyTextViewer = false;
+ new LineHighlighter(); // eslint-disable-line no-new
});
} else {
this.legacyRichViewer = html;
@@ -301,6 +303,7 @@ export default {
:code-navigation-path="blobInfo.codeNavigationPath"
:blob-path="blobInfo.path"
:path-prefix="blobInfo.projectBlobPathRoot"
+ :wrap-text-nodes="glFeatures.highlightJs"
/>
diff --git a/app/assets/javascripts/repository/components/blob_viewers/index.js b/app/assets/javascripts/repository/components/blob_viewers/index.js
index cbe18ea396..81d2168e2c 100644
--- a/app/assets/javascripts/repository/components/blob_viewers/index.js
+++ b/app/assets/javascripts/repository/components/blob_viewers/index.js
@@ -8,6 +8,7 @@ const viewers = {
pdf: () => import('./pdf_viewer.vue'),
lfs: () => import('./lfs_viewer.vue'),
audio: () => import('./audio_viewer.vue'),
+ svg: () => import('./image_viewer.vue'),
};
export const loadViewer = (type, isUsingLfs) => {
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
index 08faf19d12..84c9f9d0bb 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -9,8 +9,7 @@ import {
} from '@gitlab/ui';
import permissionsQuery from 'shared_queries/repository/permissions.query.graphql';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { __ } from '../../locale';
+import { __ } from '~/locale';
import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql';
import projectShortPathQuery from '../queries/project_short_path.query.graphql';
@@ -58,7 +57,7 @@ export default {
directives: {
GlModal: GlModalDirective,
},
- mixins: [getRefMixin, glFeatureFlagsMixin()],
+ mixins: [getRefMixin],
props: {
currentPath: {
type: String,
@@ -176,11 +175,7 @@ export default {
return this.canEditTree && !this.$apollo.queries.userPermissions.loading;
},
showNewDirectoryModal() {
- return (
- this.glFeatures.newDirModal &&
- this.canEditTree &&
- !this.$apollo.queries.userPermissions.loading
- );
+ return this.canEditTree && !this.$apollo.queries.userPermissions.loading;
},
dropdownItems() {
const items = [];
@@ -209,24 +204,13 @@ export default {
},
);
- if (this.glFeatures.newDirModal) {
- items.push({
- attrs: {
- href: '#modal-create-new-dir',
- },
- text: __('New directory'),
- modalId: NEW_DIRECTORY_MODAL_ID,
- });
- } else {
- items.push({
- attrs: {
- href: '#modal-create-new-dir',
- 'data-target': '#modal-create-new-dir',
- 'data-toggle': 'modal',
- },
- text: __('New directory'),
- });
- }
+ items.push({
+ attrs: {
+ href: '#modal-create-new-dir',
+ },
+ text: __('New directory'),
+ modalId: NEW_DIRECTORY_MODAL_ID,
+ });
} else if (this.canCreateMrFromFork) {
items.push(
{
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index c3d121505b..2810db33e6 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -10,10 +10,10 @@ import {
import defaultAvatarUrl from 'images/no_avatar.png';
import pathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql';
import { sprintf, s__ } from '~/locale';
-import CiIcon from '../../vue_shared/components/ci_icon.vue';
-import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
-import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
-import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql';
@@ -171,7 +171,7 @@ export default {
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlButton } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { sprintf, __ } from '../../../locale';
+import { sprintf, __ } from '~/locale';
import getRefMixin from '../../mixins/get_ref';
import projectPathQuery from '../../queries/project_path.query.graphql';
import TableHeader from './header.vue';
diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue
index 130ebf7736..2200e999c7 100644
--- a/app/assets/javascripts/repository/components/tree_content.vue
+++ b/app/assets/javascripts/repository/components/tree_content.vue
@@ -2,7 +2,7 @@
import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql';
import createFlash from '~/flash';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { __ } from '../../locale';
+import { __ } from '~/locale';
import {
TREE_PAGE_SIZE,
TREE_INITIAL_FETCH_COUNT,
diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
index 8aba91eedf..accc9926a5 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -1,12 +1,14 @@
-
-
- {{ displayedValue }}
-
-
-
-
+
diff --git a/app/assets/javascripts/runner/components/runner_assigned_item.vue b/app/assets/javascripts/runner/components/runner_assigned_item.vue
index ea8074199a..38bdfecb7d 100644
--- a/app/assets/javascripts/runner/components/runner_assigned_item.vue
+++ b/app/assets/javascripts/runner/components/runner_assigned_item.vue
@@ -1,5 +1,6 @@
-
+
{{ fullName }}
diff --git a/app/assets/javascripts/runner/components/runner_bulk_delete.vue b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
new file mode 100644
index 0000000000..50791de0bd
--- /dev/null
+++ b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+ {{
+ s__('Runners|Clear selection')
+ }}
+ {{
+ s__('Runners|Delete selected')
+ }}
+
+
+
+
diff --git a/app/assets/javascripts/runner/components/runner_delete_button.vue b/app/assets/javascripts/runner/components/runner_delete_button.vue
index 854c983f4d..b58665ecbc 100644
--- a/app/assets/javascripts/runner/components/runner_delete_button.vue
+++ b/app/assets/javascripts/runner/components/runner_delete_button.vue
@@ -5,7 +5,12 @@ import { createAlert } from '~/flash';
import { sprintf } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { I18N_DELETE_RUNNER, I18N_DELETED_TOAST } from '../constants';
+import {
+ I18N_DELETE_DISABLED_MANY_PROJECTS,
+ I18N_DELETE_DISABLED_UNKNOWN_REASON,
+ I18N_DELETE_RUNNER,
+ I18N_DELETED_TOAST,
+} from '../constants';
import RunnerDeleteModal from './runner_delete_modal.vue';
export default {
@@ -26,6 +31,11 @@ export default {
return runner?.id && runner?.shortSha;
},
},
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
compact: {
type: Boolean,
required: false,
@@ -75,7 +85,14 @@ export default {
return null;
},
tooltip() {
- // Only show tooltip when compact.
+ if (this.disabled && this.runner.projectCount > 1) {
+ return I18N_DELETE_DISABLED_MANY_PROJECTS;
+ }
+ if (this.disabled) {
+ return I18N_DELETE_DISABLED_UNKNOWN_REASON;
+ }
+
+ // Only show basic "delete" tooltip when compact.
// Also prevent a "sticky" tooltip: If this button is
// disabled, mouseout listeners don't run leaving the tooltip stuck
if (this.compact && !this.deleting) {
@@ -83,6 +100,14 @@ export default {
}
return '';
},
+ wrapperTabindex() {
+ if (this.disabled) {
+ // Trigger tooltip on keyboard-focusable wrapper
+ // See https://bootstrap-vue.org/docs/directives/tooltip
+ return '0';
+ }
+ return null;
+ },
},
methods: {
async onDelete() {
@@ -90,31 +115,37 @@ export default {
// should only change back if the operation fails.
this.deleting = true;
try {
- const {
- data: {
- runnerDelete: { errors },
- },
- } = await this.$apollo.mutate({
+ await this.$apollo.mutate({
mutation: runnerDeleteMutation,
variables: {
input: {
id: this.runner.id,
},
},
+ update: (cache, { data }) => {
+ const { errors } = data.runnerDelete;
+
+ if (errors?.length) {
+ this.onError(new Error(errors.join(' ')));
+ return;
+ }
+
+ this.$emit('deleted', {
+ message: sprintf(I18N_DELETED_TOAST, { name: this.runnerName }),
+ });
+
+ // Remove deleted runner from the cache
+ const cacheId = cache.identify(this.runner);
+ cache.evict({ id: cacheId });
+ cache.gc();
+ },
});
- if (errors && errors.length) {
- throw new Error(errors.join(' '));
- } else {
- this.$emit('deleted', {
- message: sprintf(I18N_DELETED_TOAST, { name: this.runnerName }),
- });
- }
} catch (e) {
- this.deleting = false;
this.onError(e);
}
},
onError(error) {
+ this.deleting = false;
const { message } = error;
createAlert({ message });
@@ -125,20 +156,22 @@ export default {
-
- {{ buttonContent }}
+
+
+ {{ buttonContent }}
+
-
+
diff --git a/app/assets/javascripts/runner/components/runner_jobs.vue b/app/assets/javascripts/runner/components/runner_jobs.vue
index eb77babcc5..b25d92d049 100644
--- a/app/assets/javascripts/runner/components/runner_jobs.vue
+++ b/app/assets/javascripts/runner/components/runner_jobs.vue
@@ -1,5 +1,5 @@
@@ -74,13 +115,34 @@ export default {
:aria-busy="loading"
:class="tableClass"
:items="runners"
- :fields="$options.fields"
+ :fields="fields"
:tbody-tr-attr="runnerTrAttr"
data-testid="runner-list"
stacked="md"
primary-key="id"
fixed
>
+
+
+
+
+
+
+
+
+
+
+ {{ label }}
+
+
+
@@ -99,12 +161,6 @@ export default {
-
-
- {{ ipAddress }}
-
-
-
{{ formatJobCount(jobCount) }}
diff --git a/app/assets/javascripts/runner/components/runner_pause_button.vue b/app/assets/javascripts/runner/components/runner_pause_button.vue
index c88634bfbd..334e5f6023 100644
--- a/app/assets/javascripts/runner/components/runner_pause_button.vue
+++ b/app/assets/javascripts/runner/components/runner_pause_button.vue
@@ -24,6 +24,7 @@ export default {
default: false,
},
},
+ emits: ['toggledPaused'],
data() {
return {
updating: false,
@@ -83,6 +84,7 @@ export default {
if (errors && errors.length) {
throw new Error(errors.join(' '));
}
+ this.$emit('toggledPaused');
} catch (e) {
this.onError(e);
} finally {
diff --git a/app/assets/javascripts/runner/components/runner_projects.vue b/app/assets/javascripts/runner/components/runner_projects.vue
index f8ec29b8a2..d080d34fdd 100644
--- a/app/assets/javascripts/runner/components/runner_projects.vue
+++ b/app/assets/javascripts/runner/components/runner_projects.vue
@@ -1,5 +1,5 @@
diff --git a/app/assets/javascripts/runner/components/runner_status_popover.vue b/app/assets/javascripts/runner/components/runner_status_popover.vue
new file mode 100644
index 0000000000..5b22f7828a
--- /dev/null
+++ b/app/assets/javascripts/runner/components/runner_status_popover.vue
@@ -0,0 +1,75 @@
+
+
+
+
+ {{ $options.I18N_STATUS_POPOVER_TITLE }}
+
+
+ {{ $options.I18N_STATUS_POPOVER_NEVER_CONTACTED }}
+
+
+ {{ content }}
+
+
+
+
+ {{ $options.I18N_STATUS_POPOVER_ONLINE }}
+
+ {{ onlineContactTimeoutDuration }}
+
+
+
+ {{ $options.I18N_STATUS_POPOVER_OFFLINE }}
+
+ {{ onlineContactTimeoutDuration }}
+
+
+
+ {{ $options.I18N_STATUS_POPOVER_STALE }}
+
+ {{ staleTimeoutDuration }}
+
+
+
+
diff --git a/app/assets/javascripts/runner/components/runner_update_form.vue b/app/assets/javascripts/runner/components/runner_update_form.vue
index e44450a2a8..119e5236f8 100644
--- a/app/assets/javascripts/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/runner/components/runner_update_form.vue
@@ -138,7 +138,11 @@ export default {
>
{{ __('Lock to current projects') }}
- {{ s__('Runners|Use the runner for the currently assigned projects only.') }}
+ {{
+ s__(
+ 'Runners|Use the runner for the currently assigned projects only. Only administrators can change the assigned projects.',
+ )
+ }}
diff --git a/app/assets/javascripts/runner/components/search_tokens/paused_token_config.js b/app/assets/javascripts/runner/components/search_tokens/paused_token_config.js
new file mode 100644
index 0000000000..1bab875a8a
--- /dev/null
+++ b/app/assets/javascripts/runner/components/search_tokens/paused_token_config.js
@@ -0,0 +1,28 @@
+import { __, s__ } from '~/locale';
+import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
+import { PARAM_KEY_PAUSED } from '../../constants';
+
+const options = [
+ { value: 'true', title: __('Yes') },
+ { value: 'false', title: __('No') },
+];
+
+export const pausedTokenConfig = {
+ icon: 'pause',
+ title: s__('Runners|Paused'),
+ type: PARAM_KEY_PAUSED,
+ token: BaseToken,
+ unique: true,
+ options: options.map(({ value, title }) => ({
+ value,
+ // Replace whitespace with a special character to avoid
+ // splitting this value.
+ // Replacing in each option, as translations may also
+ // contain spaces!
+ // see: https://gitlab.com/gitlab-org/gitlab/-/issues/344142
+ // see: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1438
+ title: title.replace(' ', '\u00a0'),
+ })),
+ operators: OPERATOR_IS_ONLY,
+};
diff --git a/app/assets/javascripts/runner/components/search_tokens/status_token_config.js b/app/assets/javascripts/runner/components/search_tokens/status_token_config.js
index 79038eb822..f28bd491ea 100644
--- a/app/assets/javascripts/runner/components/search_tokens/status_token_config.js
+++ b/app/assets/javascripts/runner/components/search_tokens/status_token_config.js
@@ -2,8 +2,6 @@ import { __, s__ } from '~/locale';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import {
- STATUS_ACTIVE,
- STATUS_PAUSED,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_NEVER_CONTACTED,
@@ -12,8 +10,6 @@ import {
} from '../../constants';
const options = [
- { value: STATUS_ACTIVE, title: s__('Runners|Active') },
- { value: STATUS_PAUSED, title: s__('Runners|Paused') },
{ value: STATUS_ONLINE, title: s__('Runners|Online') },
{ value: STATUS_OFFLINE, title: s__('Runners|Offline') },
{ value: STATUS_NEVER_CONTACTED, title: s__('Runners|Never contacted') },
diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js
index bd5be2175a..b9621c26b5 100644
--- a/app/assets/javascripts/runner/constants.js
+++ b/app/assets/javascripts/runner/constants.js
@@ -21,18 +21,39 @@ export const I18N_GROUP_RUNNER_DESCRIPTION = s__(
);
export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one or more projects');
-// Status
-export const I18N_ONLINE_RUNNER_TIMEAGO_DESCRIPTION = s__(
+// Status help popover
+export const I18N_STATUS_POPOVER_TITLE = s__('Runners|Runner statuses');
+
+export const I18N_STATUS_POPOVER_NEVER_CONTACTED = s__('Runners|Never contacted:');
+export const I18N_STATUS_POPOVER_NEVER_CONTACTED_DESCRIPTION = s__(
+ 'Runners|Runner has never contacted GitLab (when you register a runner, use %{codeStart}gitlab-runner run%{codeEnd} to bring it online)',
+);
+export const I18N_STATUS_POPOVER_ONLINE = s__('Runners|Online:');
+export const I18N_STATUS_POPOVER_ONLINE_DESCRIPTION = s__(
+ 'Runners|Runner has contacted GitLab within the last %{elapsedTime}',
+);
+export const I18N_STATUS_POPOVER_OFFLINE = s__('Runners|Offline:');
+export const I18N_STATUS_POPOVER_OFFLINE_DESCRIPTION = s__(
+ 'Runners|Runner has not contacted GitLab in more than %{elapsedTime}',
+);
+export const I18N_STATUS_POPOVER_STALE = s__('Runners|Stale:');
+export const I18N_STATUS_POPOVER_STALE_DESCRIPTION = s__(
+ 'Runners|Runner has not contacted GitLab in more than %{elapsedTime}',
+);
+
+// Status tooltips
+export const I18N_ONLINE_TIMEAGO_TOOLTIP = s__(
'Runners|Runner is online; last contact was %{timeAgo}',
);
-export const I18N_NEVER_CONTACTED_RUNNER_DESCRIPTION = s__(
- 'Runners|This runner has never contacted this instance',
+export const I18N_NEVER_CONTACTED_TOOLTIP = s__('Runners|Runner has never contacted this instance');
+export const I18N_OFFLINE_TIMEAGO_TOOLTIP = s__(
+ 'Runners|Runner is offline; last contact was %{timeAgo}',
);
-export const I18N_OFFLINE_RUNNER_TIMEAGO_DESCRIPTION = s__(
- 'Runners|No recent contact from this runner; last contact was %{timeAgo}',
+export const I18N_STALE_TIMEAGO_TOOLTIP = s__(
+ 'Runners|Runner is stale; last contact was %{timeAgo}',
);
-export const I18N_STALE_RUNNER_DESCRIPTION = s__(
- 'Runners|No contact from this runner in over 3 months',
+export const I18N_STALE_NEVER_CONTACTED_TOOLTIP = s__(
+ 'Runners|Runner is stale; it has never contacted this instance',
);
// Actions
@@ -46,15 +67,23 @@ export const I18N_RESUME = __('Resume');
export const I18N_RESUME_TOOLTIP = s__('Runners|Resume accepting jobs');
export const I18N_DELETE_RUNNER = s__('Runners|Delete runner');
+export const I18N_DELETE_DISABLED_MANY_PROJECTS = s__(
+ 'Runners|Multi-project runners cannot be deleted',
+);
+export const I18N_DELETE_DISABLED_UNKNOWN_REASON = s__(
+ 'Runners|Runner cannot be deleted, please contact your administrator',
+);
export const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted');
-export const I18N_LOCKED_RUNNER_DESCRIPTION = s__('Runners|You cannot assign to other projects');
+export const I18N_LOCKED_RUNNER_DESCRIPTION = s__(
+ 'Runners|Runner is locked and available for currently assigned projects only. Only administrators can change the assigned projects.',
+);
// Runner details
export const I18N_ASSIGNED_PROJECTS = s__('Runners|Assigned Projects (%{projectCount})');
export const I18N_NONE = __('None');
-export const I18N_NO_JOBS_FOUND = s__('Runner|This runner has not run any jobs.');
+export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.');
// Styles
@@ -66,6 +95,7 @@ export const RUNNER_TAG_BG_CLASS = 'gl-bg-blue-100';
// - GlFilteredSearch tokens type
export const PARAM_KEY_STATUS = 'status';
+export const PARAM_KEY_PAUSED = 'paused';
export const PARAM_KEY_RUNNER_TYPE = 'runner_type';
export const PARAM_KEY_TAG = 'tag';
export const PARAM_KEY_SEARCH = 'search';
@@ -83,9 +113,6 @@ export const PROJECT_TYPE = 'PROJECT_TYPE';
// CiRunnerStatus
-export const STATUS_ACTIVE = 'ACTIVE';
-export const STATUS_PAUSED = 'PAUSED';
-
export const STATUS_ONLINE = 'ONLINE';
export const STATUS_NEVER_CONTACTED = 'NEVER_CONTACTED';
export const STATUS_OFFLINE = 'OFFLINE';
diff --git a/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql b/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql
index 2b1decd3dd..14585e62bf 100644
--- a/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql
+++ b/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql
@@ -1,4 +1,4 @@
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getRunnerJobs($id: CiRunnerID!, $first: Int, $last: Int, $before: String, $after: String) {
runner(id: $id) {
diff --git a/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql b/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql
index f97237b826..cb27de7c20 100644
--- a/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql
+++ b/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql
@@ -1,4 +1,4 @@
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getRunnerProjects(
$id: CiRunnerID!
diff --git a/app/assets/javascripts/runner/graphql/list/admin_runners.query.graphql b/app/assets/javascripts/runner/graphql/list/admin_runners.query.graphql
index 8df4c2fc65..5d0450e741 100644
--- a/app/assets/javascripts/runner/graphql/list/admin_runners.query.graphql
+++ b/app/assets/javascripts/runner/graphql/list/admin_runners.query.graphql
@@ -1,11 +1,12 @@
#import "~/runner/graphql/list/list_item.fragment.graphql"
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getRunners(
$before: String
$after: String
$first: Int
$last: Int
+ $paused: Boolean
$status: CiRunnerStatus
$type: CiRunnerType
$tagList: [String!]
@@ -17,6 +18,7 @@ query getRunners(
after: $after
first: $first
last: $last
+ paused: $paused
status: $status
type: $type
tagList: $tagList
diff --git a/app/assets/javascripts/runner/graphql/list/admin_runners_count.query.graphql b/app/assets/javascripts/runner/graphql/list/admin_runners_count.query.graphql
index 181a4495ca..1dd258a352 100644
--- a/app/assets/javascripts/runner/graphql/list/admin_runners_count.query.graphql
+++ b/app/assets/javascripts/runner/graphql/list/admin_runners_count.query.graphql
@@ -1,10 +1,11 @@
query getRunnersCount(
+ $paused: Boolean
$status: CiRunnerStatus
$type: CiRunnerType
$tagList: [String!]
$search: String
) {
- runners(status: $status, type: $type, tagList: $tagList, search: $search) {
+ runners(paused: $paused, status: $status, type: $type, tagList: $tagList, search: $search) {
count
}
}
diff --git a/app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql b/app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql
new file mode 100644
index 0000000000..c01f1edb45
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql
@@ -0,0 +1,3 @@
+query getCheckedRunnerIds {
+ checkedRunnerIds @client
+}
diff --git a/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql b/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql
index b517f5e89a..b4f2b5cd8c 100644
--- a/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql
+++ b/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql
@@ -1,5 +1,5 @@
#import "~/runner/graphql/list/list_item.fragment.graphql"
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getGroupRunners(
$groupFullPath: ID!
@@ -7,6 +7,7 @@ query getGroupRunners(
$after: String
$first: Int
$last: Int
+ $paused: Boolean
$status: CiRunnerStatus
$type: CiRunnerType
$search: String
@@ -20,6 +21,7 @@ query getGroupRunners(
after: $after
first: $first
last: $last
+ paused: $paused
status: $status
type: $type
search: $search
@@ -30,6 +32,7 @@ query getGroupRunners(
editUrl
node {
...ListItem
+ projectCount # Used to determine why some project runners can't be deleted
}
}
pageInfo {
diff --git a/app/assets/javascripts/runner/graphql/list/group_runners_count.query.graphql b/app/assets/javascripts/runner/graphql/list/group_runners_count.query.graphql
index 554eb09e37..958b4ea0dd 100644
--- a/app/assets/javascripts/runner/graphql/list/group_runners_count.query.graphql
+++ b/app/assets/javascripts/runner/graphql/list/group_runners_count.query.graphql
@@ -1,5 +1,6 @@
query getGroupRunnersCount(
$groupFullPath: ID!
+ $paused: Boolean
$status: CiRunnerStatus
$type: CiRunnerType
$tagList: [String!]
@@ -9,6 +10,7 @@ query getGroupRunnersCount(
id # Apollo required
runners(
membership: DESCENDANTS
+ paused: $paused
status: $status
type: $type
tagList: $tagList
diff --git a/app/assets/javascripts/runner/graphql/list/local_state.js b/app/assets/javascripts/runner/graphql/list/local_state.js
new file mode 100644
index 0000000000..e87bc72c86
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/local_state.js
@@ -0,0 +1,63 @@
+import { makeVar } from '@apollo/client/core';
+import typeDefs from './typedefs.graphql';
+
+/**
+ * Local state for checkable runner items.
+ *
+ * Usage:
+ *
+ * ```
+ * import { createLocalState } from '~/runner/graphql/list/local_state';
+ *
+ * // initialize local state
+ * const { cacheConfig, typeDefs, localMutations } = createLocalState();
+ *
+ * // configure the client
+ * apolloClient = createApolloClient({}, { cacheConfig, typeDefs });
+ *
+ * // modify local state
+ * localMutations.setRunnerChecked( ... )
+ * ```
+ *
+ * Note: Currently only in use behind a feature flag:
+ * admin_runners_bulk_delete for the admin list, rollout issue:
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/353981
+ *
+ * @returns {Object} An object to configure an Apollo client:
+ * contains cacheConfig, typeDefs, localMutations.
+ */
+export const createLocalState = () => {
+ const checkedRunnerIdsVar = makeVar({});
+
+ const cacheConfig = {
+ typePolicies: {
+ Query: {
+ fields: {
+ checkedRunnerIds() {
+ return Object.entries(checkedRunnerIdsVar())
+ .filter(([, isChecked]) => isChecked)
+ .map(([key]) => key);
+ },
+ },
+ },
+ },
+ };
+
+ const localMutations = {
+ setRunnerChecked({ runner, isChecked }) {
+ checkedRunnerIdsVar({
+ ...checkedRunnerIdsVar(),
+ [runner.id]: isChecked,
+ });
+ },
+ clearChecked() {
+ checkedRunnerIdsVar({});
+ },
+ };
+
+ return {
+ cacheConfig,
+ typeDefs,
+ localMutations,
+ };
+};
diff --git a/app/assets/javascripts/runner/graphql/list/typedefs.graphql b/app/assets/javascripts/runner/graphql/list/typedefs.graphql
new file mode 100644
index 0000000000..24e9e20cc8
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/typedefs.graphql
@@ -0,0 +1,3 @@
+extend type Query {
+ checkedRunnerIds: [ID!]!
+}
diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
index 35fd7fff6d..b299d7c40f 100644
--- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
@@ -1,9 +1,9 @@
@@ -61,7 +63,7 @@ export default {
:src="item.avatar_url"
:entity-id="item.id"
:entity-name="item[name]"
- shape="rect"
+ :shape="$options.AVATAR_SHAPE_OPTION_RECT"
:size="32"
/>
diff --git a/app/assets/javascripts/search_settings/components/search_settings.vue b/app/assets/javascripts/search_settings/components/search_settings.vue
index 3e23b8a343..f6439c6f4c 100644
--- a/app/assets/javascripts/search_settings/components/search_settings.vue
+++ b/app/assets/javascripts/search_settings/components/search_settings.vue
@@ -1,6 +1,6 @@
+
+
+
+
+ {{ badge.tooltipText }}
+
+
+
+ {{ badge.text }}
+
+
+
+
diff --git a/app/assets/javascripts/security_configuration/components/training_provider_list.vue b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
index bb540303cf..ef50d085ae 100644
--- a/app/assets/javascripts/security_configuration/components/training_provider_list.vue
+++ b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
@@ -3,6 +3,7 @@ import {
GlAlert,
GlTooltipDirective,
GlCard,
+ GlFormRadio,
GlToggle,
GlLink,
GlSkeletonLoader,
@@ -44,6 +45,7 @@ export default {
components: {
GlAlert,
GlCard,
+ GlFormRadio,
GlToggle,
GlLink,
GlSkeletonLoader,
@@ -79,6 +81,9 @@ export default {
};
},
computed: {
+ primaryProviderId() {
+ return this.securityTrainingProviders.find(({ isPrimary }) => isPrimary)?.id;
+ },
enabledProviders() {
return this.securityTrainingProviders.filter(({ isEnabled }) => isEnabled);
},
@@ -256,31 +261,19 @@ export default {
{{ __('Learn more.') }}
-
-
-
-
- {{ $options.i18n.primaryTraining }}
-
+ {{ $options.i18n.primaryTraining }}
-
+
diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js
index 8416692dd2..65cf1ec27a 100644
--- a/app/assets/javascripts/security_configuration/index.js
+++ b/app/assets/javascripts/security_configuration/index.js
@@ -25,6 +25,7 @@ export const initSecurityConfiguration = (el) => {
gitlabCiHistoryPath,
autoDevopsHelpPagePath,
autoDevopsPath,
+ vulnerabilityTrainingDocsPath,
} = el.dataset;
const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures(
@@ -41,6 +42,7 @@ export const initSecurityConfiguration = (el) => {
upgradePath,
autoDevopsHelpPagePath,
autoDevopsPath,
+ vulnerabilityTrainingDocsPath,
},
render(createElement) {
return createElement(SecurityConfigurationApp, {
diff --git a/app/assets/javascripts/security_configuration/utils.js b/app/assets/javascripts/security_configuration/utils.js
index 173560f837..df23698ba7 100644
--- a/app/assets/javascripts/security_configuration/utils.js
+++ b/app/assets/javascripts/security_configuration/utils.js
@@ -30,6 +30,10 @@ export const augmentFeatures = (securityFeatures, complianceFeatures, features =
augmented.secondary = { ...augmented.secondary, ...featuresByType[feature.secondary.type] };
}
+ if (augmented.badge && augmented.metaInfoPath) {
+ augmented.badge.badgeHref = augmented.metaInfoPath;
+ }
+
return augmented;
};
diff --git a/app/assets/javascripts/serverless/components/missing_prometheus.vue b/app/assets/javascripts/serverless/components/missing_prometheus.vue
index 0023c64e3e..d9e6bb5009 100644
--- a/app/assets/javascripts/serverless/components/missing_prometheus.vue
+++ b/app/assets/javascripts/serverless/components/missing_prometheus.vue
@@ -1,7 +1,7 @@
-
-
-
+
+
+
+
+
diff --git a/app/assets/javascripts/sidebar/components/copy_email_to_clipboard.vue b/app/assets/javascripts/sidebar/components/copy_email_to_clipboard.vue
index 0d8cb8cb2b..8528ad56dd 100644
--- a/app/assets/javascripts/sidebar/components/copy_email_to_clipboard.vue
+++ b/app/assets/javascripts/sidebar/components/copy_email_to_clipboard.vue
@@ -1,5 +1,5 @@
@@ -45,6 +55,7 @@ export default {
block
:text="currentStatusLabel"
toggle-class="dropdown-menu-toggle gl-mb-2"
+ @hide="hideDropdown"
>
-import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlButton, GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { __, n__, sprintf } from '~/locale';
import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
@@ -8,9 +8,10 @@ export default {
GlTooltip: GlTooltipDirective,
},
components: {
- userAvatarImage,
+ GlButton,
GlIcon,
GlLoadingIcon,
+ userAvatarImage,
},
props: {
loading: {
@@ -124,9 +125,13 @@ export default {
-
- {{ toggleLabel }}
-
+ {{ toggleLabel }}
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
index 361a082def..a11468c876 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
@@ -73,10 +73,10 @@ export default {
v-gl-tooltip="tooltipOption"
:href="reviewerUrl"
:title="tooltipTitle"
- class="d-inline-block"
+ class="gl-display-inline-block"
>
-
+
diff --git a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
index 9485802d3d..3e6be3487b 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
@@ -94,28 +94,40 @@ export default {
-
-
+ :root-path="rootPath"
+ :issuable-type="issuableType"
+ class="gl-word-break-word gl-mr-2"
+ data-css-area="user"
+ >
{{ user.name }}
@{{ user.username }}
+
import { GlButton, GlSafeHtmlDirective } from '@gitlab/ui';
import { joinPaths } from '~/lib/utils/url_utility';
-import { sprintf, s__ } from '../../../locale';
+import { sprintf, s__ } from '~/locale';
export default {
name: 'TimeTrackingHelpState',
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index d222a2af38..fdbcef22bb 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -207,7 +207,7 @@ export default {
class="hide-collapsed gl-line-height-20 gl-text-gray-900 gl-display-flex gl-align-items-center"
>
{{ __('Time tracking') }}
-
+
{
const sourceData = cache.readQuery({ query: getIssueStateQuery });
@@ -14,7 +12,6 @@ const resolvers = {
});
cache.writeQuery({ query: getIssueStateQuery, data });
},
- ...workItemResolvers.Mutation,
},
};
diff --git a/app/assets/javascripts/sidebar/queries/issuable_labels.subscription.graphql b/app/assets/javascripts/sidebar/queries/issuable_labels.subscription.graphql
new file mode 100644
index 0000000000..edd713badd
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/issuable_labels.subscription.graphql
@@ -0,0 +1,22 @@
+#import "~/graphql_shared/fragments/label.fragment.graphql"
+
+subscription issuableLabelsUpdated($issuableId: IssuableID!) {
+ issuableLabelsUpdated(issuableId: $issuableId) {
+ ... on Issue {
+ id
+ labels {
+ nodes {
+ ...Label
+ }
+ }
+ }
+ ... on MergeRequest {
+ id
+ labels {
+ nodes {
+ ...Label
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql b/app/assets/javascripts/sidebar/queries/sidebar_details.query.graphql
similarity index 100%
rename from app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql
rename to app/assets/javascripts/sidebar/queries/sidebar_details.query.graphql
diff --git a/app/assets/javascripts/sidebar/queries/sidebarDetailsMR.query.graphql b/app/assets/javascripts/sidebar/queries/sidebar_details_mr.query.graphql
similarity index 100%
rename from app/assets/javascripts/sidebar/queries/sidebarDetailsMR.query.graphql
rename to app/assets/javascripts/sidebar/queries/sidebar_details_mr.query.graphql
diff --git a/app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql b/app/assets/javascripts/sidebar/queries/update_status.mutation.graphql
similarity index 100%
rename from app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql
rename to app/assets/javascripts/sidebar/queries/update_status.mutation.graphql
diff --git a/app/assets/javascripts/sidebar/services/sidebar_service.js b/app/assets/javascripts/sidebar/services/sidebar_service.js
index d8ab8f1c65..90d8f2098b 100644
--- a/app/assets/javascripts/sidebar/services/sidebar_service.js
+++ b/app/assets/javascripts/sidebar/services/sidebar_service.js
@@ -1,10 +1,10 @@
-import sidebarDetailsIssueQuery from 'ee_else_ce/sidebar/queries/sidebarDetails.query.graphql';
+import sidebarDetailsIssueQuery from 'ee_else_ce/sidebar/queries/sidebar_details.query.graphql';
import { TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import axios from '~/lib/utils/axios_utils';
import reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.graphql';
-import sidebarDetailsMRQuery from '../queries/sidebarDetailsMR.query.graphql';
+import sidebarDetailsMRQuery from '../queries/sidebar_details_mr.query.graphql';
import toggleAttentionRequestedMutation from '../queries/toggle_attention_requested.mutation.graphql';
const queries = {
diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue
index e3aa29d5f8..e4a97f08c8 100644
--- a/app/assets/javascripts/snippets/components/edit.vue
+++ b/app/assets/javascripts/snippets/components/edit.vue
@@ -15,8 +15,8 @@ import TitleField from '~/vue_shared/components/form/title.vue';
import { SNIPPET_CREATE_MUTATION_ERROR, SNIPPET_UPDATE_MUTATION_ERROR } from '../constants';
import { getSnippetMixin } from '../mixins/snippets';
-import CreateSnippetMutation from '../mutations/createSnippet.mutation.graphql';
-import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql';
+import CreateSnippetMutation from '../mutations/create_snippet.mutation.graphql';
+import UpdateSnippetMutation from '../mutations/update_snippet.mutation.graphql';
import { markBlobPerformance } from '../utils/blob';
import { getErrorMessage } from '../utils/error';
@@ -238,9 +238,9 @@ export default {
>
- {{
- __('Cancel')
- }}
+
+ {{ __('Cancel') }}
+
diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue
index 9b24c8afe3..dd8f289701 100644
--- a/app/assets/javascripts/snippets/components/snippet_header.vue
+++ b/app/assets/javascripts/snippets/components/snippet_header.vue
@@ -21,7 +21,7 @@ import { __, s__, sprintf } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import createFlash, { FLASH_TYPES } from '~/flash';
-import DeleteSnippetMutation from '../mutations/deleteSnippet.mutation.graphql';
+import DeleteSnippetMutation from '../mutations/delete_snippet.mutation.graphql';
export const i18n = {
snippetSpamSuccess: sprintf(
@@ -294,9 +294,9 @@ export default {
{{ __('Delete snippet?') }}
-
- {{ errorMessage }}
-
+ {{
+ errorMessage
+ }}
diff --git a/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql b/app/assets/javascripts/snippets/fragments/snippet_base.fragment.graphql
similarity index 100%
rename from app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql
rename to app/assets/javascripts/snippets/fragments/snippet_base.fragment.graphql
diff --git a/app/assets/javascripts/snippets/mutations/createSnippet.mutation.graphql b/app/assets/javascripts/snippets/mutations/create_snippet.mutation.graphql
similarity index 100%
rename from app/assets/javascripts/snippets/mutations/createSnippet.mutation.graphql
rename to app/assets/javascripts/snippets/mutations/create_snippet.mutation.graphql
diff --git a/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql b/app/assets/javascripts/snippets/mutations/delete_snippet.mutation.graphql
similarity index 100%
rename from app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql
rename to app/assets/javascripts/snippets/mutations/delete_snippet.mutation.graphql
diff --git a/app/assets/javascripts/snippets/mutations/updateSnippet.mutation.graphql b/app/assets/javascripts/snippets/mutations/update_snippet.mutation.graphql
similarity index 100%
rename from app/assets/javascripts/snippets/mutations/updateSnippet.mutation.graphql
rename to app/assets/javascripts/snippets/mutations/update_snippet.mutation.graphql
diff --git a/app/assets/javascripts/sortable/constants.js b/app/assets/javascripts/sortable/constants.js
new file mode 100644
index 0000000000..7fddac00ab
--- /dev/null
+++ b/app/assets/javascripts/sortable/constants.js
@@ -0,0 +1,19 @@
+/**
+ * Default config options for sortablejs.
+ * @type {object}
+ *
+ * @example
+ * import Sortable from 'sortablejs';
+ *
+ * const sortable = Sortable.create(el, {
+ * ...defaultSortableOptions,
+ * });
+ */
+export const defaultSortableOptions = {
+ animation: 200,
+ forceFallback: true,
+ fallbackClass: 'is-dragging',
+ fallbackOnBody: true,
+ ghostClass: 'is-ghost',
+ fallbackTolerance: 1,
+};
diff --git a/app/assets/javascripts/sortable/sortable_config.js b/app/assets/javascripts/sortable/sortable_config.js
deleted file mode 100644
index a4c4cb7f10..0000000000
--- a/app/assets/javascripts/sortable/sortable_config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export default {
- animation: 200,
- forceFallback: true,
- fallbackClass: 'is-dragging',
- fallbackOnBody: true,
- ghostClass: 'is-ghost',
- fallbackTolerance: 1,
-};
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/sortable/utils.js
similarity index 68%
rename from app/assets/javascripts/boards/mixins/sortable_default_options.js
rename to app/assets/javascripts/sortable/utils.js
index 1bb0ee5b7e..c2c8fb03b5 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js
+++ b/app/assets/javascripts/sortable/utils.js
@@ -1,6 +1,6 @@
/* global DocumentTouch */
-import sortableConfig from '~/sortable/sortable_config';
+import { defaultSortableOptions } from './constants';
export function sortableStart() {
document.body.classList.add('is-dragging');
@@ -10,12 +10,12 @@ export function sortableEnd() {
document.body.classList.remove('is-dragging');
}
-export function getBoardSortableDefaultOptions(obj) {
+export function getSortableDefaultOptions(options) {
const touchEnabled =
'ontouchstart' in window || (window.DocumentTouch && document instanceof DocumentTouch);
const defaultSortOptions = {
- ...sortableConfig,
+ ...defaultSortableOptions,
filter: '.no-drag',
delay: touchEnabled ? 100 : 0,
scrollSensitivity: touchEnabled ? 60 : 100,
@@ -24,8 +24,8 @@ export function getBoardSortableDefaultOptions(obj) {
onEnd: sortableEnd,
};
- Object.keys(obj).forEach((key) => {
- defaultSortOptions[key] = obj[key];
- });
- return defaultSortOptions;
+ return {
+ ...defaultSortOptions,
+ ...options,
+ };
}
diff --git a/app/assets/javascripts/static_site_editor/components/edit_meta_modal.vue b/app/assets/javascripts/static_site_editor/components/edit_meta_modal.vue
index 8c3ee7b960..e69a6b8cd6 100644
--- a/app/assets/javascripts/static_site_editor/components/edit_meta_modal.vue
+++ b/app/assets/javascripts/static_site_editor/components/edit_meta_modal.vue
@@ -112,7 +112,6 @@ export default {
v-model="mergeRequestMeta"
:storage-key="$options.storageKey"
:clear="clearStorage"
- as-json
/>
-import { GlEmptyState, GlIcon, GlLink } from '@gitlab/ui';
+import { GlEmptyState, GlLink } from '@gitlab/ui';
+import { s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export default {
+ i18n: {
+ title: s__("Terraform|Your project doesn't have any Terraform state files"),
+ description: s__('Terraform|How to use GitLab-managed Terraform state?'),
+ },
+ docsUrl: helpPagePath('user/infrastructure/iac/terraform_state'),
components: {
GlEmptyState,
- GlIcon,
GlLink,
},
props: {
@@ -14,23 +19,13 @@ export default {
required: true,
},
},
- computed: {
- docsUrl() {
- return helpPagePath('user/infrastructure/iac/terraform_state');
- },
- },
};
-
+
-
- {{ s__('Terraform|How to use GitLab-managed Terraform State?') }}
-
-
+ {{ $options.i18n.description }}
diff --git a/app/assets/javascripts/terraform/components/states_table_actions.vue b/app/assets/javascripts/terraform/components/states_table_actions.vue
index 817c421823..1970d6d794 100644
--- a/app/assets/javascripts/terraform/components/states_table_actions.vue
+++ b/app/assets/javascripts/terraform/components/states_table_actions.vue
@@ -11,6 +11,7 @@ import {
GlModalDirective,
} from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
+import getStatesQuery from '../graphql/queries/get_states.query.graphql';
import addDataToState from '../graphql/mutations/add_data_to_state.mutation.graphql';
import lockState from '../graphql/mutations/lock_state.mutation.graphql';
import removeState from '../graphql/mutations/remove_state.mutation.graphql';
@@ -148,7 +149,7 @@ export default {
variables: {
stateID: this.state.id,
},
- refetchQueries: () => ['getStates'],
+ refetchQueries: () => [{ query: getStatesQuery }],
awaitRefetchQueries: true,
notifyOnNetworkStatusChange: true,
})
diff --git a/app/assets/javascripts/terraform/graphql/queries/get_states.query.graphql b/app/assets/javascripts/terraform/graphql/queries/get_states.query.graphql
index 4d26ea88dd..2ae7b7d905 100644
--- a/app/assets/javascripts/terraform/graphql/queries/get_states.query.graphql
+++ b/app/assets/javascripts/terraform/graphql/queries/get_states.query.graphql
@@ -1,5 +1,5 @@
#import "../fragments/state.fragment.graphql"
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getStates($projectPath: ID!, $first: Int, $last: Int, $before: String, $after: String) {
project(fullPath: $projectPath) {
diff --git a/app/assets/javascripts/tracking/tracking.js b/app/assets/javascripts/tracking/tracking.js
index 173eef0646..f299c57b33 100644
--- a/app/assets/javascripts/tracking/tracking.js
+++ b/app/assets/javascripts/tracking/tracking.js
@@ -13,8 +13,12 @@ import {
const ALLOWED_URL_HASHES = ['#diff', '#note'];
export default class Tracking {
- static queuedEvents = [];
+ static nonInitializedQueue = [];
static initialized = false;
+ static definitionsLoaded = false;
+ static definitionsManifest = {};
+ static definitionsEventsQueue = [];
+ static definitions = [];
/**
* (Legacy) Determines if tracking is enabled at the user level.
@@ -54,13 +58,71 @@ export default class Tracking {
}
if (!this.initialized) {
- this.queuedEvents.push(eventData);
+ this.nonInitializedQueue.push(eventData);
return false;
}
return dispatchSnowplowEvent(...eventData);
}
+ /**
+ * Preloads event definitions.
+ *
+ * @returns {undefined}
+ */
+ static loadDefinitions() {
+ // TODO: fetch definitions from the server and flush the queue
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/358256
+ this.definitionsLoaded = true;
+
+ while (this.definitionsEventsQueue.length) {
+ this.dispatchFromDefinition(...this.definitionsEventsQueue.shift());
+ }
+ }
+
+ /**
+ * Dispatches a structured event with data from its event definition.
+ *
+ * @param {String} basename
+ * @param {Object} eventData
+ * @returns {undefined|Boolean}
+ */
+ static definition(basename, eventData = {}) {
+ if (!this.enabled()) {
+ return false;
+ }
+
+ if (!(basename in this.definitionsManifest)) {
+ throw new Error(`Missing Snowplow event definition "${basename}"`);
+ }
+
+ return this.dispatchFromDefinition(basename, eventData);
+ }
+
+ /**
+ * Builds an event with data from a valid definition and sends it to
+ * Snowplow. If the definitions are not loaded, it pushes the data to a queue.
+ *
+ * @param {String} basename
+ * @param {Object} eventData
+ * @returns {undefined|Boolean}
+ */
+ static dispatchFromDefinition(basename, eventData) {
+ if (!this.definitionsLoaded) {
+ this.definitionsEventsQueue.push([basename, eventData]);
+
+ return false;
+ }
+
+ const eventDefinition = this.definitions.find((definition) => definition.key === basename);
+
+ return this.event(
+ eventData.category ?? eventDefinition.category,
+ eventData.action ?? eventDefinition.action,
+ eventData,
+ );
+ }
+
/**
* Dispatches any event emitted before initialization.
*
@@ -69,8 +131,8 @@ export default class Tracking {
static flushPendingEvents() {
this.initialized = true;
- while (this.queuedEvents.length) {
- dispatchSnowplowEvent(...this.queuedEvents.shift());
+ while (this.nonInitializedQueue.length) {
+ dispatchSnowplowEvent(...this.nonInitializedQueue.shift());
}
}
diff --git a/app/assets/javascripts/user_lists/components/user_list_form.vue b/app/assets/javascripts/user_lists/components/user_list_form.vue
index b53aaf46ac..44aa2d9a5b 100644
--- a/app/assets/javascripts/user_lists/components/user_list_form.vue
+++ b/app/assets/javascripts/user_lists/components/user_list_form.vue
@@ -84,7 +84,7 @@ export default {
-
+
{{ saveButtonLabel }}
diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js
index 656c851aa3..f7a5589af9 100644
--- a/app/assets/javascripts/users_select/index.js
+++ b/app/assets/javascripts/users_select/index.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, prefer-rest-params, consistent-return, no-shadow, no-self-compare, no-unused-expressions, yoda, prefer-spread, babel/camelcase, no-param-reassign */
+/* eslint-disable func-names, prefer-rest-params, consistent-return, no-shadow, no-self-compare, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */
/* global Issuable */
/* global emitSidebarEvent */
@@ -11,10 +11,10 @@ import {
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { isUserBusy } from '~/set_status_modal/utils';
import { fixTitle, dispose } from '~/tooltips';
-import axios from '../lib/utils/axios_utils';
-import { parseBoolean, spriteIcon } from '../lib/utils/common_utils';
-import { loadCSSFile } from '../lib/utils/css_utils';
-import { s__, __, sprintf } from '../locale';
+import axios from '~/lib/utils/axios_utils';
+import { parseBoolean, spriteIcon } from '~/lib/utils/common_utils';
+import { loadCSSFile } from '~/lib/utils/css_utils';
+import { s__, __, sprintf } from '~/locale';
import { getAjaxUsersSelectOptions, getAjaxUsersSelectParams } from './utils';
// TODO: remove eventHub hack after code splitting refactor
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue
index 25dbb614c1..0e31f97b9d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue
@@ -102,7 +102,11 @@ export default {
{{ message }}
{{ message }}
-
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
index 684386883c..f1b89c42fb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
@@ -66,7 +66,15 @@ export default {
return this.loadingState === LOADING_STATES.expandedLoading;
},
isCollapsible() {
- return !this.isLoadingSummary && this.loadingState !== LOADING_STATES.collapsedError;
+ if (!this.isLoadingSummary && this.loadingState !== LOADING_STATES.collapsedError) {
+ if (this.shouldCollapse) {
+ return this.shouldCollapse();
+ }
+
+ return true;
+ }
+
+ return false;
},
hasFullData() {
return this.fullData.length > 0;
@@ -86,7 +94,7 @@ export default {
);
},
statusIconName() {
- if (this.hasFetchError) return EXTENSION_ICONS.error;
+ if (this.hasFetchError) return EXTENSION_ICONS.failed;
if (this.isLoadingSummary) return null;
return this.statusIcon(this.collapsedData);
@@ -128,7 +136,7 @@ export default {
}
}),
toggleCollapsed(e) {
- if (!e?.target?.closest('.btn:not(.btn-icon),a')) {
+ if (this.isCollapsible && !e?.target?.closest('.btn:not(.btn-icon),a')) {
this.isCollapsed = !this.isCollapsed;
this.triggerRedisTracking();
@@ -214,7 +222,7 @@ export default {
// To allow for text to be selected we check if the the user is clicking
// or selecting, if they are selecting the time difference should be
// more than 200ms
- if (up - this.down < 200) {
+ if (up - this.down < 200 && !e?.target?.closest('.btn-icon')) {
this.toggleCollapsed(e);
}
},
@@ -226,7 +234,12 @@ export default {
-The Standard Reference Architectures are designed to be platform agnostic, with everything being run on VMs via [Omnibus GitLab](https://docs.gitlab.com/omnibus/). While testing occurs primarily on GCP, ad-hoc testing has shown that they perform similarly on equivalently specced hardware on other Cloud Providers or if run on premises (bare-metal).
-
-### Cost to run
+## Cost to run