Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
01c201bc6a
commit
a08f8baa63
|
|
@ -1 +1 @@
|
|||
fa974a4ab21aa6acc4c3a00456265248a4d70703
|
||||
9cde939eef5182b062f9aa04a3a3f78d8264af4b
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|||
import { s__ } from '~/locale';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import AlertSettingsFormHelpBlock from './alert_settings_form_help_block.vue';
|
||||
import service from '../services';
|
||||
import {
|
||||
integrationTypesNew,
|
||||
JSON_VALIDATE_DELAY,
|
||||
|
|
@ -89,7 +90,7 @@ export default {
|
|||
MappingBuilder,
|
||||
},
|
||||
directives: {
|
||||
'gl-modal': GlModalDirective,
|
||||
GlModal: GlModalDirective,
|
||||
},
|
||||
inject: {
|
||||
generic: {
|
||||
|
|
@ -150,6 +151,13 @@ export default {
|
|||
apiUrl: this.currentIntegration?.apiUrl || '',
|
||||
};
|
||||
},
|
||||
testAlertPayload() {
|
||||
return {
|
||||
data: this.integrationTestPayload.json,
|
||||
endpoint: this.integrationForm.url,
|
||||
token: this.integrationForm.token,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentIntegration(val) {
|
||||
|
|
@ -170,8 +178,14 @@ export default {
|
|||
}
|
||||
},
|
||||
submitWithTestPayload() {
|
||||
// TODO: Test payload before saving via GraphQL
|
||||
this.submit();
|
||||
return service
|
||||
.updateTestAlert(this.testAlertPayload)
|
||||
.then(() => {
|
||||
this.submit();
|
||||
})
|
||||
.catch(() => {
|
||||
this.$emit('test-payload-failure');
|
||||
});
|
||||
},
|
||||
submit() {
|
||||
const { name, apiUrl } = this.integrationForm;
|
||||
|
|
@ -359,7 +373,6 @@ export default {
|
|||
</div>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
id="test-integration"
|
||||
:label="$options.i18n.integrationFormSteps.step4.label"
|
||||
label-for="test-integration"
|
||||
:invalid-feedback="integrationTestPayload.error"
|
||||
|
|
@ -395,6 +408,8 @@ export default {
|
|||
<div class="gl-display-flex gl-justify-content-end">
|
||||
<gl-button type="reset" class="gl-mr-3 js-no-auto-disable">{{ __('Cancel') }}</gl-button>
|
||||
<gl-button
|
||||
data-testid="integration-test-and-submit"
|
||||
:disabled="Boolean(integrationTestPayload.error)"
|
||||
category="secondary"
|
||||
variant="success"
|
||||
class="gl-mr-1 js-no-auto-disable"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
ADD_INTEGRATION_ERROR,
|
||||
RESET_INTEGRATION_TOKEN_ERROR,
|
||||
UPDATE_INTEGRATION_ERROR,
|
||||
INTEGRATION_PAYLOAD_TEST_ERROR,
|
||||
} from '../utils/error_messages';
|
||||
|
||||
export default {
|
||||
|
|
@ -244,6 +245,9 @@ export default {
|
|||
clearCurrentIntegration() {
|
||||
this.currentIntegration = null;
|
||||
},
|
||||
testPayloadFailure() {
|
||||
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -266,6 +270,7 @@ export default {
|
|||
@update-integration="updateIntegration"
|
||||
@reset-token="resetToken"
|
||||
@clear-current-integration="clearCurrentIntegration"
|
||||
@test-payload-failure="testPayloadFailure"
|
||||
/>
|
||||
<settings-form-old v-else />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
export default {
|
||||
// TODO: All this code save updateTestAlert will be deleted as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/255501
|
||||
updateGenericKey({ endpoint, params }) {
|
||||
return axios.put(endpoint, params);
|
||||
},
|
||||
|
|
@ -25,11 +26,11 @@ export default {
|
|||
},
|
||||
});
|
||||
},
|
||||
updateTestAlert({ endpoint, data, authKey }) {
|
||||
updateTestAlert({ endpoint, data, token }) {
|
||||
return axios.post(endpoint, data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authKey}`,
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,3 +15,7 @@ export const UPDATE_INTEGRATION_ERROR = s__(
|
|||
export const RESET_INTEGRATION_TOKEN_ERROR = s__(
|
||||
'AlertsIntegrations|The integration token could not be reset. Please try again.',
|
||||
);
|
||||
|
||||
export const INTEGRATION_PAYLOAD_TEST_ERROR = s__(
|
||||
'AlertsIntegrations|Integration payload is invalid. You can still save your changes.',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
<script>
|
||||
import DraftNote from './draft_note.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DraftNote,
|
||||
},
|
||||
props: {
|
||||
draft: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
diffFile: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
line: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr class="notes_holder js-temp-notes-holder">
|
||||
<td class="notes-content" colspan="4">
|
||||
<div class="content"><draft-note :draft="draft" :diff-file="diffFile" :line="line" /></div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import DraftNote from './draft_note.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DraftNote,
|
||||
},
|
||||
props: {
|
||||
line: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
diffFileContentSha: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('batchComments', ['draftForLine']),
|
||||
className() {
|
||||
return this.leftDraft > 0 || this.rightDraft > 0 ? '' : 'js-temp-notes-holder';
|
||||
},
|
||||
leftDraft() {
|
||||
return this.draftForLine(this.diffFileContentSha, this.line, 'left');
|
||||
},
|
||||
rightDraft() {
|
||||
return this.draftForLine(this.diffFileContentSha, this.line, 'right');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr :class="className" class="notes_holder">
|
||||
<td class="notes_line old"></td>
|
||||
<td class="notes-content parallel old" colspan="2">
|
||||
<div v-if="leftDraft.isDraft" class="content">
|
||||
<draft-note :draft="leftDraft" :line="line.left" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="notes_line new"></td>
|
||||
<td class="notes-content parallel new" colspan="2">
|
||||
<div v-if="rightDraft.isDraft" class="content">
|
||||
<draft-note :draft="rightDraft" :line="line.right" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
@ -4,7 +4,7 @@ import { GlDropdownItem, GlDropdownDivider, GlAvatarLabeled, GlAvatarLink } from
|
|||
import { __, n__ } from '~/locale';
|
||||
import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue';
|
||||
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
|
||||
import AssigneesDropdown from '~/vue_shared/components/sidebar/assignees_dropdown.vue';
|
||||
import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
|
||||
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/getIssueParticipants.query.graphql';
|
||||
|
||||
export default {
|
||||
|
|
@ -17,7 +17,7 @@ export default {
|
|||
components: {
|
||||
BoardEditableItem,
|
||||
IssuableAssignees,
|
||||
AssigneesDropdown,
|
||||
MultiSelectDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownDivider,
|
||||
GlAvatarLabeled,
|
||||
|
|
@ -92,7 +92,7 @@ export default {
|
|||
</template>
|
||||
|
||||
<template #default>
|
||||
<assignees-dropdown
|
||||
<multi-select-dropdown
|
||||
class="w-100"
|
||||
:text="$options.i18n.assignees"
|
||||
:header-text="$options.i18n.assignTo"
|
||||
|
|
@ -138,7 +138,7 @@ export default {
|
|||
</gl-avatar-link>
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
</assignees-dropdown>
|
||||
</multi-select-dropdown>
|
||||
</template>
|
||||
</board-editable-item>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -112,9 +112,9 @@ export default {
|
|||
},
|
||||
canMoveNote(note) {
|
||||
const { userPermissions } = note;
|
||||
const { adminNote } = userPermissions || {};
|
||||
const { repositionNote } = userPermissions || {};
|
||||
|
||||
return Boolean(adminNote);
|
||||
return Boolean(repositionNote);
|
||||
},
|
||||
isPositionInOverlay(position) {
|
||||
const { top, left } = this.getNoteRelativePosition(position);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
fragment DesignNotePermissions on NotePermissions {
|
||||
adminNote
|
||||
repositionNote
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
#import "../fragments/design_note.fragment.graphql"
|
||||
|
||||
mutation repositionImageDiffNote($input: RepositionImageDiffNoteInput!) {
|
||||
repositionImageDiffNote(input: $input) {
|
||||
errors
|
||||
note {
|
||||
...DesignNote
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#import "../fragments/design_note.fragment.graphql"
|
||||
|
||||
mutation updateImageDiffNote($input: UpdateImageDiffNoteInput!) {
|
||||
updateImageDiffNote(input: $input) {
|
||||
errors
|
||||
note {
|
||||
...DesignNote
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,19 +13,19 @@ import DesignReplyForm from '../../components/design_notes/design_reply_form.vue
|
|||
import DesignSidebar from '../../components/design_sidebar.vue';
|
||||
import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
|
||||
import createImageDiffNoteMutation from '../../graphql/mutations/create_image_diff_note.mutation.graphql';
|
||||
import updateImageDiffNoteMutation from '../../graphql/mutations/update_image_diff_note.mutation.graphql';
|
||||
import repositionImageDiffNoteMutation from '../../graphql/mutations/reposition_image_diff_note.mutation.graphql';
|
||||
import updateActiveDiscussionMutation from '../../graphql/mutations/update_active_discussion.mutation.graphql';
|
||||
import {
|
||||
extractDiscussions,
|
||||
extractDesign,
|
||||
updateImageDiffNoteOptimisticResponse,
|
||||
repositionImageDiffNoteOptimisticResponse,
|
||||
toDiffNoteGid,
|
||||
extractDesignNoteId,
|
||||
getPageLayoutElement,
|
||||
} from '../../utils/design_management_utils';
|
||||
import {
|
||||
updateStoreAfterAddImageDiffNote,
|
||||
updateStoreAfterUpdateImageDiffNote,
|
||||
updateStoreAfterRepositionImageDiffNote,
|
||||
} from '../../utils/cache_update';
|
||||
import {
|
||||
ADD_DISCUSSION_COMMENT_ERROR,
|
||||
|
|
@ -182,12 +182,12 @@ export default {
|
|||
updateImageDiffNoteInStore(
|
||||
store,
|
||||
{
|
||||
data: { updateImageDiffNote },
|
||||
data: { repositionImageDiffNote },
|
||||
},
|
||||
) {
|
||||
return updateStoreAfterUpdateImageDiffNote(
|
||||
return updateStoreAfterRepositionImageDiffNote(
|
||||
store,
|
||||
updateImageDiffNote,
|
||||
repositionImageDiffNote,
|
||||
getDesignQuery,
|
||||
this.designVariables,
|
||||
);
|
||||
|
|
@ -199,7 +199,7 @@ export default {
|
|||
);
|
||||
|
||||
const mutationPayload = {
|
||||
optimisticResponse: updateImageDiffNoteOptimisticResponse(note, {
|
||||
optimisticResponse: repositionImageDiffNoteOptimisticResponse(note, {
|
||||
position,
|
||||
}),
|
||||
variables: {
|
||||
|
|
@ -208,7 +208,7 @@ export default {
|
|||
position,
|
||||
},
|
||||
},
|
||||
mutation: updateImageDiffNoteMutation,
|
||||
mutation: repositionImageDiffNoteMutation,
|
||||
update: this.updateImageDiffNoteInStore,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) =
|
|||
});
|
||||
};
|
||||
|
||||
const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables) => {
|
||||
const updateImageDiffNoteInStore = (store, repositionImageDiffNote, query, variables) => {
|
||||
const sourceData = store.readQuery({
|
||||
query,
|
||||
variables,
|
||||
|
|
@ -111,12 +111,12 @@ const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables
|
|||
const design = extractDesign(draftData);
|
||||
const discussion = extractCurrentDiscussion(
|
||||
design.discussions,
|
||||
updateImageDiffNote.note.discussion.id,
|
||||
repositionImageDiffNote.note.discussion.id,
|
||||
);
|
||||
|
||||
discussion.notes = {
|
||||
...discussion.notes,
|
||||
nodes: [updateImageDiffNote.note, ...discussion.notes.nodes.slice(1)],
|
||||
nodes: [repositionImageDiffNote.note, ...discussion.notes.nodes.slice(1)],
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ export const updateStoreAfterAddImageDiffNote = (store, data, query, queryVariab
|
|||
}
|
||||
};
|
||||
|
||||
export const updateStoreAfterUpdateImageDiffNote = (store, data, query, queryVariables) => {
|
||||
export const updateStoreAfterRepositionImageDiffNote = (store, data, query, queryVariables) => {
|
||||
if (hasErrors(data)) {
|
||||
onError(data, UPDATE_IMAGE_DIFF_NOTE_ERROR);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -107,12 +107,12 @@ export const designUploadOptimisticResponse = files => {
|
|||
* @param {Object} note
|
||||
* @param {Object} position
|
||||
*/
|
||||
export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({
|
||||
export const repositionImageDiffNoteOptimisticResponse = (note, { position }) => ({
|
||||
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
__typename: 'Mutation',
|
||||
updateImageDiffNote: {
|
||||
__typename: 'UpdateImageDiffNotePayload',
|
||||
repositionImageDiffNote: {
|
||||
__typename: 'RepositionImageDiffNotePayload',
|
||||
note: {
|
||||
...note,
|
||||
position: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import DiffDiscussions from './diff_discussions.vue';
|
||||
import DiffLineNoteForm from './diff_line_note_form.vue';
|
||||
import DiffDiscussionReply from './diff_discussion_reply.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DiffDiscussions,
|
||||
DiffLineNoteForm,
|
||||
DiffDiscussionReply,
|
||||
},
|
||||
props: {
|
||||
line: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
diffFileHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
hasDraft: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
linePosition: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['showCommentForm']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content">
|
||||
<diff-discussions
|
||||
v-if="line.renderDiscussion"
|
||||
:line="line"
|
||||
:discussions="line.discussions"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
<diff-discussion-reply
|
||||
v-if="!hasDraft"
|
||||
:has-form="line.hasCommentForm"
|
||||
:render-reply-placeholder="Boolean(line.discussions.length)"
|
||||
@showNewDiscussionForm="showCommentForm({ lineCode: line.line_code, fileHash: diffFileHash })"
|
||||
>
|
||||
<template #form>
|
||||
<diff-line-note-form
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line"
|
||||
:note-target-line="line"
|
||||
:help-page-path="helpPagePath"
|
||||
:line-position="linePosition"
|
||||
/>
|
||||
</template>
|
||||
</diff-discussion-reply>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -10,6 +10,7 @@ import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_prev
|
|||
import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
|
||||
import InlineDiffView from './inline_diff_view.vue';
|
||||
import ParallelDiffView from './parallel_diff_view.vue';
|
||||
import DiffView from './diff_view.vue';
|
||||
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import NoteForm from '../../notes/components/note_form.vue';
|
||||
import ImageDiffOverlay from './image_diff_overlay.vue';
|
||||
|
|
@ -18,12 +19,14 @@ import eventHub from '../../notes/event_hub';
|
|||
import { IMAGE_DIFF_POSITION_TYPE } from '../constants';
|
||||
import { getDiffMode } from '../store/utils';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
import { mapInline, mapParallel } from './diff_row_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
InlineDiffView,
|
||||
ParallelDiffView,
|
||||
DiffView,
|
||||
DiffViewer,
|
||||
NoteForm,
|
||||
DiffDiscussions,
|
||||
|
|
@ -83,6 +86,19 @@ export default {
|
|||
author() {
|
||||
return this.getUserData;
|
||||
},
|
||||
mappedLines() {
|
||||
if (this.glFeatures.unifiedDiffLines && this.glFeatures.unifiedDiffComponents) {
|
||||
return this.diffLines(this.diffFile, true).map(mapParallel(this)) || [];
|
||||
}
|
||||
|
||||
// TODO: Everything below this line can be deleted when unifiedDiffComponents FF is removed
|
||||
if (this.isInlineView) {
|
||||
return this.diffFile.highlighted_diff_lines.map(mapInline(this));
|
||||
}
|
||||
return this.glFeatures.unifiedDiffLines
|
||||
? this.diffLines(this.diffFile).map(mapParallel(this))
|
||||
: this.diffFile.parallel_diff_lines.map(mapParallel(this)) || [];
|
||||
},
|
||||
},
|
||||
updated() {
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -113,19 +129,28 @@ export default {
|
|||
<template>
|
||||
<div class="diff-content">
|
||||
<div class="diff-viewer">
|
||||
<template v-if="isTextFile">
|
||||
<template
|
||||
v-if="isTextFile && glFeatures.unifiedDiffLines && glFeatures.unifiedDiffComponents"
|
||||
>
|
||||
<diff-view
|
||||
:diff-file="diffFile"
|
||||
:diff-lines="mappedLines"
|
||||
:help-page-path="helpPagePath"
|
||||
:inline="isInlineView"
|
||||
/>
|
||||
<gl-loading-icon v-if="diffFile.renderingLines" size="md" class="mt-3" />
|
||||
</template>
|
||||
<template v-else-if="isTextFile">
|
||||
<inline-diff-view
|
||||
v-if="isInlineView"
|
||||
:diff-file="diffFile"
|
||||
:diff-lines="diffFile.highlighted_diff_lines"
|
||||
:diff-lines="mappedLines"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
<parallel-diff-view
|
||||
v-else-if="isParallelView"
|
||||
:diff-file="diffFile"
|
||||
:diff-lines="
|
||||
glFeatures.unifiedDiffLines ? diffLines(diffFile) : diffFile.parallel_diff_lines || []
|
||||
"
|
||||
:diff-lines="mappedLines"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
<gl-loading-icon v-if="diffFile.renderingLines" size="md" class="mt-3" />
|
||||
|
|
|
|||
|
|
@ -54,11 +54,6 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
colspan: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 4,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
|
|
@ -231,28 +226,26 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<td :colspan="colspan" class="text-center gl-font-regular">
|
||||
<div class="content js-line-expansion-content">
|
||||
<a
|
||||
v-if="canExpandDown"
|
||||
class="gl-mx-2 gl-cursor-pointer js-unfold-down gl-display-inline-block gl-py-4"
|
||||
@click="handleExpandLines(EXPAND_DOWN)"
|
||||
>
|
||||
<gl-icon :size="12" name="expand-down" aria-hidden="true" />
|
||||
<span>{{ $options.i18n.showMore }}</span>
|
||||
</a>
|
||||
<a class="gl-mx-2 cursor-pointer js-unfold-all" @click="handleExpandLines()">
|
||||
<gl-icon :size="12" name="expand" aria-hidden="true" />
|
||||
<span>{{ $options.i18n.showAll }}</span>
|
||||
</a>
|
||||
<a
|
||||
v-if="canExpandUp"
|
||||
class="gl-mx-2 gl-cursor-pointer js-unfold gl-display-inline-block gl-py-4"
|
||||
@click="handleExpandLines(EXPAND_UP)"
|
||||
>
|
||||
<gl-icon :size="12" name="expand-up" aria-hidden="true" />
|
||||
<span>{{ $options.i18n.showMore }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<div class="content js-line-expansion-content">
|
||||
<a
|
||||
v-if="canExpandDown"
|
||||
class="gl-mx-2 gl-cursor-pointer js-unfold-down gl-display-inline-block gl-py-4"
|
||||
@click="handleExpandLines(EXPAND_DOWN)"
|
||||
>
|
||||
<gl-icon :size="12" name="expand-down" aria-hidden="true" />
|
||||
<span>{{ $options.i18n.showMore }}</span>
|
||||
</a>
|
||||
<a class="gl-mx-2 cursor-pointer js-unfold-all" @click="handleExpandLines()">
|
||||
<gl-icon :size="12" name="expand" aria-hidden="true" />
|
||||
<span>{{ $options.i18n.showAll }}</span>
|
||||
</a>
|
||||
<a
|
||||
v-if="canExpandUp"
|
||||
class="gl-mx-2 gl-cursor-pointer js-unfold gl-display-inline-block gl-py-4"
|
||||
@click="handleExpandLines(EXPAND_UP)"
|
||||
>
|
||||
<gl-icon :size="12" name="expand-up" aria-hidden="true" />
|
||||
<span>{{ $options.i18n.showMore }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,271 @@
|
|||
<script>
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
|
||||
import { CONTEXT_LINE_CLASS_NAME, PARALLEL_DIFF_VIEW_TYPE } from '../constants';
|
||||
import DiffGutterAvatars from './diff_gutter_avatars.vue';
|
||||
import * as utils from './diff_row_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
DiffGutterAvatars,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
SafeHtml,
|
||||
},
|
||||
props: {
|
||||
fileHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
filePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
line: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isCommented: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('diffs', ['fileLineCoverage']),
|
||||
...mapGetters(['isLoggedIn']),
|
||||
...mapState({
|
||||
isHighlighted(state) {
|
||||
const line = this.line.left?.line_code ? this.line.left : this.line.right;
|
||||
return utils.isHighlighted(state, line, this.isCommented);
|
||||
},
|
||||
}),
|
||||
classNameMap() {
|
||||
return {
|
||||
[CONTEXT_LINE_CLASS_NAME]: this.line.isContextLineLeft,
|
||||
[PARALLEL_DIFF_VIEW_TYPE]: true,
|
||||
};
|
||||
},
|
||||
parallelViewLeftLineType() {
|
||||
return utils.parallelViewLeftLineType(this.line, this.isHighlighted);
|
||||
},
|
||||
coverageState() {
|
||||
return this.fileLineCoverage(this.filePath, this.line.right.new_line);
|
||||
},
|
||||
classNameMapCellLeft() {
|
||||
return utils.classNameMapCell(this.line.left, this.isHighlighted, this.isLoggedIn);
|
||||
},
|
||||
classNameMapCellRight() {
|
||||
return utils.classNameMapCell(this.line.right, this.isHighlighted, this.isLoggedIn);
|
||||
},
|
||||
addCommentTooltipLeft() {
|
||||
return utils.addCommentTooltip(this.line.left);
|
||||
},
|
||||
addCommentTooltipRight() {
|
||||
return utils.addCommentTooltip(this.line.right);
|
||||
},
|
||||
shouldRenderCommentButton() {
|
||||
return (
|
||||
this.isLoggedIn &&
|
||||
!this.line.isContextLineLeft &&
|
||||
!this.line.isMetaLineLeft &&
|
||||
!this.line.hasDiscussionsLeft &&
|
||||
!this.line.hasDiscussionsRight
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToLineIfNeededParallel(this.line);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', [
|
||||
'scrollToLineIfNeededParallel',
|
||||
'showCommentForm',
|
||||
'setHighlightedRow',
|
||||
'toggleLineDiscussions',
|
||||
]),
|
||||
// Prevent text selecting on both sides of parallel diff view
|
||||
// Backport of the same code from legacy diff notes.
|
||||
handleParallelLineMouseDown(e) {
|
||||
const line = e.currentTarget;
|
||||
const table = line.closest('.diff-table');
|
||||
|
||||
table.classList.remove('left-side-selected', 'right-side-selected');
|
||||
const [lineClass] = ['left-side', 'right-side'].filter(name => line.classList.contains(name));
|
||||
|
||||
if (lineClass) {
|
||||
table.classList.add(`${lineClass}-selected`);
|
||||
}
|
||||
},
|
||||
handleCommentButton(line) {
|
||||
this.showCommentForm({ lineCode: line.line_code, fileHash: this.fileHash });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classNameMap" class="diff-grid-row diff-tr line_holder">
|
||||
<div class="diff-grid-left left-side">
|
||||
<template v-if="line.left">
|
||||
<div
|
||||
:class="classNameMapCellLeft"
|
||||
data-testid="leftLineNumber"
|
||||
class="diff-td diff-line-num old_line"
|
||||
>
|
||||
<span
|
||||
v-if="shouldRenderCommentButton"
|
||||
v-gl-tooltip
|
||||
data-testid="leftCommentButton"
|
||||
class="add-diff-note tooltip-wrapper"
|
||||
:title="addCommentTooltipLeft"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="add-diff-note note-button js-add-diff-note-button qa-diff-comment"
|
||||
:disabled="line.left.commentsDisabled"
|
||||
@click="handleCommentButton(line.left)"
|
||||
>
|
||||
<gl-icon :size="12" name="comment" />
|
||||
</button>
|
||||
</span>
|
||||
<a
|
||||
v-if="line.left.old_line"
|
||||
:data-linenumber="line.left.old_line"
|
||||
:href="line.lineHrefOld"
|
||||
@click="setHighlightedRow(line.lineCode)"
|
||||
>
|
||||
</a>
|
||||
<diff-gutter-avatars
|
||||
v-if="line.hasDiscussionsLeft"
|
||||
:discussions="line.left.discussions"
|
||||
:discussions-expanded="line.left.discussionsExpanded"
|
||||
data-testid="leftDiscussions"
|
||||
@toggleLineDiscussions="
|
||||
toggleLineDiscussions({
|
||||
lineCode: line.left.line_code,
|
||||
fileHash,
|
||||
expanded: !line.left.discussionsExpanded,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div :class="classNameMapCellLeft" class="diff-td diff-line-num old_line">
|
||||
<a
|
||||
v-if="line.left.old_line"
|
||||
:data-linenumber="line.left.old_line"
|
||||
:href="line.lineHrefOld"
|
||||
@click="setHighlightedRow(line.lineCode)"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<div :class="parallelViewLeftLineType" class="diff-td line-coverage left-side"></div>
|
||||
<div
|
||||
:id="line.left.line_code"
|
||||
:key="line.left.line_code"
|
||||
v-safe-html="line.left.rich_text"
|
||||
:class="parallelViewLeftLineType"
|
||||
class="diff-td line_content with-coverage parallel left-side"
|
||||
data-testid="leftContent"
|
||||
@mousedown="handleParallelLineMouseDown"
|
||||
></div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div data-testid="leftEmptyCell" class="diff-td diff-line-num old_line empty-cell"></div>
|
||||
<div class="diff-td diff-line-num old_line empty-cell"></div>
|
||||
<div class="diff-td line-coverage left-side empty-cell"></div>
|
||||
<div class="diff-td line_content with-coverage parallel left-side empty-cell"></div>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="!inline || (line.right && Boolean(line.right.type))"
|
||||
class="diff-grid-right right-side"
|
||||
>
|
||||
<template v-if="line.right">
|
||||
<div
|
||||
:class="classNameMapCellRight"
|
||||
data-testid="rightLineNumber"
|
||||
class="diff-td diff-line-num new_line"
|
||||
>
|
||||
<span
|
||||
v-if="shouldRenderCommentButton"
|
||||
v-gl-tooltip
|
||||
data-testid="rightCommentButton"
|
||||
class="add-diff-note tooltip-wrapper"
|
||||
:title="addCommentTooltipRight"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="add-diff-note note-button js-add-diff-note-button qa-diff-comment"
|
||||
:disabled="line.right.commentsDisabled"
|
||||
@click="handleCommentButton(line.right)"
|
||||
>
|
||||
<gl-icon :size="12" name="comment" />
|
||||
</button>
|
||||
</span>
|
||||
<a
|
||||
v-if="line.right.new_line"
|
||||
:data-linenumber="line.right.new_line"
|
||||
:href="line.lineHrefNew"
|
||||
@click="setHighlightedRow(line.lineCode)"
|
||||
>
|
||||
</a>
|
||||
<diff-gutter-avatars
|
||||
v-if="line.hasDiscussionsRight"
|
||||
:discussions="line.right.discussions"
|
||||
:discussions-expanded="line.right.discussionsExpanded"
|
||||
data-testid="rightDiscussions"
|
||||
@toggleLineDiscussions="
|
||||
toggleLineDiscussions({
|
||||
lineCode: line.right.line_code,
|
||||
fileHash,
|
||||
expanded: !line.right.discussionsExpanded,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div :class="classNameMapCellRight" class="diff-td diff-line-num new_line">
|
||||
<a
|
||||
v-if="line.right.new_line"
|
||||
:data-linenumber="line.right.new_line"
|
||||
:href="line.lineHrefNew"
|
||||
@click="setHighlightedRow(line.lineCode)"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
v-gl-tooltip.hover
|
||||
:title="coverageState.text"
|
||||
:class="[line.right.type, coverageState.class, { hll: isHighlighted }]"
|
||||
class="diff-td line-coverage right-side"
|
||||
></div>
|
||||
<div
|
||||
:id="line.right.line_code"
|
||||
:key="line.right.rich_text"
|
||||
v-safe-html="line.right.rich_text"
|
||||
:class="[
|
||||
line.right.type,
|
||||
{
|
||||
hll: isHighlighted,
|
||||
},
|
||||
]"
|
||||
class="diff-td line_content with-coverage parallel right-side"
|
||||
@mousedown="handleParallelLineMouseDown"
|
||||
></div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div data-testid="rightEmptyCell" class="diff-td diff-line-num old_line empty-cell"></div>
|
||||
<div class="diff-td diff-line-num old_line empty-cell"></div>
|
||||
<div class="diff-td line-coverage right-side empty-cell"></div>
|
||||
<div class="diff-td line_content with-coverage parallel right-side empty-cell"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -83,3 +83,76 @@ export const parallelViewLeftLineType = (line, hll) => {
|
|||
export const shouldShowCommentButton = (hover, context, meta, discussions) => {
|
||||
return hover && !context && !meta && !discussions;
|
||||
};
|
||||
|
||||
export const mapParallel = content => line => {
|
||||
let { left, right } = line;
|
||||
|
||||
// Dicussions/Comments
|
||||
const hasExpandedDiscussionOnLeft =
|
||||
left?.discussions?.length > 0 ? left?.discussionsExpanded : false;
|
||||
const hasExpandedDiscussionOnRight =
|
||||
right?.discussions?.length > 0 ? right?.discussionsExpanded : false;
|
||||
|
||||
const renderCommentRow =
|
||||
hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight || left?.hasForm || right?.hasForm;
|
||||
|
||||
if (left) {
|
||||
left = {
|
||||
...left,
|
||||
renderDiscussion: hasExpandedDiscussionOnLeft,
|
||||
hasDraft: content.hasParallelDraftLeft(content.diffFile.file_hash, line),
|
||||
lineDraft: content.draftForLine(content.diffFile.file_hash, line, 'left'),
|
||||
hasCommentForm: left.hasForm,
|
||||
};
|
||||
}
|
||||
if (right) {
|
||||
right = {
|
||||
...right,
|
||||
renderDiscussion: Boolean(hasExpandedDiscussionOnRight && right.type),
|
||||
hasDraft: content.hasParallelDraftRight(content.diffFile.file_hash, line),
|
||||
lineDraft: content.draftForLine(content.diffFile.file_hash, line, 'right'),
|
||||
hasCommentForm: Boolean(right.hasForm && right.type),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...line,
|
||||
left,
|
||||
right,
|
||||
isMatchLineLeft: isMatchLine(left?.type),
|
||||
isMatchLineRight: isMatchLine(right?.type),
|
||||
isContextLineLeft: isContextLine(left?.type),
|
||||
isContextLineRight: isContextLine(right?.type),
|
||||
hasDiscussionsLeft: hasDiscussions(left),
|
||||
hasDiscussionsRight: hasDiscussions(right),
|
||||
lineHrefOld: lineHref(left),
|
||||
lineHrefNew: lineHref(right),
|
||||
lineCode: lineCode(line),
|
||||
isMetaLineLeft: isMetaLine(left?.type),
|
||||
isMetaLineRight: isMetaLine(right?.type),
|
||||
draftRowClasses: left?.lineDraft > 0 || right?.lineDraft > 0 ? '' : 'js-temp-notes-holder',
|
||||
renderCommentRow,
|
||||
commentRowClasses: hasDiscussions(left) || hasDiscussions(right) ? '' : 'js-temp-notes-holder',
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: Delete this function when unifiedDiffComponents FF is removed
|
||||
export const mapInline = content => line => {
|
||||
// Discussions/Comments
|
||||
const renderCommentRow = line.hasForm || (line.discussions?.length && line.discussionsExpanded);
|
||||
|
||||
return {
|
||||
...line,
|
||||
renderDiscussion: Boolean(line.discussions?.length),
|
||||
isMatchLine: isMatchLine(line.type),
|
||||
commentRowClasses: line.discussions?.length ? '' : 'js-temp-notes-holder',
|
||||
renderCommentRow,
|
||||
hasDraft: content.shouldRenderDraftRow(content.diffFile.file_hash, line),
|
||||
hasCommentForm: line.hasForm,
|
||||
isMetaLine: isMetaLine(line.type),
|
||||
isContextLine: isContextLine(line.type),
|
||||
hasDiscussions: hasDiscussions(line),
|
||||
lineHref: lineHref(line),
|
||||
lineCode: lineCode(line),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
<script>
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
|
||||
import DraftNote from '~/batch_comments/components/draft_note.vue';
|
||||
import DiffRow from './diff_row.vue';
|
||||
import DiffCommentCell from './diff_comment_cell.vue';
|
||||
import DiffExpansionCell from './diff_expansion_cell.vue';
|
||||
import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DiffExpansionCell,
|
||||
DiffRow,
|
||||
DiffCommentCell,
|
||||
DraftNote,
|
||||
},
|
||||
mixins: [draftCommentsMixin],
|
||||
props: {
|
||||
diffFile: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
diffLines: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('diffs', ['commitId']),
|
||||
...mapState({
|
||||
selectedCommentPosition: ({ notes }) => notes.selectedCommentPosition,
|
||||
selectedCommentPositionHover: ({ notes }) => notes.selectedCommentPositionHover,
|
||||
}),
|
||||
diffLinesLength() {
|
||||
return this.diffLines.length;
|
||||
},
|
||||
commentedLines() {
|
||||
return getCommentedLines(
|
||||
this.selectedCommentPosition || this.selectedCommentPositionHover,
|
||||
this.diffLines,
|
||||
);
|
||||
},
|
||||
},
|
||||
userColorScheme: window.gon.user_color_scheme,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[$options.userColorScheme, { inline }]"
|
||||
:data-commit-id="commitId"
|
||||
class="diff-grid diff-table code diff-wrap-lines js-syntax-highlight text-file"
|
||||
>
|
||||
<template v-for="(line, index) in diffLines">
|
||||
<div
|
||||
v-if="line.isMatchLineLeft || line.isMatchLineRight"
|
||||
:key="`expand-${index}`"
|
||||
class="diff-tr line_expansion match"
|
||||
>
|
||||
<div class="diff-td text-center gl-font-regular">
|
||||
<diff-expansion-cell
|
||||
:file-hash="diffFile.file_hash"
|
||||
:context-lines-path="diffFile.context_lines_path"
|
||||
:line="line.left"
|
||||
:is-top="index === 0"
|
||||
:is-bottom="index + 1 === diffLinesLength"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<diff-row
|
||||
v-if="!line.isMatchLineLeft && !line.isMatchLineRight"
|
||||
:key="line.line_code"
|
||||
:file-hash="diffFile.file_hash"
|
||||
:file-path="diffFile.file_path"
|
||||
:line="line"
|
||||
:is-bottom="index + 1 === diffLinesLength"
|
||||
:is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
|
||||
:inline="inline"
|
||||
/>
|
||||
<div
|
||||
v-if="line.renderCommentRow"
|
||||
:key="`dcr-${line.line_code || index}`"
|
||||
:class="line.commentRowClasses"
|
||||
class="diff-grid-comments diff-tr notes_holder"
|
||||
>
|
||||
<div
|
||||
v-if="!inline || (line.left && line.left.discussions.length)"
|
||||
class="diff-td notes-content parallel old"
|
||||
>
|
||||
<diff-comment-cell
|
||||
v-if="line.left"
|
||||
:line="line.left"
|
||||
:diff-file-hash="diffFile.file_hash"
|
||||
:help-page-path="helpPagePath"
|
||||
:has-draft="line.left.hasDraft"
|
||||
line-position="left"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!inline || (line.right && line.right.discussions.length)"
|
||||
class="diff-td notes-content parallel new"
|
||||
>
|
||||
<diff-comment-cell
|
||||
v-if="line.right"
|
||||
:line="line.right"
|
||||
:diff-file-hash="diffFile.file_hash"
|
||||
:line-index="index"
|
||||
:help-page-path="helpPagePath"
|
||||
:has-draft="line.right.hasDraft"
|
||||
line-position="right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
|
||||
:key="`drafts-${index}`"
|
||||
:class="line.draftRowClasses"
|
||||
class="diff-grid-drafts diff-tr notes_holder"
|
||||
>
|
||||
<div
|
||||
v-if="!inline || (line.left && line.left.lineDraft.isDraft)"
|
||||
class="diff-td notes-content parallel old"
|
||||
>
|
||||
<div v-if="line.left && line.left.lineDraft.isDraft" class="content">
|
||||
<draft-note :draft="line.left.lineDraft" :line="line.left" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!inline || (line.right && line.right.lineDraft.isDraft)"
|
||||
class="diff-td notes-content parallel new"
|
||||
>
|
||||
<div v-if="line.right && line.right.lineDraft.isDraft" class="content">
|
||||
<draft-note :draft="line.right.lineDraft" :line="line.right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import DiffDiscussions from './diff_discussions.vue';
|
||||
import DiffLineNoteForm from './diff_line_note_form.vue';
|
||||
import DiffDiscussionReply from './diff_discussion_reply.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DiffDiscussions,
|
||||
DiffLineNoteForm,
|
||||
DiffDiscussionReply,
|
||||
},
|
||||
props: {
|
||||
line: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
diffFileHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
hasDraft: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
className() {
|
||||
return this.line.discussions.length ? '' : 'js-temp-notes-holder';
|
||||
},
|
||||
shouldRender() {
|
||||
if (this.line.hasForm) return true;
|
||||
|
||||
if (!this.line.discussions || !this.line.discussions.length) {
|
||||
return false;
|
||||
}
|
||||
return this.line.discussionsExpanded;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['showCommentForm']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr v-if="shouldRender" :class="className" class="notes_holder">
|
||||
<td class="notes-content" colspan="4">
|
||||
<div class="content">
|
||||
<diff-discussions
|
||||
v-if="line.discussions.length"
|
||||
:line="line"
|
||||
:discussions="line.discussions"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
<diff-discussion-reply
|
||||
v-if="!hasDraft"
|
||||
:has-form="line.hasForm"
|
||||
:render-reply-placeholder="Boolean(line.discussions.length)"
|
||||
@showNewDiscussionForm="
|
||||
showCommentForm({ lineCode: line.line_code, fileHash: diffFileHash })
|
||||
"
|
||||
>
|
||||
<template #form>
|
||||
<diff-line-note-form
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line"
|
||||
:note-target-line="line"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
</template>
|
||||
</diff-discussion-reply>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<script>
|
||||
import DiffExpansionCell from './diff_expansion_cell.vue';
|
||||
import { MATCH_LINE_TYPE } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DiffExpansionCell,
|
||||
},
|
||||
props: {
|
||||
fileHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contextLinesPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
line: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isTop: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
isBottom: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isMatchLine() {
|
||||
return this.line.type === MATCH_LINE_TYPE;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr v-if="isMatchLine" class="line_expansion match">
|
||||
<diff-expansion-cell
|
||||
:file-hash="fileHash"
|
||||
:context-lines-path="contextLinesPath"
|
||||
:line="line"
|
||||
:is-top="isTop"
|
||||
:is-bottom="isBottom"
|
||||
/>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
@ -3,7 +3,13 @@ import { mapActions, mapGetters, mapState } from 'vuex';
|
|||
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
|
||||
import { CONTEXT_LINE_CLASS_NAME } from '../constants';
|
||||
import DiffGutterAvatars from './diff_gutter_avatars.vue';
|
||||
import * as utils from './diff_row_utils';
|
||||
import {
|
||||
isHighlighted,
|
||||
shouldShowCommentButton,
|
||||
shouldRenderCommentButton,
|
||||
classNameMapCell,
|
||||
addCommentTooltip,
|
||||
} from './diff_row_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -48,60 +54,42 @@ export default {
|
|||
...mapGetters('diffs', ['fileLineCoverage']),
|
||||
...mapState({
|
||||
isHighlighted(state) {
|
||||
return utils.isHighlighted(state, this.line, this.isCommented);
|
||||
return isHighlighted(state, this.line, this.isCommented);
|
||||
},
|
||||
}),
|
||||
isContextLine() {
|
||||
return utils.isContextLine(this.line.type);
|
||||
},
|
||||
classNameMap() {
|
||||
return [
|
||||
this.line.type,
|
||||
{
|
||||
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
|
||||
[CONTEXT_LINE_CLASS_NAME]: this.line.isContextLine,
|
||||
},
|
||||
];
|
||||
},
|
||||
inlineRowId() {
|
||||
return this.line.line_code || `${this.fileHash}_${this.line.old_line}_${this.line.new_line}`;
|
||||
},
|
||||
isMatchLine() {
|
||||
return utils.isMatchLine(this.line.type);
|
||||
},
|
||||
coverageState() {
|
||||
return this.fileLineCoverage(this.filePath, this.line.new_line);
|
||||
},
|
||||
isMetaLine() {
|
||||
return utils.isMetaLine(this.line.type);
|
||||
},
|
||||
classNameMapCell() {
|
||||
return utils.classNameMapCell(this.line, this.isHighlighted, this.isLoggedIn, this.isHover);
|
||||
return classNameMapCell(this.line, this.isHighlighted, this.isLoggedIn, this.isHover);
|
||||
},
|
||||
addCommentTooltip() {
|
||||
return utils.addCommentTooltip(this.line);
|
||||
return addCommentTooltip(this.line);
|
||||
},
|
||||
shouldRenderCommentButton() {
|
||||
return utils.shouldRenderCommentButton(this.isLoggedIn, true);
|
||||
return shouldRenderCommentButton(this.isLoggedIn, true);
|
||||
},
|
||||
shouldShowCommentButton() {
|
||||
return utils.shouldShowCommentButton(
|
||||
return shouldShowCommentButton(
|
||||
this.isHover,
|
||||
this.isContextLine,
|
||||
this.isMetaLine,
|
||||
this.hasDiscussions,
|
||||
this.line.isContextLine,
|
||||
this.line.isMetaLine,
|
||||
this.line.hasDiscussions,
|
||||
);
|
||||
},
|
||||
hasDiscussions() {
|
||||
return utils.hasDiscussions(this.line);
|
||||
},
|
||||
lineHref() {
|
||||
return utils.lineHref(this.line);
|
||||
},
|
||||
lineCode() {
|
||||
return utils.lineCode(this.line);
|
||||
},
|
||||
shouldShowAvatarsOnGutter() {
|
||||
return this.hasDiscussions;
|
||||
return this.line.hasDiscussions;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -128,7 +116,6 @@ export default {
|
|||
|
||||
<template>
|
||||
<tr
|
||||
v-if="!isMatchLine"
|
||||
:id="inlineRowId"
|
||||
:class="classNameMap"
|
||||
class="line_holder"
|
||||
|
|
@ -158,8 +145,8 @@ export default {
|
|||
v-if="line.old_line"
|
||||
ref="lineNumberRefOld"
|
||||
:data-linenumber="line.old_line"
|
||||
:href="lineHref"
|
||||
@click="setHighlightedRow(lineCode)"
|
||||
:href="line.lineHref"
|
||||
@click="setHighlightedRow(line.lineCode)"
|
||||
>
|
||||
</a>
|
||||
<diff-gutter-avatars
|
||||
|
|
@ -167,7 +154,11 @@ export default {
|
|||
:discussions="line.discussions"
|
||||
:discussions-expanded="line.discussionsExpanded"
|
||||
@toggleLineDiscussions="
|
||||
toggleLineDiscussions({ lineCode, fileHash, expanded: !line.discussionsExpanded })
|
||||
toggleLineDiscussions({
|
||||
lineCode: line.lineCode,
|
||||
fileHash,
|
||||
expanded: !line.discussionsExpanded,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
|
|
@ -176,8 +167,8 @@ export default {
|
|||
v-if="line.new_line"
|
||||
ref="lineNumberRefNew"
|
||||
:data-linenumber="line.new_line"
|
||||
:href="lineHref"
|
||||
@click="setHighlightedRow(lineCode)"
|
||||
:href="line.lineHref"
|
||||
@click="setHighlightedRow(line.lineCode)"
|
||||
>
|
||||
</a>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@
|
|||
import { mapGetters, mapState } from 'vuex';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
|
||||
import InlineDraftCommentRow from '~/batch_comments/components/inline_draft_comment_row.vue';
|
||||
import DraftNote from '~/batch_comments/components/draft_note.vue';
|
||||
import inlineDiffTableRow from './inline_diff_table_row.vue';
|
||||
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
|
||||
import inlineDiffExpansionRow from './inline_diff_expansion_row.vue';
|
||||
import DiffCommentCell from './diff_comment_cell.vue';
|
||||
import DiffExpansionCell from './diff_expansion_cell.vue';
|
||||
import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
inlineDiffCommentRow,
|
||||
DiffCommentCell,
|
||||
inlineDiffTableRow,
|
||||
InlineDraftCommentRow,
|
||||
inlineDiffExpansionRow,
|
||||
DraftNote,
|
||||
DiffExpansionCell,
|
||||
},
|
||||
mixins: [draftCommentsMixin, glFeatureFlagsMixin()],
|
||||
props: {
|
||||
|
|
@ -65,15 +65,19 @@ export default {
|
|||
</colgroup>
|
||||
<tbody>
|
||||
<template v-for="(line, index) in diffLines">
|
||||
<inline-diff-expansion-row
|
||||
:key="`expand-${index}`"
|
||||
:file-hash="diffFile.file_hash"
|
||||
:context-lines-path="diffFile.context_lines_path"
|
||||
:line="line"
|
||||
:is-top="index === 0"
|
||||
:is-bottom="index + 1 === diffLinesLength"
|
||||
/>
|
||||
<tr v-if="line.isMatchLine" :key="`expand-${index}`" class="line_expansion match">
|
||||
<td colspan="4" class="text-center gl-font-regular">
|
||||
<diff-expansion-cell
|
||||
:file-hash="diffFile.file_hash"
|
||||
:context-lines-path="diffFile.context_lines_path"
|
||||
:line="line"
|
||||
:is-top="index === 0"
|
||||
:is-bottom="index + 1 === diffLinesLength"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<inline-diff-table-row
|
||||
v-if="!line.isMatchLine"
|
||||
:key="`${line.line_code || index}`"
|
||||
:file-hash="diffFile.file_hash"
|
||||
:file-path="diffFile.file_path"
|
||||
|
|
@ -81,20 +85,32 @@ export default {
|
|||
:is-bottom="index + 1 === diffLinesLength"
|
||||
:is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
|
||||
/>
|
||||
<inline-diff-comment-row
|
||||
<tr
|
||||
v-if="line.renderCommentRow"
|
||||
:key="`icr-${line.line_code || index}`"
|
||||
:diff-file-hash="diffFile.file_hash"
|
||||
:line="line"
|
||||
:help-page-path="helpPagePath"
|
||||
:has-draft="shouldRenderDraftRow(diffFile.file_hash, line) || false"
|
||||
/>
|
||||
<inline-draft-comment-row
|
||||
v-if="shouldRenderDraftRow(diffFile.file_hash, line)"
|
||||
:key="`draft_${index}`"
|
||||
:draft="draftForLine(diffFile.file_hash, line)"
|
||||
:diff-file="diffFile"
|
||||
:line="line"
|
||||
/>
|
||||
:class="line.commentRowClasses"
|
||||
class="notes_holder"
|
||||
>
|
||||
<td class="notes-content" colspan="4">
|
||||
<diff-comment-cell
|
||||
:diff-file-hash="diffFile.file_hash"
|
||||
:line="line"
|
||||
:help-page-path="helpPagePath"
|
||||
:has-draft="line.hasDraft"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="line.hasDraft" :key="`draft_${index}`" class="notes_holder js-temp-notes-holder">
|
||||
<td class="notes-content" colspan="4">
|
||||
<div class="content">
|
||||
<draft-note
|
||||
:draft="draftForLine(diffFile.file_hash, line)"
|
||||
:diff-file="diffFile"
|
||||
:line="line"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import DiffDiscussions from './diff_discussions.vue';
|
||||
import DiffLineNoteForm from './diff_line_note_form.vue';
|
||||
import DiffDiscussionReply from './diff_discussion_reply.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DiffDiscussions,
|
||||
DiffLineNoteForm,
|
||||
DiffDiscussionReply,
|
||||
},
|
||||
props: {
|
||||
line: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
diffFileHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
lineIndex: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
hasDraftLeft: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
hasDraftRight: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasExpandedDiscussionOnLeft() {
|
||||
return this.line.left && this.line.left.discussions.length
|
||||
? this.line.left.discussionsExpanded
|
||||
: false;
|
||||
},
|
||||
hasExpandedDiscussionOnRight() {
|
||||
return this.line.right && this.line.right.discussions.length
|
||||
? this.line.right.discussionsExpanded
|
||||
: false;
|
||||
},
|
||||
hasAnyExpandedDiscussion() {
|
||||
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
|
||||
},
|
||||
shouldRenderDiscussionsOnLeft() {
|
||||
return (
|
||||
this.line.left &&
|
||||
this.line.left.discussions &&
|
||||
this.line.left.discussions.length &&
|
||||
this.hasExpandedDiscussionOnLeft
|
||||
);
|
||||
},
|
||||
shouldRenderDiscussionsOnRight() {
|
||||
return (
|
||||
this.line.right &&
|
||||
this.line.right.discussions &&
|
||||
this.line.right.discussions.length &&
|
||||
this.hasExpandedDiscussionOnRight &&
|
||||
this.line.right.type
|
||||
);
|
||||
},
|
||||
showRightSideCommentForm() {
|
||||
return this.line.right && this.line.right.type && this.line.right.hasForm;
|
||||
},
|
||||
showLeftSideCommentForm() {
|
||||
return this.line.left && this.line.left.hasForm;
|
||||
},
|
||||
className() {
|
||||
return (this.left && this.line.left.discussions.length > 0) ||
|
||||
(this.right && this.line.right.discussions.length > 0)
|
||||
? ''
|
||||
: 'js-temp-notes-holder';
|
||||
},
|
||||
shouldRender() {
|
||||
const { line } = this;
|
||||
const hasDiscussion =
|
||||
(line.left && line.left.discussions && line.left.discussions.length) ||
|
||||
(line.right && line.right.discussions && line.right.discussions.length);
|
||||
|
||||
if (
|
||||
hasDiscussion &&
|
||||
(this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasCommentFormOnLeft = line.left && line.left.hasForm;
|
||||
const hasCommentFormOnRight = line.right && line.right.hasForm;
|
||||
|
||||
return hasCommentFormOnLeft || hasCommentFormOnRight;
|
||||
},
|
||||
shouldRenderReplyPlaceholderOnLeft() {
|
||||
return Boolean(
|
||||
this.line.left && this.line.left.discussions && this.line.left.discussions.length,
|
||||
);
|
||||
},
|
||||
shouldRenderReplyPlaceholderOnRight() {
|
||||
return Boolean(
|
||||
this.line.right && this.line.right.discussions && this.line.right.discussions.length,
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['showCommentForm']),
|
||||
showNewDiscussionForm(lineCode) {
|
||||
this.showCommentForm({ lineCode, fileHash: this.diffFileHash });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr v-if="shouldRender" :class="className" class="notes_holder">
|
||||
<td class="notes-content parallel old" colspan="3">
|
||||
<div v-if="shouldRenderDiscussionsOnLeft" class="content">
|
||||
<diff-discussions
|
||||
:discussions="line.left.discussions"
|
||||
:line="line.left"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
</div>
|
||||
<diff-discussion-reply
|
||||
v-if="!hasDraftLeft"
|
||||
:has-form="showLeftSideCommentForm"
|
||||
:render-reply-placeholder="shouldRenderReplyPlaceholderOnLeft"
|
||||
@showNewDiscussionForm="showNewDiscussionForm(line.left.line_code)"
|
||||
>
|
||||
<template #form>
|
||||
<diff-line-note-form
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line.left"
|
||||
:note-target-line="line.left"
|
||||
:help-page-path="helpPagePath"
|
||||
line-position="left"
|
||||
/>
|
||||
</template>
|
||||
</diff-discussion-reply>
|
||||
</td>
|
||||
<td class="notes-content parallel new" colspan="3">
|
||||
<div v-if="shouldRenderDiscussionsOnRight" class="content">
|
||||
<diff-discussions
|
||||
:discussions="line.right.discussions"
|
||||
:line="line.right"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
</div>
|
||||
<diff-discussion-reply
|
||||
v-if="!hasDraftRight"
|
||||
:has-form="showRightSideCommentForm"
|
||||
:render-reply-placeholder="shouldRenderReplyPlaceholderOnRight"
|
||||
@showNewDiscussionForm="showNewDiscussionForm(line.right.line_code)"
|
||||
>
|
||||
<template #form>
|
||||
<diff-line-note-form
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line.right"
|
||||
:note-target-line="line.right"
|
||||
line-position="right"
|
||||
/>
|
||||
</template>
|
||||
</diff-discussion-reply>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
<script>
|
||||
import { MATCH_LINE_TYPE } from '../constants';
|
||||
import DiffExpansionCell from './diff_expansion_cell.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DiffExpansionCell,
|
||||
},
|
||||
props: {
|
||||
fileHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contextLinesPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
line: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isTop: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
isBottom: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isMatchLineLeft() {
|
||||
return this.line.left && this.line.left.type === MATCH_LINE_TYPE;
|
||||
},
|
||||
isMatchLineRight() {
|
||||
return this.line.right && this.line.right.type === MATCH_LINE_TYPE;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<tr class="line_expansion match">
|
||||
<template v-if="isMatchLineLeft || isMatchLineRight">
|
||||
<diff-expansion-cell
|
||||
:file-hash="fileHash"
|
||||
:context-lines-path="contextLinesPath"
|
||||
:line="line.left"
|
||||
:is-top="isTop"
|
||||
:is-bottom="isBottom"
|
||||
:colspan="6"
|
||||
/>
|
||||
</template>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
@ -55,27 +55,15 @@ export default {
|
|||
return utils.isHighlighted(state, line, this.isCommented);
|
||||
},
|
||||
}),
|
||||
isContextLineLeft() {
|
||||
return utils.isContextLine(this.line.left?.type);
|
||||
},
|
||||
isContextLineRight() {
|
||||
return utils.isContextLine(this.line.right?.type);
|
||||
},
|
||||
classNameMap() {
|
||||
return {
|
||||
[CONTEXT_LINE_CLASS_NAME]: this.isContextLineLeft,
|
||||
[CONTEXT_LINE_CLASS_NAME]: this.line.isContextLineLeft,
|
||||
[PARALLEL_DIFF_VIEW_TYPE]: true,
|
||||
};
|
||||
},
|
||||
parallelViewLeftLineType() {
|
||||
return utils.parallelViewLeftLineType(this.line, this.isHighlighted);
|
||||
},
|
||||
isMatchLineLeft() {
|
||||
return utils.isMatchLine(this.line.left?.type);
|
||||
},
|
||||
isMatchLineRight() {
|
||||
return utils.isMatchLine(this.line.right?.type);
|
||||
},
|
||||
coverageState() {
|
||||
return this.fileLineCoverage(this.filePath, this.line.right.new_line);
|
||||
},
|
||||
|
|
@ -107,40 +95,19 @@ export default {
|
|||
shouldShowCommentButtonLeft() {
|
||||
return utils.shouldShowCommentButton(
|
||||
this.isLeftHover,
|
||||
this.isContextLineLeft,
|
||||
this.isMetaLineLeft,
|
||||
this.hasDiscussionsLeft,
|
||||
this.line.isContextLineLeft,
|
||||
this.line.isMetaLineLeft,
|
||||
this.line.hasDiscussionsLeft,
|
||||
);
|
||||
},
|
||||
shouldShowCommentButtonRight() {
|
||||
return utils.shouldShowCommentButton(
|
||||
this.isRightHover,
|
||||
this.isContextLineRight,
|
||||
this.isMetaLineRight,
|
||||
this.hasDiscussionsRight,
|
||||
this.line.isContextLineRight,
|
||||
this.line.isMetaLineRight,
|
||||
this.line.hasDiscussionsRight,
|
||||
);
|
||||
},
|
||||
hasDiscussionsLeft() {
|
||||
return utils.hasDiscussions(this.line.left);
|
||||
},
|
||||
hasDiscussionsRight() {
|
||||
return utils.hasDiscussions(this.line.right);
|
||||
},
|
||||
lineHrefOld() {
|
||||
return utils.lineHref(this.line.left);
|
||||
},
|
||||
lineHrefNew() {
|
||||
return utils.lineHref(this.line.right);
|
||||
},
|
||||
lineCode() {
|
||||
return utils.lineCode(this.line);
|
||||
},
|
||||
isMetaLineLeft() {
|
||||
return utils.isMetaLine(this.line.left?.type);
|
||||
},
|
||||
isMetaLineRight() {
|
||||
return utils.isMetaLine(this.line.right?.type);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToLineIfNeededParallel(this.line);
|
||||
|
|
@ -203,7 +170,7 @@ export default {
|
|||
@mouseover="handleMouseMove"
|
||||
@mouseout="handleMouseMove"
|
||||
>
|
||||
<template v-if="line.left && !isMatchLineLeft">
|
||||
<template v-if="line.left && !line.isMatchLineLeft">
|
||||
<td ref="oldTd" :class="classNameMapCellLeft" class="diff-line-num old_line">
|
||||
<span
|
||||
v-if="shouldRenderCommentButton"
|
||||
|
|
@ -227,12 +194,12 @@ export default {
|
|||
v-if="line.left.old_line"
|
||||
ref="lineNumberRefOld"
|
||||
:data-linenumber="line.left.old_line"
|
||||
:href="lineHrefOld"
|
||||
@click="setHighlightedRow(lineCode)"
|
||||
:href="line.lineHrefOld"
|
||||
@click="setHighlightedRow(line.lineCode)"
|
||||
>
|
||||
</a>
|
||||
<diff-gutter-avatars
|
||||
v-if="hasDiscussionsLeft"
|
||||
v-if="line.hasDiscussionsLeft"
|
||||
:discussions="line.left.discussions"
|
||||
:discussions-expanded="line.left.discussionsExpanded"
|
||||
@toggleLineDiscussions="
|
||||
|
|
@ -259,7 +226,7 @@ export default {
|
|||
<td class="line-coverage left-side empty-cell"></td>
|
||||
<td class="line_content with-coverage parallel left-side empty-cell"></td>
|
||||
</template>
|
||||
<template v-if="line.right && !isMatchLineRight">
|
||||
<template v-if="line.right && !line.isMatchLineRight">
|
||||
<td ref="newTd" :class="classNameMapCellRight" class="diff-line-num new_line">
|
||||
<span
|
||||
v-if="shouldRenderCommentButton"
|
||||
|
|
@ -283,12 +250,12 @@ export default {
|
|||
v-if="line.right.new_line"
|
||||
ref="lineNumberRefNew"
|
||||
:data-linenumber="line.right.new_line"
|
||||
:href="lineHrefNew"
|
||||
@click="setHighlightedRow(lineCode)"
|
||||
:href="line.lineHrefNew"
|
||||
@click="setHighlightedRow(line.lineCode)"
|
||||
>
|
||||
</a>
|
||||
<diff-gutter-avatars
|
||||
v-if="hasDiscussionsRight"
|
||||
v-if="line.hasDiscussionsRight"
|
||||
:discussions="line.right.discussions"
|
||||
:discussions-expanded="line.right.discussionsExpanded"
|
||||
@toggleLineDiscussions="
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
<script>
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
|
||||
import ParallelDraftCommentRow from '~/batch_comments/components/parallel_draft_comment_row.vue';
|
||||
import DraftNote from '~/batch_comments/components/draft_note.vue';
|
||||
import parallelDiffTableRow from './parallel_diff_table_row.vue';
|
||||
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
|
||||
import parallelDiffExpansionRow from './parallel_diff_expansion_row.vue';
|
||||
import DiffCommentCell from './diff_comment_cell.vue';
|
||||
import DiffExpansionCell from './diff_expansion_cell.vue';
|
||||
import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
parallelDiffExpansionRow,
|
||||
DiffExpansionCell,
|
||||
parallelDiffTableRow,
|
||||
parallelDiffCommentRow,
|
||||
ParallelDraftCommentRow,
|
||||
DiffCommentCell,
|
||||
DraftNote,
|
||||
},
|
||||
mixins: [draftCommentsMixin],
|
||||
props: {
|
||||
|
|
@ -66,14 +66,21 @@ export default {
|
|||
</colgroup>
|
||||
<tbody>
|
||||
<template v-for="(line, index) in diffLines">
|
||||
<parallel-diff-expansion-row
|
||||
<tr
|
||||
v-if="line.isMatchLineLeft || line.isMatchLineRight"
|
||||
:key="`expand-${index}`"
|
||||
:file-hash="diffFile.file_hash"
|
||||
:context-lines-path="diffFile.context_lines_path"
|
||||
:line="line"
|
||||
:is-top="index === 0"
|
||||
:is-bottom="index + 1 === diffLinesLength"
|
||||
/>
|
||||
class="line_expansion match"
|
||||
>
|
||||
<td colspan="6" class="text-center gl-font-regular">
|
||||
<diff-expansion-cell
|
||||
:file-hash="diffFile.file_hash"
|
||||
:context-lines-path="diffFile.context_lines_path"
|
||||
:line="line.left"
|
||||
:is-top="index === 0"
|
||||
:is-bottom="index + 1 === diffLinesLength"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<parallel-diff-table-row
|
||||
:key="line.line_code"
|
||||
:file-hash="diffFile.file_hash"
|
||||
|
|
@ -82,21 +89,53 @@ export default {
|
|||
:is-bottom="index + 1 === diffLinesLength"
|
||||
:is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
|
||||
/>
|
||||
<parallel-diff-comment-row
|
||||
<tr
|
||||
v-if="line.renderCommentRow"
|
||||
:key="`dcr-${line.line_code || index}`"
|
||||
:line="line"
|
||||
:diff-file-hash="diffFile.file_hash"
|
||||
:line-index="index"
|
||||
:help-page-path="helpPagePath"
|
||||
:has-draft-left="hasParallelDraftLeft(diffFile.file_hash, line) || false"
|
||||
:has-draft-right="hasParallelDraftRight(diffFile.file_hash, line) || false"
|
||||
/>
|
||||
<parallel-draft-comment-row
|
||||
:class="line.commentRowClasses"
|
||||
class="notes_holder"
|
||||
>
|
||||
<td class="notes-content parallel old" colspan="3">
|
||||
<diff-comment-cell
|
||||
v-if="line.left"
|
||||
:line="line.left"
|
||||
:diff-file-hash="diffFile.file_hash"
|
||||
:help-page-path="helpPagePath"
|
||||
:has-draft="line.left.hasDraft"
|
||||
line-position="left"
|
||||
/>
|
||||
</td>
|
||||
<td class="notes-content parallel new" colspan="3">
|
||||
<diff-comment-cell
|
||||
v-if="line.right"
|
||||
:line="line.right"
|
||||
:diff-file-hash="diffFile.file_hash"
|
||||
:line-index="index"
|
||||
:help-page-path="helpPagePath"
|
||||
:has-draft="line.right.hasDraft"
|
||||
line-position="right"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
|
||||
:key="`drafts-${index}`"
|
||||
:line="line"
|
||||
:diff-file-content-sha="diffFile.file_hash"
|
||||
/>
|
||||
:class="line.draftRowClasses"
|
||||
class="notes_holder"
|
||||
>
|
||||
<td class="notes_line old"></td>
|
||||
<td class="notes-content parallel old" colspan="2">
|
||||
<div v-if="line.left && line.left.lineDraft.isDraft" class="content">
|
||||
<draft-note :draft="line.left.lineDraft" :line="line.left" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="notes_line new"></td>
|
||||
<td class="notes-content parallel new" colspan="2">
|
||||
<div v-if="line.right && line.right.lineDraft.isDraft" class="content">
|
||||
<draft-note :draft="line.right.lineDraft" :line="line.right" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -165,8 +165,8 @@ export const fileLineCoverage = state => (file, line) => {
|
|||
export const currentDiffIndex = state =>
|
||||
Math.max(0, state.diffFiles.findIndex(diff => diff.file_hash === state.currentDiffFileId));
|
||||
|
||||
export const diffLines = state => file => {
|
||||
if (state.diffViewType === INLINE_DIFF_VIEW_TYPE) {
|
||||
export const diffLines = state => (file, unifiedDiffComponents) => {
|
||||
if (!unifiedDiffComponents && state.diffViewType === INLINE_DIFF_VIEW_TYPE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,13 +84,7 @@ export default class Issue {
|
|||
projectIssuesCounter.text(addDelimiter(numProjectIssues));
|
||||
|
||||
if (this.createMergeRequestDropdown) {
|
||||
if (isClosed) {
|
||||
this.createMergeRequestDropdown.unavailable();
|
||||
this.createMergeRequestDropdown.disable();
|
||||
} else {
|
||||
// We should check in case a branch was created in another tab
|
||||
this.createMergeRequestDropdown.checkAbilityToCreateBranch();
|
||||
}
|
||||
this.createMergeRequestDropdown.checkAbilityToCreateBranch();
|
||||
}
|
||||
} else {
|
||||
flash(issueFailMessage);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<script>
|
||||
import { GlLink, GlModal } from '@gitlab/ui';
|
||||
import { JOB_RETRY_FORWARD_DEPLOYMENT_MODAL } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'JobRetryForwardDeploymentModal',
|
||||
components: {
|
||||
GlLink,
|
||||
GlModal,
|
||||
},
|
||||
i18n: {
|
||||
...JOB_RETRY_FORWARD_DEPLOYMENT_MODAL,
|
||||
},
|
||||
props: {
|
||||
modalId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
retryOutdatedJobDocsUrl: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
primaryProps: {
|
||||
text: this.$options.i18n.primaryText,
|
||||
attributes: [
|
||||
{
|
||||
'data-method': 'post',
|
||||
'data-testid': 'retry-button-modal',
|
||||
href: this.href,
|
||||
variant: 'danger',
|
||||
},
|
||||
],
|
||||
},
|
||||
cancelProps: {
|
||||
text: this.$options.i18n.cancel,
|
||||
attributes: [{ category: 'secondary', variant: 'default' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-modal
|
||||
:action-cancel="cancelProps"
|
||||
:action-primary="primaryProps"
|
||||
:modal-id="modalId"
|
||||
:title="$options.i18n.title"
|
||||
>
|
||||
<p>
|
||||
{{ $options.i18n.info }}
|
||||
<gl-link v-if="retryOutdatedJobDocsUrl" :href="retryOutdatedJobDocsUrl" target="_blank">
|
||||
{{ $options.i18n.moreInfo }}
|
||||
</gl-link>
|
||||
</p>
|
||||
<p>{{ $options.i18n.areYouSure }}</p>
|
||||
</gl-modal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<script>
|
||||
import { GlButton, GlLink, GlModalDirective } from '@gitlab/ui';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { JOB_SIDEBAR } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'JobSidebarRetryButton',
|
||||
i18n: {
|
||||
retryLabel: JOB_SIDEBAR.retry,
|
||||
},
|
||||
components: {
|
||||
GlButton,
|
||||
GlLink,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
},
|
||||
props: {
|
||||
modalId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['hasForwardDeploymentFailure']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-button
|
||||
v-if="hasForwardDeploymentFailure"
|
||||
v-gl-modal="modalId"
|
||||
:aria-label="$options.i18n.retryLabel"
|
||||
category="primary"
|
||||
variant="info"
|
||||
>{{ $options.i18n.retryLabel }}</gl-button
|
||||
>
|
||||
<gl-link v-else :href="href" data-method="post" rel="nofollow"
|
||||
>{{ $options.i18n.retryLabel }}
|
||||
</gl-link>
|
||||
</template>
|
||||
|
|
@ -24,7 +24,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="js-jobs-container builds-container">
|
||||
<div class="builds-container">
|
||||
<job-container-item
|
||||
v-for="job in jobs"
|
||||
:key="job.id"
|
||||
|
|
|
|||
|
|
@ -1,28 +1,39 @@
|
|||
<script>
|
||||
import { isEmpty } from 'lodash';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { GlButton, GlIcon, GlLink } from '@gitlab/ui';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import ArtifactsBlock from './artifacts_block.vue';
|
||||
import JobSidebarRetryButton from './job_sidebar_retry_button.vue';
|
||||
import JobRetryForwardDeploymentModal from './job_retry_forward_deployment_modal.vue';
|
||||
import TriggerBlock from './trigger_block.vue';
|
||||
import CommitBlock from './commit_block.vue';
|
||||
import StagesDropdown from './stages_dropdown.vue';
|
||||
import JobsContainer from './jobs_container.vue';
|
||||
import SidebarJobDetailsContainer from './sidebar_job_details_container.vue';
|
||||
import JobSidebarDetailsContainer from './sidebar_job_details_container.vue';
|
||||
import { JOB_SIDEBAR } from '../constants';
|
||||
|
||||
export const forwardDeploymentFailureModalId = 'forward-deployment-failure';
|
||||
|
||||
export default {
|
||||
name: 'JobSidebar',
|
||||
i18n: {
|
||||
...JOB_SIDEBAR,
|
||||
},
|
||||
forwardDeploymentFailureModalId,
|
||||
components: {
|
||||
ArtifactsBlock,
|
||||
CommitBlock,
|
||||
GlIcon,
|
||||
TriggerBlock,
|
||||
StagesDropdown,
|
||||
JobsContainer,
|
||||
GlLink,
|
||||
GlButton,
|
||||
SidebarJobDetailsContainer,
|
||||
GlLink,
|
||||
GlIcon,
|
||||
JobsContainer,
|
||||
JobSidebarRetryButton,
|
||||
JobRetryForwardDeploymentModal,
|
||||
JobSidebarDetailsContainer,
|
||||
StagesDropdown,
|
||||
TooltipOnTruncate,
|
||||
TriggerBlock,
|
||||
},
|
||||
props: {
|
||||
artifactHelpUrl: {
|
||||
|
|
@ -37,9 +48,10 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['hasForwardDeploymentFailure']),
|
||||
...mapState(['job', 'stages', 'jobs', 'selectedStage']),
|
||||
retryButtonClass() {
|
||||
let className = 'js-retry-button btn btn-retry';
|
||||
let className = 'btn btn-retry';
|
||||
className +=
|
||||
this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
|
||||
return className;
|
||||
|
|
@ -56,6 +68,9 @@ export default {
|
|||
commit() {
|
||||
return this.job?.pipeline?.commit || {};
|
||||
},
|
||||
shouldShowJobRetryForwardDeploymentModal() {
|
||||
return this.job.retry_path && this.hasForwardDeploymentFailure;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchJobsForStage', 'toggleSidebar']),
|
||||
|
|
@ -73,27 +88,27 @@ export default {
|
|||
</h4>
|
||||
</tooltip-on-truncate>
|
||||
<div class="flex-grow-1 flex-shrink-0 text-right">
|
||||
<gl-link
|
||||
<job-sidebar-retry-button
|
||||
v-if="job.retry_path"
|
||||
:class="retryButtonClass"
|
||||
:href="job.retry_path"
|
||||
data-method="post"
|
||||
:modal-id="$options.forwardDeploymentFailureModalId"
|
||||
data-qa-selector="retry_button"
|
||||
rel="nofollow"
|
||||
>{{ __('Retry') }}
|
||||
</gl-link>
|
||||
data-testid="retry-button"
|
||||
/>
|
||||
<gl-link
|
||||
v-if="job.cancel_path"
|
||||
:href="job.cancel_path"
|
||||
class="js-cancel-job btn btn-default"
|
||||
class="btn btn-default"
|
||||
data-method="post"
|
||||
data-testid="cancel-button"
|
||||
rel="nofollow"
|
||||
>{{ __('Cancel') }}
|
||||
>{{ $options.i18n.cancel }}
|
||||
</gl-link>
|
||||
</div>
|
||||
|
||||
<gl-button
|
||||
:aria-label="__('Toggle Sidebar')"
|
||||
:aria-label="$options.i18n.toggleSidebar"
|
||||
category="tertiary"
|
||||
class="gl-display-md-none gl-ml-2 js-sidebar-build-toggle"
|
||||
icon="chevron-double-lg-right"
|
||||
|
|
@ -107,19 +122,20 @@ export default {
|
|||
:href="job.new_issue_path"
|
||||
class="btn btn-success btn-inverted float-left mr-2"
|
||||
data-testid="job-new-issue"
|
||||
>{{ __('New issue') }}
|
||||
>{{ $options.i18n.newIssue }}
|
||||
</gl-link>
|
||||
<gl-link
|
||||
v-if="job.terminal_path"
|
||||
:href="job.terminal_path"
|
||||
class="js-terminal-link btn btn-primary btn-inverted visible-md-block visible-lg-block float-left"
|
||||
class="btn btn-primary btn-inverted visible-md-block visible-lg-block float-left"
|
||||
target="_blank"
|
||||
data-testid="terminal-link"
|
||||
>
|
||||
{{ __('Debug') }}
|
||||
{{ $options.i18n.debug }}
|
||||
<gl-icon :size="14" name="external-link" />
|
||||
</gl-link>
|
||||
</div>
|
||||
<sidebar-job-details-container :runner-help-url="runnerHelpUrl" />
|
||||
<job-sidebar-details-container :runner-help-url="runnerHelpUrl" />
|
||||
<artifacts-block v-if="hasArtifact" :artifact="job.artifact" :help-url="artifactHelpUrl" />
|
||||
<trigger-block v-if="hasTriggers" :trigger="job.trigger" />
|
||||
<commit-block
|
||||
|
|
@ -139,5 +155,10 @@ export default {
|
|||
|
||||
<jobs-container v-if="jobs.length" :job-id="job.id" :jobs="jobs" />
|
||||
</div>
|
||||
<job-retry-forward-deployment-modal
|
||||
v-if="shouldShowJobRetryForwardDeploymentModal"
|
||||
:modal-id="$options.forwardDeploymentFailureModalId"
|
||||
:href="job.retry_path"
|
||||
/>
|
||||
</aside>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import timeagoMixin from '~/vue_shared/mixins/timeago';
|
|||
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
|
||||
|
||||
export default {
|
||||
name: 'SidebarJobDetailsContainer',
|
||||
name: 'JobSidebarDetailsContainer',
|
||||
components: {
|
||||
DetailRow,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
<script>
|
||||
import { isEmpty } from 'lodash';
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { GlLink, GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CiIcon,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlLink,
|
||||
},
|
||||
props: {
|
||||
|
|
@ -78,20 +80,15 @@ export default {
|
|||
</template>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
class="js-selected-stage dropdown-menu-toggle gl-mt-3"
|
||||
>
|
||||
{{ selectedStage }} <i class="fa fa-chevron-down"></i>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<li v-for="stage in stages" :key="stage.name">
|
||||
<button type="button" class="js-stage-item stage-item" @click="onStageClick(stage)">
|
||||
{{ stage.name }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<gl-dropdown :text="selectedStage" class="js-selected-stage gl-w-full gl-mt-3">
|
||||
<gl-dropdown-item
|
||||
v-for="stage in stages"
|
||||
:key="stage.name"
|
||||
class="js-stage-item stage-item"
|
||||
@click="onStageClick(stage)"
|
||||
>
|
||||
{{ stage.name }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { __, s__ } from '~/locale';
|
||||
|
||||
const cancel = __('Cancel');
|
||||
const moreInfo = __('More information');
|
||||
|
||||
export const JOB_SIDEBAR = {
|
||||
cancel,
|
||||
debug: __('Debug'),
|
||||
newIssue: __('New issue'),
|
||||
retry: __('Retry'),
|
||||
toggleSidebar: __('Toggle Sidebar'),
|
||||
};
|
||||
|
||||
export const JOB_RETRY_FORWARD_DEPLOYMENT_MODAL = {
|
||||
cancel,
|
||||
info: s__(
|
||||
`Jobs|You're about to retry a job that failed because it attempted to deploy code that is older than the latest deployment.
|
||||
Retrying this job could result in overwriting the environment with the older source code.`,
|
||||
),
|
||||
areYouSure: s__('Jobs|Are you sure you want to proceed?'),
|
||||
moreInfo,
|
||||
primaryText: __('Retry job'),
|
||||
title: s__('Jobs|Are you sure you want to retry this job?'),
|
||||
};
|
||||
|
|
@ -10,27 +10,31 @@ export default () => {
|
|||
// Let's start initializing the store (i.e. fetching data) right away
|
||||
store.dispatch('init', element.dataset);
|
||||
|
||||
const {
|
||||
artifactHelpUrl,
|
||||
deploymentHelpUrl,
|
||||
runnerHelpUrl,
|
||||
runnerSettingsUrl,
|
||||
variablesSettingsUrl,
|
||||
subscriptionsMoreMinutesUrl,
|
||||
endpoint,
|
||||
pagePath,
|
||||
logState,
|
||||
buildStatus,
|
||||
projectPath,
|
||||
retryOutdatedJobDocsUrl,
|
||||
} = element.dataset;
|
||||
|
||||
return new Vue({
|
||||
el: element,
|
||||
store,
|
||||
components: {
|
||||
JobApp,
|
||||
},
|
||||
provide: {
|
||||
retryOutdatedJobDocsUrl,
|
||||
},
|
||||
render(createElement) {
|
||||
const {
|
||||
artifactHelpUrl,
|
||||
deploymentHelpUrl,
|
||||
runnerHelpUrl,
|
||||
runnerSettingsUrl,
|
||||
variablesSettingsUrl,
|
||||
subscriptionsMoreMinutesUrl,
|
||||
endpoint,
|
||||
pagePath,
|
||||
logState,
|
||||
buildStatus,
|
||||
projectPath,
|
||||
} = element.dataset;
|
||||
|
||||
return createElement('job-app', {
|
||||
props: {
|
||||
artifactHelpUrl,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
|
|||
|
||||
export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
|
||||
|
||||
export const hasForwardDeploymentFailure = state =>
|
||||
state?.job?.failure_reason === 'forward_deployment_failure';
|
||||
|
||||
export const hasUnmetPrerequisitesFailure = state =>
|
||||
state?.job?.failure_reason === 'unmet_prerequisites';
|
||||
|
||||
|
|
|
|||
|
|
@ -740,6 +740,24 @@ export const roundOffFloat = (number, precision = 0) => {
|
|||
return Math.round(number * multiplier) / multiplier;
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to round down values with decimal places
|
||||
* with provided precision.
|
||||
*
|
||||
* Eg; roundDownFloat(3.141592, 3) = 3.141
|
||||
*
|
||||
* Refer to spec/javascripts/lib/utils/common_utils_spec.js for
|
||||
* more supported examples.
|
||||
*
|
||||
* @param {Float} number
|
||||
* @param {Number} precision
|
||||
*/
|
||||
export const roundDownFloat = (number, precision = 0) => {
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
const multiplier = Math.pow(10, precision);
|
||||
return Math.floor(number * multiplier) / multiplier;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents navigation type constants of the Performance Navigation API.
|
||||
* Detailed explanation see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigation.
|
||||
|
|
|
|||
|
|
@ -37,15 +37,6 @@ export default {
|
|||
ROW_SCHEDULED_FOR_DELETION,
|
||||
},
|
||||
computed: {
|
||||
encodedItem() {
|
||||
const params = JSON.stringify({
|
||||
name: this.item.path,
|
||||
tags_path: this.item.tags_path,
|
||||
id: this.item.id,
|
||||
cleanup_policy_started_at: this.item.cleanup_policy_started_at,
|
||||
});
|
||||
return window.btoa(params);
|
||||
},
|
||||
disabledDelete() {
|
||||
return !this.item.destroy_path || this.item.deleting;
|
||||
},
|
||||
|
|
@ -82,7 +73,7 @@ export default {
|
|||
<router-link
|
||||
class="gl-text-body gl-font-weight-bold"
|
||||
data-testid="detailsLink"
|
||||
:to="{ name: 'details', params: { id: encodedItem } }"
|
||||
:to="{ name: 'details', params: { id: item.id } }"
|
||||
>
|
||||
{{ item.path }}
|
||||
</router-link>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default {
|
|||
return {
|
||||
tagName,
|
||||
className,
|
||||
text: this.$route.meta.nameGenerator(this.$route),
|
||||
text: this.$route.meta.nameGenerator(this.$store.state),
|
||||
path: { to: this.$route.name },
|
||||
};
|
||||
},
|
||||
|
|
@ -48,7 +48,7 @@ export default {
|
|||
></li>
|
||||
<li v-if="!isRootRoute">
|
||||
<router-link ref="rootRouteLink" :to="rootRoute.path">
|
||||
{{ rootRoute.meta.nameGenerator(rootRoute) }}
|
||||
{{ rootRoute.meta.nameGenerator($store.state) }}
|
||||
</router-link>
|
||||
<component :is="divider.tagName" v-safe-html="divider.innerHTML" :class="divider.classList" />
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import TagsList from '../components/details_page/tags_list.vue';
|
|||
import TagsLoader from '../components/details_page/tags_loader.vue';
|
||||
import EmptyTagsState from '../components/details_page/empty_tags_state.vue';
|
||||
|
||||
import { decodeAndParse } from '../utils';
|
||||
import {
|
||||
ALERT_SUCCESS_TAG,
|
||||
ALERT_DANGER_TAG,
|
||||
|
|
@ -43,12 +42,9 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['tagsPagination', 'isLoading', 'config', 'tags']),
|
||||
queryParameters() {
|
||||
return decodeAndParse(this.$route.params.id);
|
||||
},
|
||||
...mapState(['tagsPagination', 'isLoading', 'config', 'tags', 'imageDetails']),
|
||||
showPartialCleanupWarning() {
|
||||
return this.queryParameters.cleanup_policy_started_at && !this.dismissPartialCleanupWarning;
|
||||
return this.imageDetails?.cleanup_policy_started_at && !this.dismissPartialCleanupWarning;
|
||||
},
|
||||
tracking() {
|
||||
return {
|
||||
|
|
@ -61,15 +57,20 @@ export default {
|
|||
return this.tagsPagination.page;
|
||||
},
|
||||
set(page) {
|
||||
this.requestTagsList({ pagination: { page }, params: this.$route.params.id });
|
||||
this.requestTagsList({ page });
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.requestTagsList({ params: this.$route.params.id });
|
||||
this.requestImageDetailsAndTagsList(this.$route.params.id);
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['requestTagsList', 'requestDeleteTag', 'requestDeleteTags']),
|
||||
...mapActions([
|
||||
'requestTagsList',
|
||||
'requestDeleteTag',
|
||||
'requestDeleteTags',
|
||||
'requestImageDetailsAndTagsList',
|
||||
]),
|
||||
deleteTags(toBeDeleted) {
|
||||
this.itemsToBeDeleted = this.tags.filter(tag => toBeDeleted[tag.name]);
|
||||
this.track('click_button');
|
||||
|
|
@ -78,7 +79,7 @@ export default {
|
|||
handleSingleDelete() {
|
||||
const [itemToDelete] = this.itemsToBeDeleted;
|
||||
this.itemsToBeDeleted = [];
|
||||
return this.requestDeleteTag({ tag: itemToDelete, params: this.$route.params.id })
|
||||
return this.requestDeleteTag({ tag: itemToDelete })
|
||||
.then(() => {
|
||||
this.deleteAlertType = ALERT_SUCCESS_TAG;
|
||||
})
|
||||
|
|
@ -92,7 +93,6 @@ export default {
|
|||
|
||||
return this.requestDeleteTags({
|
||||
ids: itemsToBeDeleted.map(x => x.name),
|
||||
params: this.$route.params.id,
|
||||
})
|
||||
.then(() => {
|
||||
this.deleteAlertType = ALERT_SUCCESS_TAGS;
|
||||
|
|
@ -132,7 +132,7 @@ export default {
|
|||
@dismiss="dismissPartialCleanupWarning = true"
|
||||
/>
|
||||
|
||||
<details-header :image-name="queryParameters.name" />
|
||||
<details-header :image-name="imageDetails.name" />
|
||||
|
||||
<tags-loader v-if="isLoading" />
|
||||
<template v-else>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import Vue from 'vue';
|
|||
import VueRouter from 'vue-router';
|
||||
import List from './pages/list.vue';
|
||||
import Details from './pages/details.vue';
|
||||
import { decodeAndParse } from './utils';
|
||||
import { CONTAINER_REGISTRY_TITLE } from './constants/index';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
|
@ -26,7 +25,7 @@ export default function createRouter(base) {
|
|||
path: '/:id',
|
||||
component: Details,
|
||||
meta: {
|
||||
nameGenerator: route => decodeAndParse(route.params.id).name,
|
||||
nameGenerator: ({ imageDetails }) => imageDetails?.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
FETCH_TAGS_LIST_ERROR_MESSAGE,
|
||||
FETCH_IMAGE_DETAILS_ERROR_MESSAGE,
|
||||
} from '../constants/index';
|
||||
import { decodeAndParse } from '../utils';
|
||||
import { pathGenerator } from '../utils';
|
||||
|
||||
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
|
||||
export const setShowGarbageCollectionTip = ({ commit }, data) =>
|
||||
|
|
@ -45,13 +45,13 @@ export const requestImagesList = (
|
|||
});
|
||||
};
|
||||
|
||||
export const requestTagsList = ({ commit, dispatch }, { pagination = {}, params }) => {
|
||||
export const requestTagsList = ({ commit, dispatch, state: { imageDetails } }, pagination = {}) => {
|
||||
commit(types.SET_MAIN_LOADING, true);
|
||||
const { tags_path } = decodeAndParse(params);
|
||||
const tagsPath = pathGenerator(imageDetails);
|
||||
|
||||
const { page = DEFAULT_PAGE, perPage = DEFAULT_PAGE_SIZE } = pagination;
|
||||
return axios
|
||||
.get(tags_path, { params: { page, per_page: perPage } })
|
||||
.get(tagsPath, { params: { page, per_page: perPage } })
|
||||
.then(({ data, headers }) => {
|
||||
dispatch('receiveTagsListSuccess', { data, headers });
|
||||
})
|
||||
|
|
@ -76,30 +76,30 @@ export const requestImageDetailsAndTagsList = ({ dispatch, commit }, id) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) => {
|
||||
export const requestDeleteTag = ({ commit, dispatch, state }, { tag }) => {
|
||||
commit(types.SET_MAIN_LOADING, true);
|
||||
return axios
|
||||
.delete(tag.destroy_path)
|
||||
.then(() => {
|
||||
dispatch('setShowGarbageCollectionTip', true);
|
||||
return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
|
||||
|
||||
return dispatch('requestTagsList', state.tagsPagination);
|
||||
})
|
||||
.finally(() => {
|
||||
commit(types.SET_MAIN_LOADING, false);
|
||||
});
|
||||
};
|
||||
|
||||
export const requestDeleteTags = ({ commit, dispatch, state }, { ids, params }) => {
|
||||
export const requestDeleteTags = ({ commit, dispatch, state }, { ids }) => {
|
||||
commit(types.SET_MAIN_LOADING, true);
|
||||
const { tags_path } = decodeAndParse(params);
|
||||
|
||||
const url = tags_path.replace('?format=json', '/bulk_destroy');
|
||||
const tagsPath = pathGenerator(state.imageDetails, '/bulk_destroy');
|
||||
|
||||
return axios
|
||||
.delete(url, { params: { ids } })
|
||||
.delete(tagsPath, { params: { ids } })
|
||||
.then(() => {
|
||||
dispatch('setShowGarbageCollectionTip', true);
|
||||
return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
|
||||
return dispatch('requestTagsList', state.tagsPagination);
|
||||
})
|
||||
.finally(() => {
|
||||
commit(types.SET_MAIN_LOADING, false);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
export const decodeAndParse = param => JSON.parse(window.atob(param));
|
||||
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
export const pathGenerator = (imageDetails, ending = 'tags?format=json') => {
|
||||
export const pathGenerator = (imageDetails, ending = '?format=json') => {
|
||||
// this method is a temporary workaround, to be removed with graphql implementation
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/276432
|
||||
const basePath = imageDetails.path.replace(`/${imageDetails.name}`, '');
|
||||
return `/${basePath}/registry/repository/${imageDetails.id}/${ending}`;
|
||||
return `/${basePath}/registry/repository/${imageDetails.id}/tags${ending}`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import { roundOffFloat } from '~/lib/utils/common_utils';
|
||||
import { roundDownFloat } from '~/lib/utils/common_utils';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
|
|
@ -89,7 +89,7 @@ export default {
|
|||
return 0;
|
||||
}
|
||||
|
||||
const percent = roundOffFloat((count / this.totalCount) * 100, 1);
|
||||
const percent = roundDownFloat((count / this.totalCount) * 100, 1);
|
||||
if (percent > 0 && percent < 1) {
|
||||
return '< 1';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.diff-table.code,
|
||||
table.code {
|
||||
width: 100%;
|
||||
font-family: $monospace-font;
|
||||
|
|
@ -459,10 +460,12 @@ table.code {
|
|||
table-layout: fixed;
|
||||
border-radius: 0 0 $border-radius-default $border-radius-default;
|
||||
|
||||
.diff-tr:first-of-type.line_expansion > .diff-td,
|
||||
tr:first-of-type.line_expansion > td {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.diff-tr:nth-last-of-type(2).line_expansion > .diff-td,
|
||||
tr:nth-last-of-type(2).line_expansion,
|
||||
tr:last-of-type.line_expansion {
|
||||
> td {
|
||||
|
|
@ -470,6 +473,7 @@ table.code {
|
|||
}
|
||||
}
|
||||
|
||||
.diff-tr.line_holder .diff-td,
|
||||
tr.line_holder td {
|
||||
line-height: $code-line-height;
|
||||
font-size: $code-font-size;
|
||||
|
|
@ -565,24 +569,95 @@ table.code {
|
|||
}
|
||||
|
||||
.line_holder:last-of-type {
|
||||
.diff-td:first-child,
|
||||
td:first-child {
|
||||
border-bottom-left-radius: $border-radius-default;
|
||||
}
|
||||
}
|
||||
|
||||
&.left-side-selected {
|
||||
.diff-td.line_content.parallel.right-side,
|
||||
td.line_content.parallel.right-side {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.right-side-selected {
|
||||
.diff-td.line_content.parallel.left-side,
|
||||
td.line_content.parallel.left-side {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge request diff grid layout
|
||||
.diff-grid {
|
||||
.diff-grid-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.diff-grid-left,
|
||||
.diff-grid-right {
|
||||
display: grid;
|
||||
grid-template-columns: 50px 8px 1fr;
|
||||
|
||||
.diff-td:nth-child(2) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.diff-grid-comments {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.diff-grid-drafts {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
&.inline {
|
||||
.diff-grid-comments {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.diff-grid-drafts {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.diff-grid-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.diff-grid-left,
|
||||
.diff-grid-right {
|
||||
grid-template-columns: 50px 50px 8px 1fr;
|
||||
|
||||
.diff-td:nth-child(2) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.diff-grid-left .old:nth-child(2) [data-linenumber],
|
||||
.diff-grid-right .new:nth-child(2) [data-linenumber] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.diff-grid-left .old:nth-child(3) [data-linenumber],
|
||||
.diff-grid-right .new:nth-child(1) [data-linenumber] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge request diff grid layout overrides
|
||||
.diff-table.code .diff-tr.line_holder .diff-td.line_content.parallel {
|
||||
width: unset;
|
||||
}
|
||||
|
||||
.diff-stats {
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
@mixin diff-expansion($background, $border, $link) {
|
||||
background-color: $background;
|
||||
|
||||
.diff-td,
|
||||
td {
|
||||
border-top: 1px solid $border;
|
||||
border-bottom: 1px solid $border;
|
||||
|
|
@ -41,3 +42,12 @@
|
|||
border-left: 3px solid $no-coverage;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin line-number-hover($color) {
|
||||
background-color: $color;
|
||||
border-color: darken($color, 5%);
|
||||
|
||||
a {
|
||||
color: darken($color, 15%);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,9 @@ $dark-il: #de935f;
|
|||
@include dark-diff-match-line;
|
||||
}
|
||||
|
||||
.diff-td.diff-line-num.hll:not(.empty-cell),
|
||||
.diff-td.line-coverage.hll:not(.empty-cell),
|
||||
.diff-td.line_content.hll:not(.empty-cell),
|
||||
td.diff-line-num.hll:not(.empty-cell),
|
||||
td.line-coverage.hll:not(.empty-cell),
|
||||
td.line_content.hll:not(.empty-cell) {
|
||||
|
|
@ -158,15 +161,17 @@ $dark-il: #de935f;
|
|||
}
|
||||
}
|
||||
|
||||
.diff-grid-left:hover,
|
||||
.diff-grid-right:hover {
|
||||
.diff-line-num:not(.empty-cell) {
|
||||
@include line-number-hover($dark-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.diff-line-num {
|
||||
&.is-over,
|
||||
&.hll:not(.empty-cell).is-over {
|
||||
background-color: $dark-over-bg;
|
||||
border-color: darken($dark-over-bg, 5%);
|
||||
|
||||
a {
|
||||
color: darken($dark-over-bg, 15%);
|
||||
}
|
||||
@include line-number-hover($dark-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -125,6 +125,9 @@ $monokai-gi: #a6e22e;
|
|||
@include dark-diff-match-line;
|
||||
}
|
||||
|
||||
.diff-td.diff-line-num.hll:not(.empty-cell),
|
||||
.diff-td.line-coverage.hll:not(.empty-cell),
|
||||
.diff-td.line_content.hll:not(.empty-cell),
|
||||
td.diff-line-num.hll:not(.empty-cell),
|
||||
td.line-coverage.hll:not(.empty-cell),
|
||||
td.line_content.hll:not(.empty-cell) {
|
||||
|
|
@ -158,15 +161,17 @@ $monokai-gi: #a6e22e;
|
|||
}
|
||||
}
|
||||
|
||||
.diff-grid-left:hover,
|
||||
.diff-grid-right:hover {
|
||||
.diff-line-num:not(.empty-cell) {
|
||||
@include line-number-hover($monokai-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.diff-line-num {
|
||||
&.is-over,
|
||||
&.hll:not(.empty-cell).is-over {
|
||||
background-color: $monokai-over-bg;
|
||||
border-color: darken($monokai-over-bg, 5%);
|
||||
|
||||
a {
|
||||
color: darken($monokai-over-bg, 15%);
|
||||
}
|
||||
@include line-number-hover($monokai-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.diff-grid-left:hover,
|
||||
.diff-grid-right:hover {
|
||||
.diff-line-num:not(.empty-cell) {
|
||||
@include line-number-hover($none-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.diff-line-num {
|
||||
&.old {
|
||||
a {
|
||||
|
|
@ -74,12 +81,7 @@
|
|||
|
||||
&.is-over,
|
||||
&.hll:not(.empty-cell).is-over {
|
||||
background-color: $none-over-bg;
|
||||
border-color: darken($none-over-bg, 5%);
|
||||
|
||||
a {
|
||||
color: darken($none-over-bg, 15%);
|
||||
}
|
||||
@include line-number-hover($none-over-bg);
|
||||
}
|
||||
|
||||
&.hll:not(.empty-cell) {
|
||||
|
|
|
|||
|
|
@ -129,6 +129,9 @@ $solarized-dark-il: #2aa198;
|
|||
@include dark-diff-match-line;
|
||||
}
|
||||
|
||||
.diff-td.diff-line-num.hll:not(.empty-cell),
|
||||
.diff-td.line-coverage.hll:not(.empty-cell),
|
||||
.diff-td.line_content.hll:not(.empty-cell),
|
||||
td.diff-line-num.hll:not(.empty-cell),
|
||||
td.line-coverage.hll:not(.empty-cell),
|
||||
td.line_content.hll:not(.empty-cell) {
|
||||
|
|
@ -140,6 +143,13 @@ $solarized-dark-il: #2aa198;
|
|||
@include line-coverage-border-color($solarized-dark-coverage, $solarized-dark-no-coverage);
|
||||
}
|
||||
|
||||
.diff-grid-left:hover,
|
||||
.diff-grid-right:hover {
|
||||
.diff-line-num:not(.empty-cell) {
|
||||
@include line-number-hover($solarized-dark-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.diff-line-num.new,
|
||||
.line-coverage.new,
|
||||
.line_content.new {
|
||||
|
|
@ -165,12 +175,7 @@ $solarized-dark-il: #2aa198;
|
|||
.diff-line-num {
|
||||
&.is-over,
|
||||
&.hll:not(.empty-cell).is-over {
|
||||
background-color: $solarized-dark-over-bg;
|
||||
border-color: darken($solarized-dark-over-bg, 5%);
|
||||
|
||||
a {
|
||||
color: darken($solarized-dark-over-bg, 15%);
|
||||
}
|
||||
@include line-number-hover($solarized-dark-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,9 @@ $solarized-light-il: #2aa198;
|
|||
@include match-line;
|
||||
}
|
||||
|
||||
.diff-td.diff-line-num.hll:not(.empty-cell),
|
||||
.diff-td.line-coverage.hll:not(.empty-cell),
|
||||
.diff-td.line_content.hll:not(.empty-cell),
|
||||
td.diff-line-num.hll:not(.empty-cell),
|
||||
td.line-coverage.hll:not(.empty-cell),
|
||||
td.line_content.hll:not(.empty-cell) {
|
||||
|
|
@ -159,6 +162,13 @@ $solarized-light-il: #2aa198;
|
|||
}
|
||||
}
|
||||
|
||||
.diff-grid-left:hover,
|
||||
.diff-grid-right:hover {
|
||||
.diff-line-num:not(.empty-cell) {
|
||||
@include line-number-hover($solarized-light-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.diff-line-num.old,
|
||||
.line-coverage.old,
|
||||
.line_content.old {
|
||||
|
|
@ -173,12 +183,7 @@ $solarized-light-il: #2aa198;
|
|||
.diff-line-num {
|
||||
&.is-over,
|
||||
&.hll:not(.empty-cell).is-over {
|
||||
background-color: $solarized-light-over-bg;
|
||||
border-color: darken($solarized-light-over-bg, 5%);
|
||||
|
||||
a {
|
||||
color: darken($solarized-light-over-bg, 15%);
|
||||
}
|
||||
@include line-number-hover($solarized-light-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,13 @@ pre.code,
|
|||
@include match-line;
|
||||
}
|
||||
|
||||
.diff-grid-left:hover,
|
||||
.diff-grid-right:hover {
|
||||
.diff-line-num:not(.empty-cell) {
|
||||
@include line-number-hover($white-over-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.diff-line-num {
|
||||
&.old {
|
||||
background-color: $line-number-old;
|
||||
|
|
@ -134,12 +141,7 @@ pre.code,
|
|||
|
||||
&.is-over,
|
||||
&.hll:not(.empty-cell).is-over {
|
||||
background-color: $white-over-bg;
|
||||
border-color: darken($white-over-bg, 5%);
|
||||
|
||||
a {
|
||||
color: darken($white-over-bg, 15%);
|
||||
}
|
||||
@include line-number-hover($white-over-bg);
|
||||
}
|
||||
|
||||
&.hll:not(.empty-cell) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
* Note Form
|
||||
*/
|
||||
.diff-file .diff-content {
|
||||
.diff-tr.line_holder:hover > .diff-td .line_note_link,
|
||||
tr.line_holder:hover > td .line_note_link {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity = 100);
|
||||
|
|
|
|||
|
|
@ -453,6 +453,8 @@ $note-form-margin-left: 72px;
|
|||
}
|
||||
|
||||
.diff-file {
|
||||
.diff-grid-left:hover,
|
||||
.diff-grid-right:hover,
|
||||
.is-over {
|
||||
.add-diff-note {
|
||||
display: inline-flex;
|
||||
|
|
@ -490,6 +492,7 @@ $note-form-margin-left: 72px;
|
|||
.notes_holder {
|
||||
font-family: $regular-font;
|
||||
|
||||
.diff-td,
|
||||
td {
|
||||
border: 1px solid $border-color;
|
||||
border-left: 0;
|
||||
|
|
@ -805,6 +808,8 @@ $note-form-margin-left: 72px;
|
|||
* Line note button on the side of diffs
|
||||
*/
|
||||
|
||||
.diff-grid-left:hover,
|
||||
.diff-grid-right:hover,
|
||||
.line_holder .is-over:not(.no-comment-btn) {
|
||||
.add-diff-note {
|
||||
opacity: 1;
|
||||
|
|
|
|||
|
|
@ -218,8 +218,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
|
||||
# TODO Remove domain_denylist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-foss/issues/67204)
|
||||
params.delete(:domain_denylist_raw) if params[:domain_denylist_file]
|
||||
params.delete(:domain_denylist_raw) if params[:domain_blacklist]
|
||||
params.delete(:domain_allowlist_raw) if params[:domain_whitelist]
|
||||
params.delete(:domain_denylist_raw) if params[:domain_denylist]
|
||||
params.delete(:domain_allowlist_raw) if params[:domain_allowlist]
|
||||
|
||||
params.require(:application_setting).permit(
|
||||
visible_application_setting_attributes
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true)
|
||||
push_frontend_feature_flag(:merge_request_widget_graphql, @project)
|
||||
push_frontend_feature_flag(:unified_diff_lines, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:unified_diff_components, @project)
|
||||
push_frontend_feature_flag(:highlight_current_diff_row, @project)
|
||||
push_frontend_feature_flag(:default_merge_ref_for_diffs, @project)
|
||||
push_frontend_feature_flag(:core_security_mr_widget, @project, default_enabled: true)
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ module Mutations
|
|||
super
|
||||
end
|
||||
|
||||
def resolve(args)
|
||||
def resolve(queue_name:, **args)
|
||||
{
|
||||
result: Gitlab::SidekiqQueue.new(args[:queue_name]).drop_jobs!(args, timeout: 30),
|
||||
result: Gitlab::SidekiqQueue.new(queue_name).drop_jobs!(args, timeout: 30),
|
||||
errors: []
|
||||
}
|
||||
rescue Gitlab::SidekiqQueue::NoMetadataError
|
||||
|
|
@ -44,7 +44,7 @@ module Mutations
|
|||
errors: ['No metadata provided']
|
||||
}
|
||||
rescue Gitlab::SidekiqQueue::InvalidQueueError
|
||||
raise Gitlab::Graphql::Errors::ResourceNotAvailable, "Queue #{args[:queue_name]} not found"
|
||||
raise Gitlab::Graphql::Errors::ResourceNotAvailable, "Queue #{queue_name} not found"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ module Mutations
|
|||
module AlertManagement
|
||||
class Base < BaseMutation
|
||||
include Gitlab::Utils::UsageData
|
||||
include ResolvesProject
|
||||
|
||||
argument :project_path, GraphQL::ID_TYPE,
|
||||
required: true,
|
||||
|
|
@ -33,13 +32,12 @@ module Mutations
|
|||
|
||||
private
|
||||
|
||||
def find_object(project_path:, iid:)
|
||||
project = resolve_project(full_path: project_path)
|
||||
def find_object(project_path:, **args)
|
||||
project = Project.find_by_full_path(project_path)
|
||||
|
||||
return unless project
|
||||
|
||||
resolver = Resolvers::AlertManagement::AlertResolver.single.new(object: project, context: context, field: nil)
|
||||
resolver.resolve(iid: iid)
|
||||
::AlertManagement::AlertsFinder.new(current_user, project, args).execute.first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ module Mutations
|
|||
required: true,
|
||||
description: 'The status to set the alert'
|
||||
|
||||
def resolve(args)
|
||||
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
|
||||
result = update_status(alert, args[:status])
|
||||
def resolve(project_path:, iid:, status:)
|
||||
alert = authorized_find!(project_path: project_path, iid: iid)
|
||||
result = update_status(alert, status)
|
||||
|
||||
track_usage_event(:incident_management_alert_status_changed, current_user.id)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,16 +11,6 @@ module Mutations
|
|||
id = ::Types::GlobalIDType[::Todo].coerce_isolated_input(id)
|
||||
GitlabSchema.find_by_gid(id)
|
||||
end
|
||||
|
||||
def map_to_global_ids(ids)
|
||||
return [] if ids.blank?
|
||||
|
||||
ids.map { |id| to_global_id(id) }
|
||||
end
|
||||
|
||||
def to_global_id(id)
|
||||
Gitlab::GlobalId.as_global_id(id, model_name: Todo.name).to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module Mutations
|
|||
authorize :update_user
|
||||
|
||||
field :updated_ids,
|
||||
[GraphQL::ID_TYPE],
|
||||
[::Types::GlobalIDType[::Todo]],
|
||||
null: false,
|
||||
deprecated: { reason: 'Use todos', milestone: '13.2' },
|
||||
description: 'Ids of the updated todos'
|
||||
|
|
@ -23,7 +23,7 @@ module Mutations
|
|||
updated_ids = mark_all_todos_done
|
||||
|
||||
{
|
||||
updated_ids: map_to_global_ids(updated_ids),
|
||||
updated_ids: updated_ids,
|
||||
todos: Todo.id_in(updated_ids),
|
||||
errors: []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module Mutations
|
|||
required: true,
|
||||
description: 'The global ids of the todos to restore (a maximum of 50 is supported at once)'
|
||||
|
||||
field :updated_ids, [GraphQL::ID_TYPE],
|
||||
field :updated_ids, [::Types::GlobalIDType[Todo]],
|
||||
null: false,
|
||||
description: 'The ids of the updated todo items',
|
||||
deprecated: { reason: 'Use todos', milestone: '13.2' }
|
||||
|
|
@ -28,7 +28,7 @@ module Mutations
|
|||
updated_ids = restore(todos)
|
||||
|
||||
{
|
||||
updated_ids: gids_of(updated_ids),
|
||||
updated_ids: updated_ids,
|
||||
todos: Todo.id_in(updated_ids),
|
||||
errors: errors_on_objects(todos)
|
||||
}
|
||||
|
|
@ -36,10 +36,6 @@ module Mutations
|
|||
|
||||
private
|
||||
|
||||
def gids_of(ids)
|
||||
ids.map { |id| Gitlab::GlobalId.as_global_id(id, model_name: Todo.name).to_s }
|
||||
end
|
||||
|
||||
def model_ids_of(ids)
|
||||
ids.map do |gid|
|
||||
# TODO: remove this line when the compatibility layer is removed
|
||||
|
|
|
|||
|
|
@ -199,11 +199,11 @@ module ApplicationSettingsHelper
|
|||
:default_projects_limit,
|
||||
:default_snippet_visibility,
|
||||
:disabled_oauth_sign_in_sources,
|
||||
:domain_blacklist,
|
||||
:domain_blacklist_enabled,
|
||||
:domain_denylist,
|
||||
:domain_denylist_enabled,
|
||||
# TODO Remove domain_denylist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-foss/issues/67204)
|
||||
:domain_denylist_raw,
|
||||
:domain_whitelist,
|
||||
:domain_allowlist,
|
||||
# TODO Remove domain_allowlist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-foss/issues/67204)
|
||||
:domain_allowlist_raw,
|
||||
:outbound_local_requests_allowlist_raw,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ module Ci
|
|||
"build_status" => @build.status,
|
||||
"build_stage" => @build.stage,
|
||||
"log_state" => '',
|
||||
"build_options" => javascript_build_options
|
||||
"build_options" => javascript_build_options,
|
||||
"retry_outdated_job_docs_url" => help_page_path('ci/pipelines/settings', anchor: 'retry-outdated-jobs')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ class ApplicationSetting < ApplicationRecord
|
|||
serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :domain_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :domain_denylist, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
|
|
@ -184,9 +184,9 @@ class ApplicationSetting < ApplicationRecord
|
|||
validates :enabled_git_access_protocol,
|
||||
inclusion: { in: %w(ssh http), allow_blank: true }
|
||||
|
||||
validates :domain_blacklist,
|
||||
presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' },
|
||||
if: :domain_blacklist_enabled?
|
||||
validates :domain_denylist,
|
||||
presence: { message: 'Domain denylist cannot be empty if denylist is enabled.' },
|
||||
if: :domain_denylist_enabled?
|
||||
|
||||
validates :housekeeping_incremental_repack_period,
|
||||
presence: true,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ module ApplicationSettingImplementation
|
|||
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
|
||||
disabled_oauth_sign_in_sources: [],
|
||||
dns_rebinding_protection_enabled: true,
|
||||
domain_whitelist: Settings.gitlab['domain_whitelist'],
|
||||
domain_allowlist: Settings.gitlab['domain_allowlist'],
|
||||
dsa_key_restriction: 0,
|
||||
ecdsa_key_restriction: 0,
|
||||
ed25519_key_restriction: 0,
|
||||
|
|
@ -203,19 +203,19 @@ module ApplicationSettingImplementation
|
|||
end
|
||||
|
||||
def domain_allowlist_raw
|
||||
array_to_string(self.domain_whitelist)
|
||||
array_to_string(self.domain_allowlist)
|
||||
end
|
||||
|
||||
def domain_denylist_raw
|
||||
array_to_string(self.domain_blacklist)
|
||||
array_to_string(self.domain_denylist)
|
||||
end
|
||||
|
||||
def domain_allowlist_raw=(values)
|
||||
self.domain_whitelist = strings_to_array(values)
|
||||
self.domain_allowlist = strings_to_array(values)
|
||||
end
|
||||
|
||||
def domain_denylist_raw=(values)
|
||||
self.domain_blacklist = strings_to_array(values)
|
||||
self.domain_denylist = strings_to_array(values)
|
||||
end
|
||||
|
||||
def domain_denylist_file=(file)
|
||||
|
|
@ -242,7 +242,7 @@ module ApplicationSettingImplementation
|
|||
end
|
||||
|
||||
# This method separates out the strings stored in the
|
||||
# application_setting.outbound_local_requests_allowlist array into 2 arrays;
|
||||
# application_setting.outbound_local_requests_whitelist array into 2 arrays;
|
||||
# an array of IPAddr objects (`[IPAddr.new('127.0.0.1')]`), and an array of
|
||||
# domain strings (`['www.example.com']`).
|
||||
def outbound_local_requests_allowlist_arrays
|
||||
|
|
|
|||
|
|
@ -1843,15 +1843,15 @@ class User < ApplicationRecord
|
|||
valid = true
|
||||
error = nil
|
||||
|
||||
if Gitlab::CurrentSettings.domain_blacklist_enabled?
|
||||
blocked_domains = Gitlab::CurrentSettings.domain_blacklist
|
||||
if Gitlab::CurrentSettings.domain_denylist_enabled?
|
||||
blocked_domains = Gitlab::CurrentSettings.domain_denylist
|
||||
if domain_matches?(blocked_domains, email)
|
||||
error = 'is not from an allowed domain.'
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
allowed_domains = Gitlab::CurrentSettings.domain_whitelist
|
||||
allowed_domains = Gitlab::CurrentSettings.domain_allowlist
|
||||
unless allowed_domains.blank?
|
||||
if domain_matches?(allowed_domains, email)
|
||||
valid = true
|
||||
|
|
|
|||
|
|
@ -31,14 +31,14 @@
|
|||
.form-text.text-muted
|
||||
= _("See GitLab's %{password_policy_guidelines}").html_safe % { password_policy_guidelines: password_policy_guidelines_link }
|
||||
.form-group
|
||||
= f.label :domain_whitelist, _('Allowed domains for sign-ups'), class: 'label-bold'
|
||||
= f.label :domain_allowlist, _('Allowed domains for sign-ups'), class: 'label-bold'
|
||||
= f.text_area :domain_allowlist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
|
||||
.form-text.text-muted ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
|
||||
.form-group
|
||||
= f.label :domain_blacklist_enabled, _('Domain denylist'), class: 'label-bold'
|
||||
= f.label :domain_denylist_enabled, _('Domain denylist'), class: 'label-bold'
|
||||
.form-check
|
||||
= f.check_box :domain_blacklist_enabled, class: 'form-check-input'
|
||||
= f.label :domain_blacklist_enabled, class: 'form-check-label' do
|
||||
= f.check_box :domain_denylist_enabled, class: 'form-check-input'
|
||||
= f.label :domain_denylist_enabled, class: 'form-check-label' do
|
||||
Enable domain denylist for sign ups
|
||||
.form-group
|
||||
.form-check
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
.option-title
|
||||
Upload denylist file
|
||||
.form-check
|
||||
= radio_button_tag :denylist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank?, class: 'form-check-input'
|
||||
= radio_button_tag :denylist_type, :raw, @application_setting.domain_denylist.present? || @application_setting.domain_denylist.blank?, class: 'form-check-input'
|
||||
= label_tag :denylist_type_raw, class: 'form-check-label' do
|
||||
.option-title
|
||||
Enter denylist manually
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
= f.file_field :domain_denylist_file, class: 'form-control', accept: '.txt,.conf'
|
||||
.form-text.text-muted Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries.
|
||||
.form-group.js-denylist-raw
|
||||
= f.label :domain_blacklist, _('Denied domains for sign-ups'), class: 'label-bold'
|
||||
= f.label :domain_denylist, _('Denied domains for sign-ups'), class: 'label-bold'
|
||||
= f.text_area :domain_denylist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
|
||||
.form-text.text-muted Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
|
||||
.form-group
|
||||
|
|
|
|||
|
|
@ -16,15 +16,16 @@ module ContainerExpirationPolicies
|
|||
return unless throttling_enabled?
|
||||
return unless container_repository
|
||||
|
||||
log_extra_metadata_on_done(:container_repository_id, container_repository.id)
|
||||
|
||||
unless allowed_to_run?(container_repository)
|
||||
container_repository.cleanup_unscheduled!
|
||||
log_info(container_repository_id: container_repository.id, cleanup_status: :skipped)
|
||||
log_extra_metadata_on_done(:cleanup_status, :skipped)
|
||||
return
|
||||
end
|
||||
|
||||
result = ContainerExpirationPolicies::CleanupService.new(container_repository)
|
||||
.execute
|
||||
log_extra_metadata_on_done(:container_repository_id, result.payload[:container_repository_id])
|
||||
log_extra_metadata_on_done(:cleanup_status, result.payload[:cleanup_status])
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Change the mutation and permissions for image note reposition
|
||||
merge_request: 47161
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Forward deployment, add modal to warn users on Retry action
|
||||
merge_request: 46416
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update container_scanning to version 3 to support FIPS
|
||||
merge_request: 47099
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Migrate-Bootstrap-dropdown-to-GitLab-UI-GlDropdown-in-app/assets/javascripts/jobs/components/stages_dropdown.vue
|
||||
merge_request: 41452
|
||||
author: nuwe1
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use allowlist/denylist in application settings backend
|
||||
merge_request: 46170
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use new image details API in container registry details
|
||||
merge_request: 47054
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixed create branch button not hiding when issue is closed
|
||||
merge_request: 47187
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: unified_diff_components
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44974
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/268039
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
||||
|
|
@ -198,7 +198,7 @@ Settings.gitlab.default_projects_features['snippets'] = true if Settin
|
|||
Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil?
|
||||
Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
|
||||
Settings.gitlab.default_projects_features['visibility_level'] = Settings.__send__(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
|
||||
Settings.gitlab['domain_whitelist'] ||= []
|
||||
Settings.gitlab['domain_allowlist'] ||= []
|
||||
Settings.gitlab['import_sources'] ||= Gitlab::ImportSources.values
|
||||
Settings.gitlab['trusted_proxies'] ||= []
|
||||
Settings.gitlab['content_security_policy'] ||= Gitlab::ContentSecurityPolicy::ConfigLoader.default_settings_hash
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RenameApplicationSettingsToAllowDenyNames < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
rename_column_concurrently :application_settings, :domain_blacklist_enabled, :domain_denylist_enabled
|
||||
rename_column_concurrently :application_settings, :domain_blacklist, :domain_denylist
|
||||
rename_column_concurrently :application_settings, :domain_whitelist, :domain_allowlist
|
||||
end
|
||||
|
||||
def down
|
||||
undo_rename_column_concurrently :application_settings, :domain_blacklist_enabled, :domain_denylist_enabled
|
||||
undo_rename_column_concurrently :application_settings, :domain_blacklist, :domain_denylist
|
||||
undo_rename_column_concurrently :application_settings, :domain_whitelist, :domain_allowlist
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CleanupApplicationSettingsToAllowDenyRename < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
cleanup_concurrent_column_rename :application_settings, :domain_blacklist_enabled, :domain_denylist_enabled
|
||||
cleanup_concurrent_column_rename :application_settings, :domain_blacklist, :domain_denylist
|
||||
cleanup_concurrent_column_rename :application_settings, :domain_whitelist, :domain_allowlist
|
||||
end
|
||||
|
||||
def down
|
||||
undo_cleanup_concurrent_column_rename :application_settings, :domain_blacklist_enabled, :domain_denylist_enabled
|
||||
undo_cleanup_concurrent_column_rename :application_settings, :domain_blacklist, :domain_denylist
|
||||
undo_cleanup_concurrent_column_rename :application_settings, :domain_whitelist, :domain_allowlist
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
c718bc731f7dc3e1f0104dfdb79a3dc46c46849153ec9b228600eeb5a92465e7
|
||||
|
|
@ -0,0 +1 @@
|
|||
a61310c95a1302871ea18881d45bc0c7357baa8f24daa31b7e2174318dab5707
|
||||
|
|
@ -9083,7 +9083,6 @@ CREATE TABLE application_settings (
|
|||
max_attachment_size integer DEFAULT 10 NOT NULL,
|
||||
default_project_visibility integer DEFAULT 0 NOT NULL,
|
||||
default_snippet_visibility integer DEFAULT 0 NOT NULL,
|
||||
domain_whitelist text,
|
||||
user_oauth_applications boolean DEFAULT true,
|
||||
after_sign_out_path character varying,
|
||||
session_expire_delay integer DEFAULT 10080 NOT NULL,
|
||||
|
|
@ -9119,8 +9118,6 @@ CREATE TABLE application_settings (
|
|||
elasticsearch_search boolean DEFAULT false NOT NULL,
|
||||
repository_storages character varying DEFAULT 'default'::character varying,
|
||||
enabled_git_access_protocol character varying,
|
||||
domain_blacklist_enabled boolean DEFAULT false,
|
||||
domain_blacklist text,
|
||||
usage_ping_enabled boolean DEFAULT true NOT NULL,
|
||||
sign_in_text_html text,
|
||||
help_page_text_html text,
|
||||
|
|
@ -9341,6 +9338,9 @@ CREATE TABLE application_settings (
|
|||
secret_detection_token_revocation_url text,
|
||||
encrypted_secret_detection_token_revocation_token text,
|
||||
encrypted_secret_detection_token_revocation_token_iv text,
|
||||
domain_denylist_enabled boolean DEFAULT false,
|
||||
domain_denylist text,
|
||||
domain_allowlist text,
|
||||
new_user_signups_cap integer,
|
||||
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
|
||||
CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)),
|
||||
|
|
|
|||
|
|
@ -20841,7 +20841,7 @@ type TodoRestoreManyPayload {
|
|||
"""
|
||||
The ids of the updated todo items. Deprecated in 13.2: Use todos
|
||||
"""
|
||||
updatedIds: [ID!]! @deprecated(reason: "Use todos. Deprecated in 13.2")
|
||||
updatedIds: [TodoID!]! @deprecated(reason: "Use todos. Deprecated in 13.2")
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
@ -20938,7 +20938,7 @@ type TodosMarkAllDonePayload {
|
|||
"""
|
||||
Ids of the updated todos. Deprecated in 13.2: Use todos
|
||||
"""
|
||||
updatedIds: [ID!]! @deprecated(reason: "Use todos. Deprecated in 13.2")
|
||||
updatedIds: [TodoID!]! @deprecated(reason: "Use todos. Deprecated in 13.2")
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -60657,7 +60657,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"name": "TodoID",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
|
|
@ -60934,7 +60934,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"name": "TodoID",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3077,7 +3077,7 @@ Autogenerated return type of TodoRestoreMany.
|
|||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
| `todos` | Todo! => Array | Updated todos |
|
||||
| `updatedIds` **{warning-solid}** | ID! => Array | **Deprecated:** Use todos. Deprecated in 13.2 |
|
||||
| `updatedIds` **{warning-solid}** | TodoID! => Array | **Deprecated:** Use todos. Deprecated in 13.2 |
|
||||
|
||||
### TodoRestorePayload
|
||||
|
||||
|
|
@ -3098,7 +3098,7 @@ Autogenerated return type of TodosMarkAllDone.
|
|||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
| `todos` | Todo! => Array | Updated todos |
|
||||
| `updatedIds` **{warning-solid}** | ID! => Array | **Deprecated:** Use todos. Deprecated in 13.2 |
|
||||
| `updatedIds` **{warning-solid}** | TodoID! => Array | **Deprecated:** Use todos. Deprecated in 13.2 |
|
||||
|
||||
### ToggleAwardEmojiPayload
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ GET /groups?statistics=true
|
|||
"parent_id": null,
|
||||
"created_at": "2020-01-15T12:36:29.590Z",
|
||||
"statistics": {
|
||||
"storage_size" : 212,
|
||||
"storage_size" : 363,
|
||||
"repository_size" : 33,
|
||||
"wiki_size" : 100,
|
||||
"lfs_objects_size" : 123,
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ Example response:
|
|||
"home_page_url" : null,
|
||||
"default_snippet_visibility" : "private",
|
||||
"outbound_local_requests_whitelist": [],
|
||||
"domain_whitelist" : [],
|
||||
"domain_blacklist_enabled" : false,
|
||||
"domain_blacklist" : [],
|
||||
"domain_allowlist" : [],
|
||||
"domain_denylist_enabled" : false,
|
||||
"domain_denylist" : [],
|
||||
"created_at" : "2016-01-04T15:44:55.176Z",
|
||||
"default_ci_config_path" : null,
|
||||
"default_project_visibility" : "private",
|
||||
|
|
@ -134,9 +134,9 @@ Example response:
|
|||
"default_snippet_visibility": "private",
|
||||
"default_group_visibility": "private",
|
||||
"outbound_local_requests_whitelist": [],
|
||||
"domain_whitelist": [],
|
||||
"domain_blacklist_enabled" : false,
|
||||
"domain_blacklist" : [],
|
||||
"domain_allowlist": [],
|
||||
"domain_denylist_enabled" : false,
|
||||
"domain_denylist" : [],
|
||||
"external_authorization_service_enabled": true,
|
||||
"external_authorization_service_url": "https://authorize.me",
|
||||
"external_authorization_service_default_label": "default",
|
||||
|
|
@ -233,9 +233,9 @@ listed in the descriptions of the relevant settings.
|
|||
| `diff_max_patch_bytes` | integer | no | Maximum diff patch size (Bytes). |
|
||||
| `disabled_oauth_sign_in_sources` | array of strings | no | Disabled OAuth sign-in sources. |
|
||||
| `dns_rebinding_protection_enabled` | boolean | no | Enforce DNS rebinding attack protection. |
|
||||
| `domain_blacklist_enabled` | boolean | no | (**If enabled, requires:** `domain_blacklist`) Allows blocking sign-ups from emails from specific domains. |
|
||||
| `domain_blacklist` | array of strings | no | Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. |
|
||||
| `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. |
|
||||
| `domain_denylist_enabled` | boolean | no | (**If enabled, requires:** `domain_denylist`) Allows blocking sign-ups from emails from specific domains. |
|
||||
| `domain_denylist` | array of strings | no | Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. |
|
||||
| `domain_allowlist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. |
|
||||
| `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. |
|
||||
| `ecdsa_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `0` (no restriction). `-1` disables ECDSA keys. |
|
||||
| `ed25519_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ED25519 key. Default is `0` (no restriction). `-1` disables ED25519 keys. |
|
||||
|
|
|
|||
|
|
@ -231,6 +231,16 @@ When enabled, any older deployments job are skipped when a new deployment starts
|
|||
|
||||
For more information, see [Deployment safety](../environments/deployment_safety.md).
|
||||
|
||||
## Retry outdated jobs
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211339) in GitLab 13.6.
|
||||
|
||||
A deployment job can fail because a newer one has run. If you retry the failed deployment job, the
|
||||
environment could be overwritten with older source code. If you click **Retry**, a modal warns you
|
||||
about this and asks for confirmation.
|
||||
|
||||
For more information, see [Deployment safety](../environments/deployment_safety.md).
|
||||
|
||||
## Pipeline Badges
|
||||
|
||||
In the pipelines settings page you can find pipeline status and test coverage
|
||||
|
|
|
|||
|
|
@ -201,8 +201,8 @@ estimated to keep migration timing to a minimum.
|
|||
NOTE: **Note:**
|
||||
Keep in mind that all runtimes should be measured against GitLab.com.
|
||||
|
||||
| Migration Type | Execution Time Recommended | Notes |
|
||||
| Migration Type | Execution Time Recommended | Notes |
|
||||
|----|----|---|
|
||||
| Regular migrations on `db/migrate` | `3 minutes` | A valid exception are index creation as this can take a long time. |
|
||||
| Post migrations on `db/post_migrate` | `10 minutes` | |
|
||||
| Background migrations | --- | Since these are suitable for larger tables, it's not possible to set a precise timing guideline, however, any single query must stay below `1 second` execution time with cold caches. |
|
||||
| Post migrations on `db/post_migrate` | `10 minutes` | |
|
||||
| Background migrations | --- | Since these are suitable for larger tables, it's not possible to set a precise timing guideline, however, any single query must stay below `1 second` execution time with cold caches. |
|
||||
|
|
|
|||
|
|
@ -1210,10 +1210,10 @@ When you take screenshots:
|
|||
or concept in the image. If the image is of the GitLab interface, append the
|
||||
GitLab version to the file name, based on the following format:
|
||||
`image_name_vX_Y.png`. For example, for a screenshot taken from the pipelines
|
||||
page of GitLab 11.1, a valid name is `pipelines_v11_1.png`. If you're adding an
|
||||
page of GitLab 11.1, a valid name is `pipelines_v11_1.png`. If you're adding an
|
||||
illustration that doesn't include parts of the user interface, add the release
|
||||
number corresponding to the release the image was added to; for an MR added to
|
||||
11.1's milestone, a valid name for an illustration is `devops_diagram_v11_1.png`.
|
||||
11.1's milestone, a valid name for an illustration is `devops_diagram_v11_1.png`.
|
||||
- Place images in a separate directory named `img/` in the same directory where
|
||||
the `.md` document that you're working on is located.
|
||||
- Consider using PNG images instead of JPEG.
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ repository and a pool.
|
|||
|
||||
### Pool existence
|
||||
|
||||
If GitLab thinks a pool repository exists (i.e. it exists according to
|
||||
If GitLab thinks a pool repository exists (i.e. it exists according to
|
||||
SQL), but it does not on the Gitaly server, then it will be created on
|
||||
the fly by Gitaly.
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ To propose additions to the glossary please
|
|||
### Inclusive language in French
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
In French, the "écriture inclusive" is now over (see on [Legifrance](https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000036068906&categorieLien=id)).
|
||||
In French, the "écriture inclusive" is now over (see on [Legifrance](https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000036068906&categorieLien=id)).
|
||||
So, to include both genders, write “Utilisateurs et utilisatrices” instead of “Utilisateur·rice·s”.
|
||||
When space is missing, the male gender should be used alone.
|
||||
<!-- vale gitlab.Spelling = YES -->
|
||||
|
|
|
|||
|
|
@ -360,28 +360,28 @@ The NDJSON tree will look like this:
|
|||
```shell
|
||||
tree
|
||||
├── project
|
||||
│ ├── auto_devops.ndjson
|
||||
│ ├── boards.ndjson
|
||||
│ ├── ci_cd_settings.ndjson
|
||||
│ ├── ci_pipelines.ndjson
|
||||
│ ├── container_expiration_policy.ndjson
|
||||
│ ├── custom_attributes.ndjson
|
||||
│ ├── error_tracking_setting.ndjson
|
||||
│ ├── external_pull_requests.ndjson
|
||||
│ ├── issues.ndjson
|
||||
│ ├── labels.ndjson
|
||||
│ ├── merge_requests.ndjson
|
||||
│ ├── milestones.ndjson
|
||||
│ ├── pipeline_schedules.ndjson
|
||||
│ ├── project_badges.ndjson
|
||||
│ ├── project_feature.ndjson
|
||||
│ ├── project_members.ndjson
|
||||
│ ├── protected_branches.ndjson
|
||||
│ ├── protected_tags.ndjson
|
||||
│ ├── releases.ndjson
|
||||
│ ├── services.ndjson
|
||||
│ ├── snippets.ndjson
|
||||
│ └── triggers.ndjson
|
||||
│ ├── auto_devops.ndjson
|
||||
│ ├── boards.ndjson
|
||||
│ ├── ci_cd_settings.ndjson
|
||||
│ ├── ci_pipelines.ndjson
|
||||
│ ├── container_expiration_policy.ndjson
|
||||
│ ├── custom_attributes.ndjson
|
||||
│ ├── error_tracking_setting.ndjson
|
||||
│ ├── external_pull_requests.ndjson
|
||||
│ ├── issues.ndjson
|
||||
│ ├── labels.ndjson
|
||||
│ ├── merge_requests.ndjson
|
||||
│ ├── milestones.ndjson
|
||||
│ ├── pipeline_schedules.ndjson
|
||||
│ ├── project_badges.ndjson
|
||||
│ ├── project_feature.ndjson
|
||||
│ ├── project_members.ndjson
|
||||
│ ├── protected_branches.ndjson
|
||||
│ ├── protected_tags.ndjson
|
||||
│ ├── releases.ndjson
|
||||
│ ├── services.ndjson
|
||||
│ ├── snippets.ndjson
|
||||
│ └── triggers.ndjson
|
||||
└── project.json
|
||||
```
|
||||
|
||||
|
|
@ -395,19 +395,19 @@ The NDJSON tree will look like this:
|
|||
tree
|
||||
└── groups
|
||||
├── 4351
|
||||
│ ├── badges.ndjson
|
||||
│ ├── boards.ndjson
|
||||
│ ├── epics.ndjson
|
||||
│ ├── labels.ndjson
|
||||
│ ├── members.ndjson
|
||||
│ └── milestones.ndjson
|
||||
│ ├── badges.ndjson
|
||||
│ ├── boards.ndjson
|
||||
│ ├── epics.ndjson
|
||||
│ ├── labels.ndjson
|
||||
│ ├── members.ndjson
|
||||
│ └── milestones.ndjson
|
||||
├── 4352
|
||||
│ ├── badges.ndjson
|
||||
│ ├── boards.ndjson
|
||||
│ ├── epics.ndjson
|
||||
│ ├── labels.ndjson
|
||||
│ ├── members.ndjson
|
||||
│ └── milestones.ndjson
|
||||
│ ├── badges.ndjson
|
||||
│ ├── boards.ndjson
|
||||
│ ├── epics.ndjson
|
||||
│ ├── labels.ndjson
|
||||
│ ├── members.ndjson
|
||||
│ └── milestones.ndjson
|
||||
├── _all.ndjson
|
||||
├── 4351.json
|
||||
└── 4352.json
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ to `info`.
|
|||
|
||||
When executing command lines, scanners should use the `debug` level to log the command line and its output.
|
||||
For instance, the [bundler-audit](https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit) scanner
|
||||
uses the `debug` level to log the command line `bundle audit check --quiet`,
|
||||
uses the `debug` level to log the command line `bundle audit check --quiet`,
|
||||
and what `bundle audit` writes to the standard output.
|
||||
|
||||
#### common logutil package
|
||||
|
|
@ -298,7 +298,7 @@ The `vulnerabilities` field of the report is an array of vulnerability objects.
|
|||
|
||||
#### ID
|
||||
|
||||
The `id` field is the unique identifier of the vulnerability.
|
||||
The `id` field is the unique identifier of the vulnerability.
|
||||
It is used to reference a fixed vulnerability from a [remediation objects](#remediations).
|
||||
We recommend that you generate a UUID and use it as the `id` field's value.
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue