Коммит c787c155 создал по автору GitLab Bot's avatar GitLab Bot
Просмотр файлов

Add latest changes from gitlab-org/gitlab@master

владелец 6df79435
......@@ -862,7 +862,6 @@ Rails/SaveBang:
- 'ee/spec/services/todo_service_spec.rb'
- 'ee/spec/services/update_build_minutes_service_spec.rb'
- 'ee/spec/services/vulnerability_feedback/create_service_spec.rb'
- 'ee/spec/support/helpers/ee/geo_helpers.rb'
- 'ee/spec/support/protected_tags/access_control_shared_examples.rb'
- 'ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb'
- 'ee/spec/support/shared_examples/finders/geo/framework_registry_finder_shared_examples.rb'
......@@ -1306,12 +1305,6 @@ Rails/SaveBang:
- 'spec/services/users/repair_ldap_blocked_service_spec.rb'
- 'spec/services/verify_pages_domain_service_spec.rb'
- 'spec/sidekiq/cron/job_gem_dependency_spec.rb'
- 'spec/support/helpers/cycle_analytics_helpers.rb'
- 'spec/support/helpers/design_management_test_helpers.rb'
- 'spec/support/helpers/jira_service_helper.rb'
- 'spec/support/helpers/login_helpers.rb'
- 'spec/support/helpers/notification_helpers.rb'
- 'spec/support/helpers/stub_object_storage.rb'
- 'spec/support/migrations_helpers/cluster_helpers.rb'
- 'spec/support/migrations_helpers/namespaces_helper.rb'
- 'spec/support/shared_contexts/email_shared_context.rb'
......
......@@ -3,7 +3,6 @@ import {
GlAlert,
GlButton,
GlCollapse,
GlDeprecatedButton,
GlFormCheckbox,
GlFormCombobox,
GlFormGroup,
......@@ -39,7 +38,6 @@ export default {
GlAlert,
GlButton,
GlCollapse,
GlDeprecatedButton,
GlFormCheckbox,
GlFormCombobox,
GlFormGroup,
......@@ -340,24 +338,25 @@ export default {
</gl-alert>
</gl-collapse>
<template #modal-footer>
<gl-deprecated-button @click="hideModal">{{ __('Cancel') }}</gl-deprecated-button>
<gl-deprecated-button
<gl-button @click="hideModal">{{ __('Cancel') }}</gl-button>
<gl-button
v-if="variableBeingEdited"
ref="deleteCiVariable"
category="secondary"
variant="danger"
category="secondary"
data-qa-selector="ci_variable_delete_button"
@click="deleteVarAndClose"
>{{ __('Delete variable') }}</gl-deprecated-button
>{{ __('Delete variable') }}</gl-button
>
<gl-deprecated-button
<gl-button
ref="updateOrAddVariable"
:disabled="!canSubmit"
variant="success"
category="primary"
data-qa-selector="ci_variable_save_button"
@click="updateOrAddVariable"
>{{ modalActionText }}
</gl-deprecated-button>
</gl-button>
</template>
</gl-modal>
</template>
import { take } from 'lodash';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import sanitize from 'sanitize-html';
import { sanitize } from 'dompurify';
import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants';
export const isMobile = () => ['md', 'sm', 'xs'].includes(bp.getBreakpointSize());
......@@ -52,7 +52,7 @@ export const sanitizeItem = item => {
return {};
}
return { [key]: sanitize(item[key].toString(), { allowedTags: [] }) };
return { [key]: sanitize(item[key].toString(), { ALLOWED_TAGS: [] }) };
};
return {
......
......@@ -13,6 +13,7 @@ import {
GlPagination,
GlTabs,
GlTab,
GlBadge,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
......@@ -20,7 +21,8 @@ import { convertToSnakeCase } from '~/lib/utils/text_utility';
import { s__ } from '~/locale';
import { mergeUrlParams, joinPaths, visitUrl } from '~/lib/utils/url_utility';
import getIncidents from '../graphql/queries/get_incidents.query.graphql';
import { I18N, DEFAULT_PAGE_SIZE, INCIDENT_SEARCH_DELAY, INCIDENT_STATE_TABS } from '../constants';
import getIncidentsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
import { I18N, DEFAULT_PAGE_SIZE, INCIDENT_SEARCH_DELAY, INCIDENT_STATUS_TABS } from '../constants';
const TH_TEST_ID = { 'data-testid': 'incident-management-created-at-sort' };
const tdClass =
......@@ -39,7 +41,7 @@ const initialPaginationState = {
export default {
i18n: I18N,
stateTabs: INCIDENT_STATE_TABS,
statusTabs: INCIDENT_STATUS_TABS,
fields: [
{
key: 'title',
......@@ -77,6 +79,7 @@ export default {
GlTabs,
GlTab,
PublishedCell: () => import('ee_component/incidents/components/published_cell.vue'),
GlBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -94,7 +97,7 @@ export default {
variables() {
return {
searchTerm: this.searchTerm,
state: this.stateFilter,
status: this.statusFilter,
projectPath: this.projectPath,
issueTypes: ['INCIDENT'],
sort: this.sort,
......@@ -114,6 +117,19 @@ export default {
this.errored = true;
},
},
incidentsCount: {
query: getIncidentsCountByStatus,
variables() {
return {
searchTerm: this.searchTerm,
projectPath: this.projectPath,
issueTypes: ['INCIDENT'],
};
},
update(data) {
return data.project?.issueStatusCounts;
},
},
},
data() {
return {
......@@ -123,15 +139,16 @@ export default {
searchTerm: '',
pagination: initialPaginationState,
incidents: {},
stateFilter: '',
sort: 'created_desc',
sortBy: 'createdAt',
sortDesc: true,
statusFilter: '',
filteredByStatus: '',
};
},
computed: {
showErrorMsg() {
return this.errored && !this.isErrorAlertDismissed && !this.searchTerm;
return this.errored && !this.isErrorAlertDismissed && this.incidentsCount?.all === 0;
},
loading() {
return this.$apollo.queries.incidents.loading;
......@@ -139,6 +156,9 @@ export default {
hasIncidents() {
return this.incidents?.list?.length;
},
incidentsForCurrentTab() {
return this.incidentsCount?.[this.filteredByStatus.toLowerCase()] ?? 0;
},
showPaginationControls() {
return Boolean(
this.incidents?.pageInfo?.hasNextPage || this.incidents?.pageInfo?.hasPreviousPage,
......@@ -149,7 +169,9 @@ export default {
},
nextPage() {
const nextPage = this.pagination.currentPage + 1;
return this.incidents?.list?.length < DEFAULT_PAGE_SIZE ? null : nextPage;
return nextPage > Math.ceil(this.incidentsForCurrentTab / DEFAULT_PAGE_SIZE)
? null
: nextPage;
},
tbodyTrClass() {
return {
......@@ -181,9 +203,10 @@ export default {
this.searchTerm = trimmedInput;
}
}, INCIDENT_SEARCH_DELAY),
filterIncidentsByState(tabIndex) {
const { filters } = this.$options.stateTabs[tabIndex];
this.stateFilter = filters;
filterIncidentsByStatus(tabIndex) {
const { filters, status } = this.$options.statusTabs[tabIndex];
this.statusFilter = filters;
this.filteredByStatus = status;
},
hasAssignees(assignees) {
return Boolean(assignees.nodes?.length);
......@@ -231,10 +254,13 @@ export default {
<div
class="incident-management-list-header gl-display-flex gl-justify-content-space-between gl-border-b-solid gl-border-b-1 gl-border-gray-100"
>
<gl-tabs content-class="gl-p-0" @input="filterIncidentsByState">
<gl-tab v-for="tab in $options.stateTabs" :key="tab.state" :data-testid="tab.state">
<gl-tabs content-class="gl-p-0" @input="filterIncidentsByStatus">
<gl-tab v-for="tab in $options.statusTabs" :key="tab.status" :data-testid="tab.status">
<template #title>
<span>{{ tab.title }}</span>
<gl-badge v-if="incidentsCount" pill size="sm" class="gl-tab-counter-badge">
{{ incidentsCount[tab.status.toLowerCase()] }}
</gl-badge>
</template>
</gl-tab>
</gl-tabs>
......
......@@ -9,20 +9,20 @@ export const I18N = {
searchPlaceholder: __('Search results…'),
};
export const INCIDENT_STATE_TABS = [
export const INCIDENT_STATUS_TABS = [
{
title: s__('IncidentManagement|Open'),
state: 'OPENED',
status: 'OPENED',
filters: 'opened',
},
{
title: s__('IncidentManagement|Closed'),
state: 'CLOSED',
status: 'CLOSED',
filters: 'closed',
},
{
title: s__('IncidentManagement|All'),
state: 'ALL',
status: 'ALL',
filters: 'all',
},
];
......
query getIncidentsCountByStatus($searchTerm: String, $projectPath: ID!, $issueTypes: [IssueType!]) {
project(fullPath: $projectPath) {
issueStatusCounts(search: $searchTerm, types: $issueTypes) {
all
opened
closed
}
}
}
......@@ -2,7 +2,7 @@ query getIncidents(
$projectPath: ID!
$issueTypes: [IssueType!]
$sort: IssueSort
$state: IssuableState
$status: IssuableState
$firstPageSize: Int
$lastPageSize: Int
$prevPageCursor: String = ""
......@@ -12,9 +12,9 @@ query getIncidents(
project(fullPath: $projectPath) {
issues(
search: $searchTerm
state: $state
types: $issueTypes
sort: $sort
state: $status
first: $firstPageSize
last: $lastPageSize
after: $nextPageCursor
......
import sanitize from 'sanitize-html';
import { sanitize } from 'dompurify';
export const parseIssuableData = () => {
try {
......
......@@ -710,3 +710,16 @@ export const dateFromParams = (year, month, day) => {
return date;
};
/**
* A utility function which computes the difference in seconds
* between 2 dates.
*
* @param {Date} startDate the start sate
* @param {Date} endDate the end date
*
* @return {Int} the difference in seconds
*/
export const differenceInSeconds = (startDate, endDate) => {
return (endDate.getTime() - startDate.getTime()) / 1000;
};
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import sanitize from 'sanitize-html';
import { sanitize } from 'dompurify';
/**
* Wraps substring matches with HTML `<span>` elements.
......@@ -24,7 +24,7 @@ export default function highlight(string, match = '', matchPrefix = '<b>', match
return string;
}
const sanitizedValue = sanitize(string.toString(), { allowedTags: [] });
const sanitizedValue = sanitize(string.toString(), { ALLOWED_TAGS: [] });
// occurrences is an array of character indices that should be
// highlighted in the original string, i.e. [3, 4, 5, 7]
......
<script>
import marked from 'marked';
import sanitize from 'sanitize-html';
import { sanitize } from 'dompurify';
import katex from 'katex';
import Prompt from './prompt.vue';
......@@ -104,65 +104,58 @@ export default {
return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), {
// allowedTags from GitLab's inline HTML guidelines
// https://docs.gitlab.com/ee/user/markdown.html#inline-html
allowedTags: [
ALLOWED_TAGS: [
'a',
'abbr',
'b',
'blockquote',
'br',
'code',
'dd',
'del',
'div',
'dl',
'dt',
'em',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'h7',
'h8',
'br',
'b',
'hr',
'i',
'strong',
'em',
'a',
'pre',
'code',
'img',
'tt',
'div',
'ins',
'del',
'sup',
'sub',
'p',
'ol',
'ul',
'table',
'thead',
'tbody',
'tfoot',
'blockquote',
'dl',
'dt',
'dd',
'kbd',
'li',
'ol',
'p',
'pre',
'q',
'samp',
'var',
'hr',
'ruby',
'rt',
'rp',
'li',
'tr',
'td',
'th',
'rt',
'ruby',
's',
'strike',
'samp',
'span',
'abbr',
'abbr',
'strike',
'strong',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'tr',
'tt',
'ul',
'var',
],
allowedAttributes: {
'*': ['class', 'style'],
a: ['href'],
img: ['src'],
},
ALLOWED_ATTR: ['class', 'style', 'href', 'src'],
});
},
},
......
<script>
import sanitize from 'sanitize-html';
import { sanitize } from 'dompurify';
import Prompt from '../prompt.vue';
export default {
......@@ -23,10 +23,7 @@ export default {
computed: {
sanitizedOutput() {
return sanitize(this.rawCode, {
allowedTags: sanitize.defaults.allowedTags.concat(['img', 'svg']),
allowedAttributes: {
img: ['src'],
},
ALLOWED_ATTR: ['src'],
});
},
showOutput() {
......
......@@ -110,6 +110,6 @@ export const composerRegistryInclude = ({ composerPath }) => {
return JSON.stringify(base);
};
export const composerPackageInclude = ({ packageEntity }) => {
const base = { package_name: packageEntity.name };
const base = { [packageEntity.name]: packageEntity.version };
return JSON.stringify(base);
};
......@@ -54,9 +54,6 @@ export default {
hasProjectLink() {
return Boolean(this.packageEntity.project_path);
},
deleteAvailable() {
return !this.disableDelete && !this.isGroup;
},
},
};
</script>
......@@ -111,7 +108,7 @@ export default {
<div
class="table-section d-flex flex-md-column justify-content-between align-items-md-end"
:class="!deleteAvailable ? 'section-50' : 'section-40'"
:class="disableDelete ? 'section-50' : 'section-40'"
>
<publish-method :package-entity="packageEntity" :is-group="isGroup" />
......@@ -126,7 +123,7 @@ export default {
</div>
</div>
<div v-if="deleteAvailable" class="table-section section-10 d-flex justify-content-end">
<div v-if="!disableDelete" class="table-section section-10 d-flex justify-content-end">
<gl-button
data-testid="action-delete"
icon="remove"
......
......@@ -2,7 +2,7 @@
import $ from 'jquery';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import sanitize from 'sanitize-html';
import { sanitize } from 'dompurify';
import axios from '~/lib/utils/axios_utils';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import flash from '~/flash';
......
import Vue from 'vue';
import sanitize from 'sanitize-html';
import { sanitize } from 'dompurify';
import UsersCache from './lib/utils/users_cache';
import UserPopover from './vue_shared/components/user_popover/user_popover.vue';
......
......@@ -458,6 +458,23 @@
}
.vue-filtered-search-bar-container {
.gl-search-box-by-click {
// Absolute width is needed to prevent flex to grow
// beyond the available width.
.gl-filtered-search-scrollable {
width: 1px;
}
// There are several styling issues happening while using
// `GlFilteredSearch` in roadmap due to some of our global
// styles which we need to override until those are fixed
// at framework level.
// See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/908
.input-group-prepend + .gl-filtered-search-scrollable {
border-radius: 0;
}
}
@include media-breakpoint-up(md) {
.sort-dropdown-container {
margin-left: 10px;
......
......@@ -446,6 +446,8 @@ $context-header-height: 60px;
$breadcrumb-min-height: 48px;
$home-panel-title-row-height: 64px;
$home-panel-avatar-mobile-size: 24px;
$issuable-title-max-width: 350px;
$milestone-title-max-width: 75px;
$gl-line-height: 16px;
$gl-line-height-18: 18px;
$gl-line-height-20: 20px;
......
......@@ -38,6 +38,9 @@ def preloads
assignees: [:assignees],
labels: [:labels],
author: [:author],
merged_at: [:metrics],
commit_count: [:metrics],
approved_by: [:approver_users],
milestone: [:milestone],
head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }]
}
......
......@@ -107,6 +107,16 @@ class AlertType < BaseObject
description: 'Todos of the current user for the alert',
resolver: Resolvers::TodoResolver
field :details_url,
GraphQL::STRING_TYPE,
null: false,
description: 'The URL of the alert detail page'
field :prometheus_alert,
Types::PrometheusAlertType,
null: true,
description: 'The alert condition for Prometheus'
def notes
object.ordered_notes
end
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать