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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-14 21:08:38 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-14 21:08:38 +0300
commit14160fad80415337f8c08755af53ee994b4a7518 (patch)
treebfe1bf6bad8cda3e3bbf905c9d8ac742420dd8a3 /app
parent7a33080fff9a735cbe77968d67b13ffa92c0ffae (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue3
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue27
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue1
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/services_item.vue1
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue3
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue10
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/constants.js5
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue83
-rw-r--r--app/assets/javascripts/graphql_shared/issuable_client.js26
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue23
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue129
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/utils.js15
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row.vue15
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue6
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/get_pipeline_failed_jobs.query.graphql34
-rw-r--r--app/assets/javascripts/search/sidebar/components/label_filter/data.js23
-rw-r--r--app/assets/javascripts/search/store/actions.js17
-rw-r--r--app/assets/javascripts/search/store/constants.js2
-rw-r--r--app/assets/javascripts/search/store/getters.js35
-rw-r--r--app/assets/javascripts/search/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/search/store/mutations.js3
-rw-r--r--app/assets/javascripts/search/store/state.js1
-rw-r--r--app/assets/javascripts/snippets/components/show.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/clone_dropdown.vue91
-rw-r--r--app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.stories.js33
-rw-r--r--app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue59
-rw-r--r--app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown_item.vue56
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue29
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue15
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue20
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue11
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue2
-rw-r--r--app/assets/javascripts/work_items/graphql/add_hierarchy_child.mutation.graphql3
-rw-r--r--app/assets/javascripts/work_items/graphql/cache_utils.js38
-rw-r--r--app/assets/javascripts/work_items/graphql/remove_hierarchy_child.mutation.graphql3
-rw-r--r--app/assets/stylesheets/fonts.scss28
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss16
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss16
-rw-r--r--app/assets/stylesheets/startup/startup-signin.scss14
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb12
-rw-r--r--app/controllers/projects/pipelines_controller.rb2
-rw-r--r--app/helpers/ide_helper.rb4
-rw-r--r--app/views/layouts/_loading_hints.html.haml6
-rw-r--r--app/views/projects/merge_requests/creations/_new_compare.html.haml10
-rw-r--r--app/views/users/_profile_basic_info.html.haml2
46 files changed, 618 insertions, 322 deletions
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue b/app/assets/javascripts/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue
index b3befcf1cbc..c2e4c234d2b 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue
@@ -3,6 +3,7 @@ import { GlDrawer } from '@gitlab/ui';
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import { __ } from '~/locale';
+import { EDITOR_APP_DRAWER_NONE } from '~/ci/pipeline_editor/constants';
import FirstPipelineCard from './cards/first_pipeline_card.vue';
import GettingStartedCard from './cards/getting_started_card.vue';
import PipelineConfigReferenceCard from './cards/pipeline_config_reference_card.vue';
@@ -41,7 +42,7 @@ export default {
},
methods: {
closeDrawer() {
- this.$emit('close-drawer');
+ this.$emit('switch-drawer', EDITOR_APP_DRAWER_NONE);
},
},
};
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue
index 069704b7bf4..6ba8884f9a6 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue
@@ -3,7 +3,14 @@ import { GlButton } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import Tracking from '~/tracking';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { pipelineEditorTrackingOptions, TEMPLATE_REPOSITORY_URL } from '../../constants';
+import {
+ EDITOR_APP_DRAWER_AI_ASSISTANT,
+ EDITOR_APP_DRAWER_HELP,
+ EDITOR_APP_DRAWER_JOB_ASSISTANT,
+ EDITOR_APP_DRAWER_NONE,
+ pipelineEditorTrackingOptions,
+ TEMPLATE_REPOSITORY_URL,
+} from '../../constants';
export default {
i18n: {
@@ -19,7 +26,7 @@ export default {
mixins: [glFeatureFlagMixin(), Tracking.mixin()],
inject: ['aiChatAvailable'],
props: {
- showDrawer: {
+ showHelpDrawer: {
type: Boolean,
required: true,
},
@@ -38,22 +45,24 @@ export default {
},
},
methods: {
- toggleDrawer() {
- if (this.showDrawer) {
- this.$emit('close-drawer');
+ toggleHelpDrawer() {
+ if (this.showHelpDrawer) {
+ this.$emit('switch-drawer', EDITOR_APP_DRAWER_NONE);
} else {
- this.$emit('open-drawer');
+ this.$emit('switch-drawer', EDITOR_APP_DRAWER_HELP);
this.trackHelpDrawerClick();
}
},
toggleJobAssistantDrawer() {
this.$emit(
- this.showJobAssistantDrawer ? 'close-job-assistant-drawer' : 'open-job-assistant-drawer',
+ 'switch-drawer',
+ this.showJobAssistantDrawer ? EDITOR_APP_DRAWER_NONE : EDITOR_APP_DRAWER_JOB_ASSISTANT,
);
},
toggleAiAssistantDrawer() {
this.$emit(
- this.showAiAssistantDrawer ? 'close-ai-assistant-drawer' : 'open-ai-assistant-drawer',
+ 'switch-drawer',
+ this.showAiAssistantDrawer ? EDITOR_APP_DRAWER_NONE : EDITOR_APP_DRAWER_AI_ASSISTANT,
);
},
trackHelpDrawerClick() {
@@ -90,7 +99,7 @@ export default {
size="small"
data-testid="drawer-toggle"
data-qa-selector="drawer_toggle"
- @click="toggleDrawer"
+ @click="toggleHelpDrawer"
>
{{ $options.i18n.help }}
</gl-button>
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue
index 653d01551c4..794763e0cd8 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/artifacts_and_cache_item.vue
@@ -96,6 +96,7 @@ export default {
category="tertiary"
icon="remove"
:data-testid="entry.generateDeleteButtonDataTestId(index)"
+ :aria-label="entry.generateDeleteButtonDataTestId(index)"
@click="deleteStringArrayItem(`${entry.key}[${index}]`)"
/>
</div>
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/services_item.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/services_item.vue
index fea9df16038..0b12d0aedd6 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/services_item.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/services_item.vue
@@ -74,6 +74,7 @@ export default {
category="tertiary"
icon="remove"
:data-testid="`delete-job-service-button-${index}`"
+ :aria-label="`delete-job-service-button-${index}`"
@click="deleteService(index)"
/>
<gl-form-group :label="$options.i18n.SERVICE_NAME">
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue
index 9eeaac32e95..1a58a112e50 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue
@@ -5,6 +5,7 @@ import { get, omit, toPath } from 'lodash';
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import eventHub, { SCROLL_EDITOR_TO_BOTTOM } from '~/ci/pipeline_editor/event_hub';
+import { EDITOR_APP_DRAWER_NONE } from '~/ci/pipeline_editor/constants';
import getRunnerTags from '../../graphql/queries/runner_tags.query.graphql';
import { JOB_TEMPLATE, JOB_RULES_WHEN, i18n } from './constants';
import { removeEmptyObj, trimFields, validateEmptyValue, validateStartIn } from './utils';
@@ -101,7 +102,7 @@ export default {
methods: {
closeDrawer() {
this.clearJob();
- this.$emit('close-job-assistant-drawer');
+ this.$emit('switch-drawer', EDITOR_APP_DRAWER_NONE);
},
addCiConfig() {
this.validateJob();
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue
index f8c8be76bd5..a954615ca8a 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue
@@ -87,19 +87,19 @@ export default {
type: String,
required: true,
},
- isNewCiConfigFile: {
+ showHelpDrawer: {
type: Boolean,
required: true,
},
- showDrawer: {
+ showJobAssistantDrawer: {
type: Boolean,
required: true,
},
- showJobAssistantDrawer: {
+ showAiAssistantDrawer: {
type: Boolean,
required: true,
},
- showAiAssistantDrawer: {
+ isNewCiConfigFile: {
type: Boolean,
required: true,
},
@@ -196,7 +196,7 @@ export default {
>
<walkthrough-popover v-if="isNewCiConfigFile" v-on="$listeners" />
<ci-editor-header
- :show-drawer="showDrawer"
+ :show-help-drawer="showHelpDrawer"
:show-job-assistant-drawer="showJobAssistantDrawer"
:show-ai-assistant-drawer="showAiAssistantDrawer"
v-on="$listeners"
diff --git a/app/assets/javascripts/ci/pipeline_editor/constants.js b/app/assets/javascripts/ci/pipeline_editor/constants.js
index 756041315e4..e85138e361f 100644
--- a/app/assets/javascripts/ci/pipeline_editor/constants.js
+++ b/app/assets/javascripts/ci/pipeline_editor/constants.js
@@ -1,5 +1,10 @@
import { s__ } from '~/locale';
+export const EDITOR_APP_DRAWER_HELP = 'HELP';
+export const EDITOR_APP_DRAWER_JOB_ASSISTANT = 'JOB_ASSISTANT';
+export const EDITOR_APP_DRAWER_AI_ASSISTANT = 'AI_ASSISTANT';
+export const EDITOR_APP_DRAWER_NONE = '';
+
// Values for CI_CONFIG_STATUS_* comes from lint graphQL
export const CI_CONFIG_STATUS_INVALID = 'INVALID';
export const CI_CONFIG_STATUS_VALID = 'VALID';
diff --git a/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue
index 2e7f3f275a2..0495546529a 100644
--- a/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue
@@ -10,12 +10,22 @@ import PipelineEditorFileNav from './components/file_nav/pipeline_editor_file_na
import PipelineEditorFileTree from './components/file_tree/container.vue';
import PipelineEditorHeader from './components/header/pipeline_editor_header.vue';
import PipelineEditorTabs from './components/pipeline_editor_tabs.vue';
-import { CREATE_TAB, FILE_TREE_DISPLAY_KEY } from './constants';
+import {
+ CREATE_TAB,
+ FILE_TREE_DISPLAY_KEY,
+ EDITOR_APP_DRAWER_HELP,
+ EDITOR_APP_DRAWER_JOB_ASSISTANT,
+ EDITOR_APP_DRAWER_AI_ASSISTANT,
+ EDITOR_APP_DRAWER_NONE,
+} from './constants';
const AiAssistantDrawer = () =>
import('ee_component/ci/pipeline_editor/components/ai_assistant_drawer.vue');
export default {
+ EDITOR_APP_DRAWER_HELP,
+ EDITOR_APP_DRAWER_JOB_ASSISTANT,
+ EDITOR_APP_DRAWER_AI_ASSISTANT,
commitSectionRef: 'commitSectionRef',
modal: {
switchBranch: {
@@ -68,15 +78,16 @@ export default {
},
data() {
return {
+ currentDrawer: EDITOR_APP_DRAWER_NONE,
currentTab: CREATE_TAB,
scrollToCommitForm: false,
shouldLoadNewBranch: false,
- showDrawer: false,
- showJobAssistantDrawer: false,
- showAiAssistantDrawer: false,
- drawerIndex: DRAWER_Z_INDEX,
- jobAssistantIndex: DRAWER_Z_INDEX,
- aiAssistantIndex: DRAWER_Z_INDEX,
+ currentDrawerIndex: DRAWER_Z_INDEX,
+ drawerIndex: {
+ [EDITOR_APP_DRAWER_HELP]: DRAWER_Z_INDEX,
+ [EDITOR_APP_DRAWER_JOB_ASSISTANT]: DRAWER_Z_INDEX,
+ [EDITOR_APP_DRAWER_AI_ASSISTANT]: DRAWER_Z_INDEX,
+ },
showFileTree: false,
showSwitchBranchModal: false,
};
@@ -88,6 +99,15 @@ export default {
includesFiles() {
return this.ciConfigData?.includes || [];
},
+ showHelpDrawer() {
+ return this.currentDrawer === EDITOR_APP_DRAWER_HELP;
+ },
+ showJobAssistantDrawer() {
+ return this.currentDrawer === EDITOR_APP_DRAWER_JOB_ASSISTANT;
+ },
+ showAiAssistantDrawer() {
+ return this.currentDrawer === EDITOR_APP_DRAWER_AI_ASSISTANT;
+ },
},
mounted() {
this.showFileTree = JSON.parse(localStorage.getItem(FILE_TREE_DISPLAY_KEY)) || false;
@@ -96,29 +116,15 @@ export default {
closeBranchModal() {
this.showSwitchBranchModal = false;
},
- closeDrawer() {
- this.showDrawer = false;
- },
- closeJobAssistantDrawer() {
- this.showJobAssistantDrawer = false;
- },
- closeAiAssistantDrawer() {
- this.showAiAssistantDrawer = false;
- },
- openAiAssistantDrawer() {
- this.showAiAssistantDrawer = true;
- this.aiAssistantIndex = this.drawerIndex + 1;
- },
handleConfirmSwitchBranch() {
this.showSwitchBranchModal = true;
},
- openDrawer() {
- this.showDrawer = true;
- this.drawerIndex = this.jobAssistantIndex + 1;
- },
- openJobAssistantDrawer() {
- this.showJobAssistantDrawer = true;
- this.jobAssistantIndex = this.drawerIndex + 1;
+ switchDrawer(drawerName) {
+ this.currentDrawer = drawerName;
+ if (this.drawerIndex[drawerName]) {
+ this.currentDrawerIndex += 1;
+ this.drawerIndex[drawerName] = this.currentDrawerIndex;
+ }
},
toggleFileTree() {
this.showFileTree = !this.showFileTree;
@@ -181,16 +187,11 @@ export default {
:commit-sha="commitSha"
:current-tab="currentTab"
:is-new-ci-config-file="isNewCiConfigFile"
- :show-drawer="showDrawer"
+ :show-help-drawer="showHelpDrawer"
:show-job-assistant-drawer="showJobAssistantDrawer"
:show-ai-assistant-drawer="showAiAssistantDrawer"
v-on="$listeners"
- @open-drawer="openDrawer"
- @close-drawer="closeDrawer"
- @open-job-assistant-drawer="openJobAssistantDrawer"
- @close-job-assistant-drawer="closeJobAssistantDrawer"
- @open-ai-assistant-drawer="openAiAssistantDrawer"
- @close-ai-assistant-drawer="closeAiAssistantDrawer"
+ @switch-drawer="switchDrawer"
@set-current-tab="setCurrentTab"
@walkthrough-popover-cta-clicked="setScrollToCommitForm"
/>
@@ -208,24 +209,24 @@ export default {
v-on="$listeners"
/>
<pipeline-editor-drawer
- :is-visible="showDrawer"
- :z-index="drawerIndex"
+ :is-visible="showHelpDrawer"
+ :z-index="drawerIndex[$options.EDITOR_APP_DRAWER_HELP]"
v-on="$listeners"
- @close-drawer="closeDrawer"
+ @switch-drawer="switchDrawer"
/>
<job-assistant-drawer
:ci-config-data="ciConfigData"
:ci-file-content="ciFileContent"
:is-visible="showJobAssistantDrawer"
- :z-index="jobAssistantIndex"
+ :z-index="drawerIndex[$options.EDITOR_APP_DRAWER_JOB_ASSISTANT]"
v-on="$listeners"
- @close-job-assistant-drawer="closeJobAssistantDrawer"
+ @switch-drawer="switchDrawer"
/>
<ai-assistant-drawer
v-if="glFeatures.aiCiConfigGenerator"
:is-visible="showAiAssistantDrawer"
- :z-index="aiAssistantIndex"
- @close-ai-assistant-drawer="closeAiAssistantDrawer"
+ :z-index="drawerIndex[$options.EDITOR_APP_DRAWER_AI_ASSISTANT]"
+ @switch-drawer="switchDrawer"
/>
</div>
</template>
diff --git a/app/assets/javascripts/graphql_shared/issuable_client.js b/app/assets/javascripts/graphql_shared/issuable_client.js
index 74eb124535e..ae7676a3e9e 100644
--- a/app/assets/javascripts/graphql_shared/issuable_client.js
+++ b/app/assets/javascripts/graphql_shared/issuable_client.js
@@ -6,8 +6,6 @@ import getIssueStateQuery from '~/issues/show/queries/get_issue_state.query.grap
import createDefaultClient from '~/lib/graphql';
import typeDefs from '~/work_items/graphql/typedefs.graphql';
import { WIDGET_TYPE_NOTES } from '~/work_items/constants';
-import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
-import { findHierarchyWidgetChildren } from '~/work_items/utils';
import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql';
export const config = {
@@ -183,30 +181,6 @@ export const config = {
export const resolvers = {
Mutation: {
- addHierarchyChild: (_, { fullPath, iid, workItem }, { cache }) => {
- const queryArgs = { query: workItemByIidQuery, variables: { fullPath, iid } };
- const sourceData = cache.readQuery(queryArgs);
-
- const data = produce(sourceData, (draftState) => {
- findHierarchyWidgetChildren(draftState.workspace.workItems.nodes[0]).push(workItem);
- });
-
- cache.writeQuery({ ...queryArgs, data });
- },
- removeHierarchyChild: (_, { fullPath, iid, workItem }, { cache }) => {
- const queryArgs = { query: workItemByIidQuery, variables: { fullPath, iid } };
- const sourceData = cache.readQuery(queryArgs);
-
- const data = produce(sourceData, (draftState) => {
- const hierarchyChildren = findHierarchyWidgetChildren(
- draftState.workspace.workItems.nodes[0],
- );
- const index = hierarchyChildren.findIndex((child) => child.id === workItem.id);
- hierarchyChildren.splice(index, 1);
- });
-
- cache.writeQuery({ ...queryArgs, data });
- },
updateIssueState: (_, { issueType = undefined, isDirty = false }, { cache }) => {
const sourceData = cache.readQuery({ query: getIssueStateQuery });
const data = produce(sourceData, (draftData) => {
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index dc7e1ad2c03..3bf4dfc7a99 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -11,8 +11,7 @@ import { TYPE_ISSUE } from '~/issues/constants';
import { __, s__, sprintf } from '~/locale';
import { getSortableDefaultOptions, isDragging } from '~/sortable/utils';
import TaskList from '~/task_list';
-import addHierarchyChildMutation from '~/work_items/graphql/add_hierarchy_child.mutation.graphql';
-import removeHierarchyChildMutation from '~/work_items/graphql/remove_hierarchy_child.mutation.graphql';
+import { addHierarchyChild, removeHierarchyChild } from '~/work_items/graphql/cache_utils';
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql';
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
@@ -360,6 +359,8 @@ export default {
workItemTypeId: this.taskWorkItemTypeId,
},
},
+ update: (cache, { data: { workItemCreate } }) =>
+ addHierarchyChild(cache, this.fullPath, String(this.issueIid), workItemCreate.workItem),
});
const { workItem, errors } = data.workItemCreate;
@@ -368,11 +369,6 @@ export default {
throw new Error(errors);
}
- await this.$apollo.mutate({
- mutation: addHierarchyChildMutation,
- variables: { fullPath: this.fullPath, iid: String(this.issueIid), workItem },
- });
-
this.$toast.show(s__('WorkItem|Converted to task'), {
action: {
text: s__('WorkItem|Undo'),
@@ -393,19 +389,14 @@ export default {
const { data } = await this.$apollo.mutate({
mutation: deleteWorkItemMutation,
variables: { input: { id } },
+ update: (cache) =>
+ removeHierarchyChild(cache, this.fullPath, String(this.issueIid), { id }),
});
- const { errors } = data.workItemDelete;
-
- if (errors?.length) {
- throw new Error(errors);
+ if (data.workItemDelete.errors?.length) {
+ throw new Error(data.workItemDelete.errors);
}
- await this.$apollo.mutate({
- mutation: removeHierarchyChildMutation,
- variables: { fullPath: this.fullPath, iid: String(this.issueIid), workItem: { id } },
- });
-
this.$toast.show(s__('WorkItem|Task reverted'));
} catch (error) {
this.showAlert(I18N_WORK_ITEM_ERROR_DELETING, error);
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue
index 09a75a7c8e5..fce0b5f525e 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue
@@ -1,44 +1,143 @@
<script>
-import { GlIcon, GlLink, GlPopover, GlSprintf } from '@gitlab/ui';
+import {
+ GlButton,
+ GlCollapse,
+ GlIcon,
+ GlLink,
+ GlLoadingIcon,
+ GlPopover,
+ GlSprintf,
+} from '@gitlab/ui';
+import { createAlert } from '~/alert';
import { __, s__ } from '~/locale';
+import getPipelineFailedJobs from '../../../graphql/queries/get_pipeline_failed_jobs.query.graphql';
+import WidgetFailedJobRow from './widget_failed_job_row.vue';
+import { sortJobsByStatus } from './utils';
+
+const JOB_ID_HEADER = __('Job ID');
+const JOB_NAME_HEADER = __('Job name');
+const STAGE_HEADER = __('Stage');
export default {
components: {
+ GlButton,
+ GlCollapse,
GlIcon,
GlLink,
+ GlLoadingIcon,
GlPopover,
GlSprintf,
+ WidgetFailedJobRow,
},
+ inject: ['fullPath'],
props: {
+ pipelineIid: {
+ required: true,
+ type: Number,
+ },
pipelinePath: {
required: true,
type: String,
},
},
-
+ data() {
+ return {
+ failedJobs: [],
+ isExpanded: false,
+ };
+ },
+ apollo: {
+ failedJobs: {
+ query: getPipelineFailedJobs,
+ skip() {
+ return !this.isExpanded;
+ },
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ pipelineIid: this.pipelineIid,
+ };
+ },
+ update(data) {
+ const jobs = data?.project?.pipeline?.jobs?.nodes || [];
+ return sortJobsByStatus(jobs);
+ },
+ error(e) {
+ createAlert({ message: e?.message || this.$options.i18n.fetchError, variant: 'danger' });
+ },
+ },
+ },
+ computed: {
+ bodyClasses() {
+ return this.isExpanded ? '' : 'gl-display-none';
+ },
+ failedJobsCount() {
+ return this.failedJobs.length;
+ },
+ iconName() {
+ return this.isExpanded ? 'chevron-down' : 'chevron-right';
+ },
+ isLoading() {
+ return this.$apollo.queries.failedJobs.loading;
+ },
+ },
+ methods: {
+ toggleWidget() {
+ this.isExpanded = !this.isExpanded;
+ },
+ },
+ columns: [
+ { text: JOB_NAME_HEADER, class: 'col-6' },
+ { text: STAGE_HEADER, class: 'col-2' },
+ { text: JOB_ID_HEADER, class: 'col-2' },
+ ],
i18n: {
additionalInfoPopover: s__(
'Pipelines|You will see a maximum of 100 jobs in this list. To view all failed jobs, %{linkStart}go to the details page%{linkEnd} of this pipeline.',
),
additionalInfoTitle: __('Limitation on this view'),
+ fetchError: __('There was a problem fetching failed jobs'),
showFailedJobs: __('Show failed jobs'),
},
};
</script>
<template>
<div class="gl-border-none!">
- <gl-icon name="chevron-right" />
- {{ $options.i18n.showFailedJobs }}
- <gl-icon id="target" name="information-o" />
- <gl-popover target="target" placement="top">
- <template #title> {{ $options.i18n.additionalInfoTitle }} </template>
- <slot>
- <gl-sprintf :message="$options.i18n.additionalInfoPopover">
- <template #link="{ content }">
- <gl-link class="gl-font-sm" :href="pipelinePath"> {{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </slot>
- </gl-popover>
+ <gl-button variant="link" @click="toggleWidget">
+ <gl-icon :name="iconName" />
+ {{ $options.i18n.showFailedJobs }}
+ <gl-icon id="target" name="information-o" />
+ <gl-popover target="target" placement="top">
+ <template #title> {{ $options.i18n.additionalInfoTitle }} </template>
+ <slot>
+ <gl-sprintf :message="$options.i18n.additionalInfoPopover">
+ <template #link="{ content }">
+ <gl-link class="gl-font-sm" :href="pipelinePath"> {{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </slot>
+ </gl-popover>
+ </gl-button>
+ <gl-loading-icon v-if="isLoading" />
+ <gl-collapse
+ v-else
+ v-model="isExpanded"
+ class="gl-bg-gray-10 gl-border-1 gl-border-t gl-border-color-gray-100 gl-mt-4 gl-pt-3"
+ >
+ <div class="container-fluid gl-grid-tpl-rows-auto">
+ <div class="row gl-mb-6 gl-text-gray-900">
+ <div
+ v-for="col in $options.columns"
+ :key="col.text"
+ class="gl-font-weight-bold gl-text-left"
+ :class="col.class"
+ data-testid="header"
+ >
+ {{ col.text }}
+ </div>
+ </div>
+ </div>
+ <widget-failed-job-row v-for="job in failedJobs" :key="job.id" :job="job" />
+ </gl-collapse>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/utils.js b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/utils.js
new file mode 100644
index 00000000000..3f395fff7e0
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/utils.js
@@ -0,0 +1,15 @@
+export const isFailedJob = (job = {}) => {
+ return job?.detailedStatus?.group === 'failed' || false;
+};
+
+export const sortJobsByStatus = (jobs = []) => {
+ const newJobs = [...jobs];
+
+ return newJobs.sort((a) => {
+ if (isFailedJob(a)) {
+ return -1;
+ }
+
+ return 1;
+ });
+};
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row.vue b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row.vue
new file mode 100644
index 00000000000..d48e6dc289b
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row.vue
@@ -0,0 +1,15 @@
+<script>
+export default {
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <div class="container-fluid gl-grid-tpl-rows-auto">
+ {{ job.name }}
+ </div>
+</template>
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 0c25fb45094..d884935d95b 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -219,7 +219,11 @@ export default {
</template>
<template #row-details="{ item }">
- <pipeline-failed-jobs-widget v-if="showFailedJobsWidget(item)" :pipeline-path="item.path" />
+ <pipeline-failed-jobs-widget
+ v-if="showFailedJobsWidget(item)"
+ :pipeline-iid="item.iid"
+ :pipeline-path="item.path"
+ />
</template>
</gl-table-lite>
diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_failed_jobs.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_failed_jobs.query.graphql
new file mode 100644
index 00000000000..2c842f1ac77
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_failed_jobs.query.graphql
@@ -0,0 +1,34 @@
+query getPipelineFailedJobs($fullPath: ID!, $pipelineIid: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ pipeline(iid: $pipelineIid) {
+ id
+ jobs(statuses: [FAILED], retried: false, jobKind: BUILD) {
+ nodes {
+ id
+ allowFailure
+ detailedStatus {
+ id
+ group
+ icon
+ action {
+ id
+ path
+ icon
+ }
+ }
+ name
+ retried
+ stage {
+ id
+ name
+ }
+ trace {
+ htmlSummary
+ }
+ webPath
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/search/sidebar/components/label_filter/data.js b/app/assets/javascripts/search/sidebar/components/label_filter/data.js
new file mode 100644
index 00000000000..654357da902
--- /dev/null
+++ b/app/assets/javascripts/search/sidebar/components/label_filter/data.js
@@ -0,0 +1,23 @@
+import { __ } from '~/locale';
+
+export const FIRST_DROPDOWN_INDEX = 0;
+
+export const SEARCH_BOX_INDEX = 0;
+
+export const SEARCH_INPUT_DESCRIPTION = 'label-search-input-description';
+
+export const SEARCH_RESULTS_DESCRIPTION = 'label-search-results-description';
+
+const header = __('Labels');
+
+const scopes = {
+ ISSUES: 'issues',
+};
+
+const filterParam = 'labels';
+
+export const labelFilterData = {
+ header,
+ scopes,
+ filterParam,
+};
diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js
index 3fdc4165baf..077c46bbe22 100644
--- a/app/assets/javascripts/search/store/actions.js
+++ b/app/assets/javascripts/search/store/actions.js
@@ -5,6 +5,7 @@ import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { logError } from '~/lib/logger';
import { __ } from '~/locale';
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
+import { labelFilterData } from '~/search/sidebar/components/label_filter/data';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY, SIDEBAR_PARAMS } from './constants';
import * as types from './mutation_types';
import {
@@ -108,10 +109,24 @@ export const applyQuery = ({ state }) => {
export const resetQuery = ({ state }) => {
visitUrl(
- setUrlParams({ ...state.query, page: null, state: null, confidential: null }, undefined, true),
+ setUrlParams(
+ { ...state.query, page: null, state: null, confidential: null, labels: null },
+ undefined,
+ true,
+ ),
);
};
+export const closeLabel = ({ state, commit }, { key }) => {
+ const labels = state?.query?.labels.filter((labelKey) => labelKey !== key);
+
+ setQuery({ state, commit }, { key: labelFilterData.filterParam, value: labels });
+};
+
+export const setLabelFilterSearch = ({ commit }, { value }) => {
+ commit(types.SET_LABEL_SEARCH_STRING, value);
+};
+
export const resetLanguageQueryWithRedirect = ({ state }) => {
visitUrl(setUrlParams({ ...state.query, language: null }, undefined, true));
};
diff --git a/app/assets/javascripts/search/store/constants.js b/app/assets/javascripts/search/store/constants.js
index c8ee0a3f9d9..91c16616f02 100644
--- a/app/assets/javascripts/search/store/constants.js
+++ b/app/assets/javascripts/search/store/constants.js
@@ -1,6 +1,7 @@
import { stateFilterData } from '~/search/sidebar/constants/state_filter_data';
import { confidentialFilterData } from '~/search/sidebar/constants/confidential_filter_data';
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
+import { labelFilterData } from '~/search/sidebar/components/label_filter/data';
export const MAX_FREQUENT_ITEMS = 5;
@@ -14,6 +15,7 @@ export const SIDEBAR_PARAMS = [
stateFilterData.filterParam,
confidentialFilterData.filterParam,
languageFilterData.filterParam,
+ labelFilterData.filterParam,
];
export const NUMBER_FORMATING_OPTIONS = { notation: 'compact', compactDisplay: 'short' };
diff --git a/app/assets/javascripts/search/store/getters.js b/app/assets/javascripts/search/store/getters.js
index 135c9a3d67c..d31d2b5ae11 100644
--- a/app/assets/javascripts/search/store/getters.js
+++ b/app/assets/javascripts/search/store/getters.js
@@ -1,5 +1,6 @@
import { findKey, has } from 'lodash';
import { languageFilterData } from '~/search/sidebar/components/language_filter/data';
+import { labelFilterData } from '~/search/sidebar/components/label_filter/data';
import { formatSearchResultCount, addCountOverLimit } from '~/search/store/utils';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY, ICON_MAP } from './constants';
@@ -20,6 +21,40 @@ export const languageAggregationBuckets = (state) => {
);
};
+export const labelAggregationBuckets = (state) => {
+ return (
+ state?.aggregations?.data?.find(
+ (aggregation) => aggregation.name === labelFilterData.filterParam,
+ )?.buckets || []
+ );
+};
+
+export const filteredLabels = (state) => {
+ if (state.searchLabelString === '') {
+ return labelAggregationBuckets(state);
+ }
+ return labelAggregationBuckets(state).filter((label) => {
+ return label.title.toLowerCase().includes(state.searchLabelString.toLowerCase());
+ });
+};
+
+export const filteredAppliedSelectedLabels = (state) =>
+ filteredLabels(state)?.filter((label) => state?.urlQuery?.labels?.includes(label.key));
+
+export const appliedSelectedLabels = (state) =>
+ labelAggregationBuckets(state)?.filter((label) => state?.urlQuery?.labels?.includes(label.key));
+
+export const filteredUnappliedSelectedLabels = (state) =>
+ filteredLabels(state)?.filter((label) => state?.query?.labels?.includes(label.key));
+
+export const filteredUnselectedLabels = (state) => {
+ if (!state?.urlQuery?.labels) {
+ return filteredLabels(state);
+ }
+
+ return filteredLabels(state)?.filter((label) => !state?.urlQuery?.labels?.includes(label.key));
+};
+
export const currentScope = (state) => findKey(state.navigation, { active: true });
export const queryLanguageFilters = (state) => state.query[languageFilterData.filterParam] || [];
diff --git a/app/assets/javascripts/search/store/mutation_types.js b/app/assets/javascripts/search/store/mutation_types.js
index 4ffbadcd083..021dd01ca93 100644
--- a/app/assets/javascripts/search/store/mutation_types.js
+++ b/app/assets/javascripts/search/store/mutation_types.js
@@ -15,3 +15,5 @@ export const RECEIVE_NAVIGATION_COUNT = 'RECEIVE_NAVIGATION_COUNT';
export const REQUEST_AGGREGATIONS = 'REQUEST_AGGREGATIONS';
export const RECEIVE_AGGREGATIONS_SUCCESS = 'RECEIVE_AGGREGATIONS_SUCCESS';
export const RECEIVE_AGGREGATIONS_ERROR = 'RECEIVE_AGGREGATIONS_ERROR';
+
+export const SET_LABEL_SEARCH_STRING = 'SET_LABEL_SEARCH_STRING';
diff --git a/app/assets/javascripts/search/store/mutations.js b/app/assets/javascripts/search/store/mutations.js
index b2f9f5ab225..65bb21f1b8a 100644
--- a/app/assets/javascripts/search/store/mutations.js
+++ b/app/assets/javascripts/search/store/mutations.js
@@ -45,4 +45,7 @@ export default {
[types.RECEIVE_AGGREGATIONS_ERROR](state) {
state.aggregations = { fetching: false, error: true, data: [] };
},
+ [types.SET_LABEL_SEARCH_STRING](state, value) {
+ state.searchLabelString = value;
+ },
};
diff --git a/app/assets/javascripts/search/store/state.js b/app/assets/javascripts/search/store/state.js
index c897d4108a8..5407b08fa83 100644
--- a/app/assets/javascripts/search/store/state.js
+++ b/app/assets/javascripts/search/store/state.js
@@ -20,6 +20,7 @@ const createState = ({ query, navigation, useSidebarNavigation }) => ({
fetching: false,
data: [],
},
+ searchLabelString: '',
});
export default createState;
diff --git a/app/assets/javascripts/snippets/components/show.vue b/app/assets/javascripts/snippets/components/show.vue
index 083474da23e..074c5fda29b 100644
--- a/app/assets/javascripts/snippets/components/show.vue
+++ b/app/assets/javascripts/snippets/components/show.vue
@@ -7,7 +7,7 @@ import {
} from '~/performance/constants';
import { performanceMarkAndMeasure } from '~/performance/utils';
import { VISIBILITY_LEVEL_PUBLIC_STRING } from '~/visibility_level/constants';
-import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue';
+import CloneDropdownButton from '~/vue_shared/components/clone_dropdown/clone_dropdown.vue';
import { getSnippetMixin } from '../mixins/snippets';
import { markBlobPerformance } from '../utils/blob';
diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
deleted file mode 100644
index 69fce23ede4..00000000000
--- a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-<script>
-import {
- GlButton,
- GlDisclosureDropdown,
- GlDisclosureDropdownItem,
- GlFormGroup,
- GlFormInputGroup,
- GlTooltipDirective,
-} from '@gitlab/ui';
-import { getHTTPProtocol } from '~/lib/utils/url_utility';
-import { __, sprintf } from '~/locale';
-
-export default {
- components: {
- GlDisclosureDropdown,
- GlDisclosureDropdownItem,
- GlFormGroup,
- GlFormInputGroup,
- GlButton,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- sshLink: {
- type: String,
- required: false,
- default: '',
- },
- httpLink: {
- type: String,
- required: false,
- default: '',
- },
- },
- computed: {
- httpLabel() {
- const protocol = this.httpLink ? getHTTPProtocol(this.httpLink)?.toUpperCase() : '';
- return sprintf(__('Clone with %{protocol}'), { protocol });
- },
- },
- labels: {
- defaultLabel: __('Clone'),
- ssh: __('Clone with SSH'),
- },
- copyURLTooltip: __('Copy URL'),
-};
-</script>
-<template>
- <gl-disclosure-dropdown
- :toggle-text="$options.labels.defaultLabel"
- category="primary"
- variant="confirm"
- placement="right"
- >
- <gl-disclosure-dropdown-item v-if="sshLink">
- <gl-form-group :label="$options.labels.ssh" class="gl-px-3 gl-my-3">
- <gl-form-input-group :value="sshLink" readonly select-on-click>
- <template #append>
- <gl-button
- v-gl-tooltip.hover
- :title="$options.copyURLTooltip"
- :aria-label="$options.copyURLTooltip"
- :data-clipboard-text="sshLink"
- data-qa-selector="copy_ssh_url_button"
- icon="copy-to-clipboard"
- class="gl-display-inline-flex"
- />
- </template>
- </gl-form-input-group>
- </gl-form-group>
- </gl-disclosure-dropdown-item>
- <gl-disclosure-dropdown-item v-if="httpLink">
- <gl-form-group :label="httpLabel" class="gl-px-3 gl-mb-3">
- <gl-form-input-group :value="httpLink" readonly select-on-click>
- <template #append>
- <gl-button
- v-gl-tooltip.hover
- :title="$options.copyURLTooltip"
- :aria-label="$options.copyURLTooltip"
- :data-clipboard-text="httpLink"
- data-qa-selector="copy_http_url_button"
- icon="copy-to-clipboard"
- class="gl-display-inline-flex"
- />
- </template>
- </gl-form-input-group>
- </gl-form-group>
- </gl-disclosure-dropdown-item>
- </gl-disclosure-dropdown>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.stories.js b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.stories.js
new file mode 100644
index 00000000000..ed0e9150bc4
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.stories.js
@@ -0,0 +1,33 @@
+import CloneDropdown from './clone_dropdown.vue';
+
+export default {
+ component: CloneDropdown,
+ title: 'vue_shared/components/clone_dropdown',
+};
+
+const Template = (args, { argTypes }) => ({
+ components: { CloneDropdown },
+ props: Object.keys(argTypes),
+ template: '<clone-dropdown v-bind="$props" />',
+});
+
+const sshLink = 'ssh://some-ssh-link';
+const httpLink = 'https://some-http-link';
+
+export const Default = Template.bind({});
+Default.args = {
+ sshLink,
+ httpLink,
+};
+
+export const HttpLink = Template.bind({});
+HttpLink.args = {
+ httpLink,
+ sshLink: '',
+};
+
+export const SSHLink = Template.bind({});
+SSHLink.args = {
+ sshLink,
+ httpLink: '',
+};
diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue
new file mode 100644
index 00000000000..fa7c5bc1978
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue
@@ -0,0 +1,59 @@
+<script>
+import { GlDisclosureDropdown, GlTooltipDirective } from '@gitlab/ui';
+import { getHTTPProtocol } from '~/lib/utils/url_utility';
+import { __, sprintf } from '~/locale';
+import CloneDropdownItem from './clone_dropdown_item.vue';
+
+export default {
+ components: {
+ GlDisclosureDropdown,
+ CloneDropdownItem,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ sshLink: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ httpLink: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ httpLabel() {
+ const protocol = this.httpLink ? getHTTPProtocol(this.httpLink)?.toUpperCase() : '';
+ return sprintf(__('Clone with %{protocol}'), { protocol });
+ },
+ },
+ labels: {
+ defaultLabel: __('Clone'),
+ ssh: __('Clone with SSH'),
+ },
+};
+</script>
+<template>
+ <gl-disclosure-dropdown
+ :toggle-text="$options.labels.defaultLabel"
+ category="primary"
+ variant="confirm"
+ placement="right"
+ >
+ <clone-dropdown-item
+ v-if="sshLink"
+ :label="$options.labels.ssh"
+ :link="sshLink"
+ qa-selector="copy_ssh_url_button"
+ />
+ <clone-dropdown-item
+ v-if="httpLink"
+ :label="httpLabel"
+ :link="httpLink"
+ qa-selector="copy_http_url_button"
+ />
+ </gl-disclosure-dropdown>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown_item.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown_item.vue
new file mode 100644
index 00000000000..0e322ebc686
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown_item.vue
@@ -0,0 +1,56 @@
+<script>
+import {
+ GlButton,
+ GlDisclosureDropdownItem,
+ GlFormGroup,
+ GlFormInputGroup,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ components: {
+ GlDisclosureDropdownItem,
+ GlFormGroup,
+ GlFormInputGroup,
+ GlButton,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ label: {
+ type: String,
+ required: true,
+ },
+ link: {
+ type: String,
+ required: true,
+ },
+ qaSelector: {
+ type: String,
+ required: true,
+ },
+ },
+ copyURLTooltip: __('Copy URL'),
+};
+</script>
+<template>
+ <gl-disclosure-dropdown-item>
+ <gl-form-group :label="label" class="gl-px-3 gl-mb-3">
+ <gl-form-input-group :value="link" readonly select-on-click>
+ <template #append>
+ <gl-button
+ v-gl-tooltip.hover
+ :title="$options.copyURLTooltip"
+ :aria-label="$options.copyURLTooltip"
+ :data-clipboard-text="link"
+ :data-qa-selector="qaSelector"
+ icon="copy-to-clipboard"
+ class="gl-display-inline-flex"
+ />
+ </template>
+ </gl-form-input-group>
+ </gl-form-group>
+ </gl-disclosure-dropdown-item>
+</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 65e8999ae03..ad759bc69e9 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -1,6 +1,5 @@
<script>
import { isEmpty } from 'lodash';
-import { produce } from 'immer';
import {
GlAlert,
GlSkeletonLoader,
@@ -398,33 +397,6 @@ export default {
this.error = this.$options.i18n.fetchError;
document.title = s__('404|Not found');
},
- addChild(child) {
- const { defaultClient: client } = this.$apollo.provider.clients;
- this.toggleChildFromCache(child, child.id, client);
- },
- toggleChildFromCache(workItem, childId, store) {
- const query = {
- query: workItemByIidQuery,
- variables: { fullPath: this.fullPath, iid: this.workItemIid },
- };
-
- const sourceData = store.readQuery(query);
-
- const newData = produce(sourceData, (draftState) => {
- const { widgets } = draftState.workspace.workItems.nodes[0];
- const widgetHierarchy = widgets.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY);
-
- const index = widgetHierarchy.children.nodes.findIndex((child) => child.id === childId);
-
- if (index >= 0) {
- widgetHierarchy.children.nodes.splice(index, 1);
- } else {
- widgetHierarchy.children.nodes.push(workItem);
- }
- });
-
- store.writeQuery({ ...query, data: newData });
- },
updateHasNotes() {
this.$emit('has-notes');
},
@@ -678,7 +650,6 @@ export default {
:children="children"
:can-update="canUpdate"
:confidential="workItem.confidential"
- @addWorkItemChild="addChild"
@show-modal="openInModal"
/>
<work-item-notes
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue
index a18dfd4d9e3..bf427feaa35 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue
@@ -10,8 +10,7 @@ import { defaultSortableOptions } from '~/sortable/constants';
import { WORK_ITEM_TYPE_VALUE_OBJECTIVE } from '../../constants';
import { findHierarchyWidgets } from '../../utils';
-import addHierarchyChildMutation from '../../graphql/add_hierarchy_child.mutation.graphql';
-import removeHierarchyChildMutation from '../../graphql/remove_hierarchy_child.mutation.graphql';
+import { addHierarchyChild, removeHierarchyChild } from '../../graphql/cache_utils';
import reorderWorkItem from '../../graphql/reorder_work_item.mutation.graphql';
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
@@ -84,17 +83,13 @@ export default {
const { data } = await this.$apollo.mutate({
mutation: updateWorkItemMutation,
variables: { input: { id: child.id, hierarchyWidget: { parentId: null } } },
+ update: (cache) => removeHierarchyChild(cache, this.fullPath, this.workItemIid, child),
});
if (data.workItemUpdate.errors.length) {
throw new Error(data.workItemUpdate.errors);
}
- await this.$apollo.mutate({
- mutation: removeHierarchyChildMutation,
- variables: { fullPath: this.fullPath, iid: this.workItemIid, workItem: child },
- });
-
this.$toast.show(s__('WorkItem|Child removed'), {
action: {
text: s__('WorkItem|Undo'),
@@ -114,17 +109,13 @@ export default {
const { data } = await this.$apollo.mutate({
mutation: updateWorkItemMutation,
variables: { input: { id: child.id, hierarchyWidget: { parentId: this.workItemId } } },
+ update: (cache) => addHierarchyChild(cache, this.fullPath, this.workItemIid, child),
});
if (data.workItemUpdate.errors.length) {
throw new Error(data.workItemUpdate.errors);
}
- await this.$apollo.mutate({
- mutation: addHierarchyChildMutation,
- variables: { fullPath: this.fullPath, iid: this.workItemIid, workItem: child },
- });
-
this.$toast.show(s__('WorkItem|Child removal reverted'));
} catch (error) {
this.$emit('error', s__('WorkItem|Something went wrong while undoing child removal.'));
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
index efcbe8062e0..b9fc92304c0 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
@@ -11,8 +11,7 @@ import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_sel
import { FORM_TYPES, WIDGET_ICONS, WORK_ITEM_STATUS_TEXT } from '../../constants';
import { findHierarchyWidgetChildren } from '../../utils';
-import addHierarchyChildMutation from '../../graphql/add_hierarchy_child.mutation.graphql';
-import removeHierarchyChildMutation from '../../graphql/remove_hierarchy_child.mutation.graphql';
+import { removeHierarchyChild } from '../../graphql/cache_utils';
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
import WidgetWrapper from '../widget_wrapper.vue';
import WorkItemDetailModal from '../work_item_detail_modal.vue';
@@ -165,24 +164,13 @@ export default {
this.updateWorkItemIdUrlQuery();
},
handleWorkItemDeleted(child) {
- this.removeHierarchyChild(child);
+ const { defaultClient: cache } = this.$apollo.provider.clients;
+ removeHierarchyChild(cache, this.fullPath, this.iid, child);
this.$toast.show(s__('WorkItem|Task deleted'));
},
updateWorkItemIdUrlQuery({ iid } = {}) {
updateHistory({ url: setUrlParams({ work_item_iid: iid }), replace: true });
},
- async addHierarchyChild(workItem) {
- return this.$apollo.mutate({
- mutation: addHierarchyChildMutation,
- variables: { fullPath: this.fullPath, iid: this.iid, workItem },
- });
- },
- async removeHierarchyChild(workItem) {
- return this.$apollo.mutate({
- mutation: removeHierarchyChildMutation,
- variables: { fullPath: this.fullPath, iid: this.iid, workItem },
- });
- },
toggleReportAbuseDrawer(isOpen, reply = {}) {
this.isReportDrawerOpen = isOpen;
this.reportedUrl = reply.url;
@@ -261,6 +249,7 @@ export default {
ref="wiLinksForm"
data-testid="add-links-form"
:issuable-gid="issuableGid"
+ :work-item-iid="iid"
:children-ids="childrenIds"
:parent-confidential="confidential"
:parent-iteration="issuableIteration"
@@ -268,7 +257,6 @@ export default {
:form-type="formType"
:parent-work-item-type="workItem.workItemType.name"
@cancel="hideAddForm"
- @addWorkItemChild="addHierarchyChild"
/>
<work-item-children-wrapper
:children="children"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
index 51c83784d06..289a48b5eaf 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
@@ -13,7 +13,8 @@ import { debounce } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { __, s__, sprintf } from '~/locale';
-import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
+import { addHierarchyChild } from '../../graphql/cache_utils';
+import projectWorkItemTypesQuery from '../../graphql/project_work_item_types.query.graphql';
import projectWorkItemsQuery from '../../graphql/project_work_items.query.graphql';
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
import createWorkItemMutation from '../../graphql/create_work_item.mutation.graphql';
@@ -48,6 +49,11 @@ export default {
required: false,
default: null,
},
+ workItemIid: {
+ type: String,
+ required: false,
+ default: null,
+ },
childrenIds: {
type: Array,
required: false,
@@ -292,13 +298,14 @@ export default {
variables: {
input: this.workItemInput,
},
+ update: (cache, { data }) =>
+ addHierarchyChild(cache, this.fullPath, this.workItemIid, data.workItemCreate.workItem),
})
.then(({ data }) => {
if (data.workItemCreate?.errors?.length) {
[this.error] = data.workItemCreate.errors;
} else {
this.unsetError();
- this.$emit('addWorkItemChild', data.workItemCreate.workItem);
}
})
.catch(() => {
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
index 4e80e0071ae..44e8dac79c4 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
@@ -136,12 +136,12 @@ export default {
ref="wiLinksForm"
data-testid="add-tree-form"
:issuable-gid="workItemId"
+ :work-item-iid="workItemIid"
:form-type="formType"
:parent-work-item-type="parentWorkItemType"
:children-type="childType"
:children-ids="childrenIds"
:parent-confidential="confidential"
- @addWorkItemChild="$emit('addWorkItemChild', $event)"
@cancel="hideAddForm"
/>
<work-item-children-wrapper
diff --git a/app/assets/javascripts/work_items/graphql/add_hierarchy_child.mutation.graphql b/app/assets/javascripts/work_items/graphql/add_hierarchy_child.mutation.graphql
deleted file mode 100644
index 9eb839dc866..00000000000
--- a/app/assets/javascripts/work_items/graphql/add_hierarchy_child.mutation.graphql
+++ /dev/null
@@ -1,3 +0,0 @@
-mutation addHierarchyChild($fullPath: ID!, $iid: String!, $workItem: WorkItem!) {
- addHierarchyChild(fullPath: $fullPath, iid: $iid, workItem: $workItem) @client
-}
diff --git a/app/assets/javascripts/work_items/graphql/cache_utils.js b/app/assets/javascripts/work_items/graphql/cache_utils.js
index 455d8b8ae7b..03b45a45c39 100644
--- a/app/assets/javascripts/work_items/graphql/cache_utils.js
+++ b/app/assets/javascripts/work_items/graphql/cache_utils.js
@@ -1,5 +1,7 @@
import { produce } from 'immer';
import { WIDGET_TYPE_NOTES } from '~/work_items/constants';
+import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
+import { findHierarchyWidgetChildren } from '~/work_items/utils';
const isNotesWidget = (widget) => widget.type === WIDGET_TYPE_NOTES;
@@ -17,7 +19,6 @@ const updateNotesWidgetDataInDraftData = (draftData, notesWidget) => {
* @param currentNotes
* @param subscriptionData
*/
-
export const updateCacheAfterCreatingNote = (currentNotes, subscriptionData) => {
if (!subscriptionData.data?.workItemNoteCreated) {
return currentNotes;
@@ -49,7 +50,6 @@ export const updateCacheAfterCreatingNote = (currentNotes, subscriptionData) =>
* @param currentNotes
* @param subscriptionData
*/
-
export const updateCacheAfterDeletingNote = (currentNotes, subscriptionData) => {
if (!subscriptionData.data?.workItemNoteDeleted) {
return currentNotes;
@@ -86,3 +86,37 @@ export const updateCacheAfterDeletingNote = (currentNotes, subscriptionData) =>
updateNotesWidgetDataInDraftData(draftData, notesWidget);
});
};
+
+export const addHierarchyChild = (cache, fullPath, iid, workItem) => {
+ const queryArgs = { query: workItemByIidQuery, variables: { fullPath, iid } };
+ const sourceData = cache.readQuery(queryArgs);
+
+ if (!sourceData) {
+ return;
+ }
+
+ cache.writeQuery({
+ ...queryArgs,
+ data: produce(sourceData, (draftState) => {
+ findHierarchyWidgetChildren(draftState.workspace.workItems.nodes[0]).push(workItem);
+ }),
+ });
+};
+
+export const removeHierarchyChild = (cache, fullPath, iid, workItem) => {
+ const queryArgs = { query: workItemByIidQuery, variables: { fullPath, iid } };
+ const sourceData = cache.readQuery(queryArgs);
+
+ if (!sourceData) {
+ return;
+ }
+
+ cache.writeQuery({
+ ...queryArgs,
+ data: produce(sourceData, (draftState) => {
+ const children = findHierarchyWidgetChildren(draftState.workspace.workItems.nodes[0]);
+ const index = children.findIndex((child) => child.id === workItem.id);
+ children.splice(index, 1);
+ }),
+ });
+};
diff --git a/app/assets/javascripts/work_items/graphql/remove_hierarchy_child.mutation.graphql b/app/assets/javascripts/work_items/graphql/remove_hierarchy_child.mutation.graphql
deleted file mode 100644
index 790f392f6e7..00000000000
--- a/app/assets/javascripts/work_items/graphql/remove_hierarchy_child.mutation.graphql
+++ /dev/null
@@ -1,3 +0,0 @@
-mutation removeHierarchyChild($fullPath: ID!, $iid: String!, $workItem: WorkItem!) {
- removeHierarchyChild(fullPath: $fullPath, iid: $iid, workItem: $workItem) @client
-}
diff --git a/app/assets/stylesheets/fonts.scss b/app/assets/stylesheets/fonts.scss
index a023b41083d..369f7072aa4 100644
--- a/app/assets/stylesheets/fonts.scss
+++ b/app/assets/stylesheets/fonts.scss
@@ -14,8 +14,31 @@ Usage:
}
/* -------------------------------------------------------
+Monospaced font: GitLab Mono.
+
+Usage:
+ html { font-family: 'GitLab Mono', sans-serif; }
+*/
+@font-face {
+ font-family: 'GitLab Mono';
+ font-display: optional;
+ font-style: normal;
+ src: font-url('gitlab-mono/GitLabMono.woff2') format('woff2');
+}
+
+@font-face {
+ font-family: 'GitLab Mono';
+ font-display: optional;
+ font-style: italic;
+ src: font-url('gitlab-mono/GitLabMono-Italic.woff2') format('woff2');
+}
+
+/* -------------------------------------------------------
Monospaced font: JetBrains Mono.
+All of the definitions below can be removed once
+`GitLab Mono` is properly rolled out.
+
Usage:
html { font-family: 'JetBrains Mono', sans-serif; }
*/
@@ -49,11 +72,6 @@ Usage:
src: font-url('jetbrains-mono/JetBrainsMono-BoldItalic.woff2') format('woff2');
}
-:root {
- --default-mono-font: 'JetBrains Mono', 'Menlo';
- --default-regular-font: 'GitLab Sans', -apple-system;
-}
-
// This isn't the best solution, but we needed a quick fix
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107592/
* {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 39672a5ed7f..f77804fb7fc 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -569,9 +569,9 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
/*
* Fonts
*/
-$monospace-font: var(--default-mono-font, 'Menlo'), 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas',
+$monospace-font: 'GitLab Mono', 'JetBrains Mono', 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas',
'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
-$regular-font: var(--default-regular-font, -apple-system), BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans',
+$regular-font: 'GitLab Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans',
Ubuntu, Cantarell, 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
$gl-monospace-font: $monospace-font;
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index 11eb39a11cb..02cc86a77ea 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -17,10 +17,9 @@ header {
}
body {
margin: 0;
- font-family: var(--default-regular-font, -apple-system), BlinkMacSystemFont,
- "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue",
- sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
- "Noto Color Emoji";
+ font-family: "GitLab Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
@@ -48,7 +47,7 @@ a:not([href]):not([class]) {
text-decoration: none;
}
kbd {
- font-family: var(--default-mono-font, "Menlo"), "DejaVu Sans Mono",
+ font-family: "GitLab Mono", "JetBrains Mono", "Menlo", "DejaVu Sans Mono",
"Liberation Mono", "Consolas", "Ubuntu Mono", "Courier New", "andale mono",
"lucida console", monospace;
font-size: 1em;
@@ -413,10 +412,9 @@ a.gl-badge.badge-warning:active {
.gl-form-input,
.gl-form-input.form-control {
background-color: #333238;
- font-family: var(--default-regular-font, -apple-system), BlinkMacSystemFont,
- "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue",
- sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
- "Noto Color Emoji";
+ font-family: "GitLab Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 0.875rem;
line-height: 1rem;
padding-top: 0.5rem;
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index 28a35c82a59..5e3944e62f1 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -17,10 +17,9 @@ header {
}
body {
margin: 0;
- font-family: var(--default-regular-font, -apple-system), BlinkMacSystemFont,
- "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue",
- sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
- "Noto Color Emoji";
+ font-family: "GitLab Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
@@ -48,7 +47,7 @@ a:not([href]):not([class]) {
text-decoration: none;
}
kbd {
- font-family: var(--default-mono-font, "Menlo"), "DejaVu Sans Mono",
+ font-family: "GitLab Mono", "JetBrains Mono", "Menlo", "DejaVu Sans Mono",
"Liberation Mono", "Consolas", "Ubuntu Mono", "Courier New", "andale mono",
"lucida console", monospace;
font-size: 1em;
@@ -413,10 +412,9 @@ a.gl-badge.badge-warning:active {
.gl-form-input,
.gl-form-input.form-control {
background-color: #fff;
- font-family: var(--default-regular-font, -apple-system), BlinkMacSystemFont,
- "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue",
- sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
- "Noto Color Emoji";
+ font-family: "GitLab Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 0.875rem;
line-height: 1rem;
padding-top: 0.5rem;
diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss
index 7c37bcd085b..d081fdfa50b 100644
--- a/app/assets/stylesheets/startup/startup-signin.scss
+++ b/app/assets/stylesheets/startup/startup-signin.scss
@@ -16,10 +16,9 @@ header {
}
body {
margin: 0;
- font-family: var(--default-regular-font, -apple-system), BlinkMacSystemFont,
- "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue",
- sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
- "Noto Color Emoji";
+ font-family: "GitLab Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
@@ -380,10 +379,9 @@ input.btn-block[type="submit"] {
.gl-form-input,
.gl-form-input.form-control {
background-color: #fff;
- font-family: var(--default-regular-font, -apple-system), BlinkMacSystemFont,
- "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue",
- sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
- "Noto Color Emoji";
+ font-family: "GitLab Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 0.875rem;
line-height: 1rem;
padding-top: 0.5rem;
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 3a03831ab88..06381315614 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -91,15 +91,17 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
end
def target_projects
- projects = MergeRequestTargetProjectFinder
- .new(current_user: current_user, source_project: @project, project_feature: :repository)
- .execute(include_routes: false, search: params[:search]).limit(20)
-
- render json: ProjectSerializer.new.represent(projects)
+ render json: ProjectSerializer.new.represent(get_target_projects)
end
private
+ def get_target_projects
+ MergeRequestTargetProjectFinder
+ .new(current_user: current_user, source_project: @project, project_feature: :repository)
+ .execute(include_routes: false, search: params[:search]).limit(20)
+ end
+
def build_merge_request
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 025720e5e10..98e6459b543 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -22,7 +22,7 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :ensure_pipeline, only: [:show, :downloadable_artifacts]
before_action :reject_if_build_artifacts_size_refreshing!, only: [:destroy]
- before_action :push_frontend_feature_flags, only: [:show]
+ before_action :push_frontend_feature_flags, only: [:show, :builds, :dag, :failures, :test_report]
# Will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/225596
before_action :redirect_for_legacy_scope_filter, only: [:index], if: -> { request.format.html? }
diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb
index af1feb7c206..696790b9dcb 100644
--- a/app/helpers/ide_helper.rb
+++ b/app/helpers/ide_helper.rb
@@ -8,8 +8,8 @@ module IdeHelper
'new-web-ide-help-page-path' => help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'),
'sign-in-path' => new_session_path(current_user),
'user-preferences-path' => profile_preferences_path,
- 'editor-font-src-url' => font_url('jetbrains-mono/JetBrainsMono.woff2'),
- 'editor-font-family' => 'JetBrains Mono',
+ 'editor-font-src-url' => font_url('gitlab-mono/GitLabMono.woff2'),
+ 'editor-font-family' => 'GitLab Mono',
'editor-font-format' => 'woff2'
}.merge(use_new_web_ide? ? new_ide_data(project: project) : legacy_ide_data(project: project))
diff --git a/app/views/layouts/_loading_hints.html.haml b/app/views/layouts/_loading_hints.html.haml
index b20b95cade8..1e6f671aacb 100644
--- a/app/views/layouts/_loading_hints.html.haml
+++ b/app/views/layouts/_loading_hints.html.haml
@@ -17,8 +17,6 @@
-# Do not use preload_link_tag for fonts, to work around Firefox double-fetch bug.
-# See https://github.com/web-platform-tests/wpt/pull/36930
%link{ rel: 'preload', href: font_path('gitlab-sans/GitLabSans.woff2'), as: 'font', crossorigin: css_crossorigin }
- %link{ rel: 'preload', href: font_path('jetbrains-mono/JetBrainsMono.woff2'), as: 'font', crossorigin: css_crossorigin }
- %link{ rel: 'preload', href: font_path('jetbrains-mono/JetBrainsMono-Bold.woff2'), as: 'font', crossorigin: css_crossorigin }
- %link{ rel: 'preload', href: font_path('jetbrains-mono/JetBrainsMono-Italic.woff2'), as: 'font', crossorigin: css_crossorigin }
- %link{ rel: 'preload', href: font_path('jetbrains-mono/JetBrainsMono-BoldItalic.woff2'), as: 'font', crossorigin: css_crossorigin }
+ %link{ rel: 'preload', href: font_path('gitlab-mono/GitLabMono.woff2'), as: 'font', crossorigin: css_crossorigin }
+ %link{ rel: 'preload', href: font_path('gitlab-mono/GitLabMono-Italic.woff2'), as: 'font', crossorigin: css_crossorigin }
= preload_link_tag(path_to_stylesheet('fonts'), crossorigin: css_crossorigin)
diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
index 0570d22529b..07bae4d2396 100644
--- a/app/views/projects/merge_requests/creations/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -1,6 +1,16 @@
%h1.page-title.gl-font-size-h-display
= _('New merge request')
+- if @saml_groups.present?
+ = render Pajamas::AlertComponent.new(variant: :warning, dismissible: false) do |c|
+ - c.with_body do
+ = s_('GroupSAML|Some branches are inaccessible because your SAML session has expired. To access the branches, select the group’s path to reauthenticate.')
+ - c.with_actions do
+ .gl-display-flex.gl-flex-wrap
+ - @saml_groups.each do |group|
+ = render Pajamas::ButtonComponent.new(href: sso_group_saml_providers_path(group, { token: group.saml_discovery_token, redirect: project_new_merge_request_branch_from_path(@source_project) }), button_options: { class: "gl-mr-3 gl-mb-3" }) do
+ = group.path
+
= gitlab_ui_form_for [@project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form js-requires-input" } do |f|
- if params[:nav_source].present?
= hidden_field_tag(:nav_source, params[:nav_source])
diff --git a/app/views/users/_profile_basic_info.html.haml b/app/views/users/_profile_basic_info.html.haml
index 51736b4970d..7c50031598c 100644
--- a/app/views/users/_profile_basic_info.html.haml
+++ b/app/views/users/_profile_basic_info.html.haml
@@ -6,4 +6,4 @@
= s_('UserProfile|User ID: %{id}') % { id: @user.id }
= clipboard_button(title: s_('UserProfile|Copy user ID'), text: @user.id)
= render 'middle_dot_divider', stacking: true do
- = s_('Member since %{date}') % { date: l(@user.created_at, format: :long) }
+ = s_('Member since %{date}') % { date: l(@user.created_at.to_date, format: :long) }