diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-24 00:13:49 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-24 00:13:49 +0300 |
commit | 00ab3a60fed93cb3c6c9148d9c9c68fb11d325ee (patch) | |
tree | bef28d84085f23cfbd30a3a1ed74a601eaa2eef5 /app | |
parent | 68caf5fd883a7fd5a3395c2e5ae2a5c511445613 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
11 files changed, 133 insertions, 13 deletions
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue index 0f974364600..41f66c8d33c 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue @@ -4,7 +4,7 @@ import { stringify, parse } from 'yaml'; import { set, omit, trim } from 'lodash'; import { getContentWrapperHeight } from '~/lib/utils/dom_utils'; import eventHub, { SCROLL_EDITOR_TO_BOTTOM } from '~/ci/pipeline_editor/event_hub'; -import getAllRunners from '~/ci/runner/graphql/list/all_runners.query.graphql'; +import getRunnerTags from '../../graphql/queries/runner_tags.query.graphql'; import { DRAWER_CONTAINER_CLASS, JOB_TEMPLATE, i18n } from './constants'; import { removeEmptyObj, trimFields } from './utils'; import JobSetupItem from './accordion_items/job_setup_item.vue'; @@ -48,7 +48,7 @@ export default { }, apollo: { runners: { - query: getAllRunners, + query: getRunnerTags, update(data) { return data?.runners?.nodes || []; }, diff --git a/app/assets/javascripts/ci/pipeline_editor/graphql/queries/runner_tags.query.graphql b/app/assets/javascripts/ci/pipeline_editor/graphql/queries/runner_tags.query.graphql new file mode 100644 index 00000000000..aab30257d13 --- /dev/null +++ b/app/assets/javascripts/ci/pipeline_editor/graphql/queries/runner_tags.query.graphql @@ -0,0 +1,8 @@ +query getRunnerTags { + runners { + nodes { + id + tagList + } + } +} diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue index e0e3b961c51..ef228370133 100644 --- a/app/assets/javascripts/clusters_list/components/agent_table.vue +++ b/app/assets/javascripts/clusters_list/components/agent_table.vue @@ -8,6 +8,9 @@ import { GlTooltipDirective, GlPopover, } from '@gitlab/ui'; +import semverLt from 'semver/functions/lt'; +import semverInc from 'semver/functions/inc'; +import semverPrerelease from 'semver/functions/prerelease'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import { helpPagePath } from '~/helpers/help_page_helper'; @@ -134,18 +137,26 @@ export default { isVersionMismatch(agent) { return agent.versions.length > 1; }, + // isVersionOutdated determines if the agent version is outdated compared to the KAS / GitLab version + // using the following heuristics: + // - KAS Version is used as *server* version if available, otherwise the GitLab version is used. + // - returns `outdated` if the agent has a different major version than the server + // - returns `outdated` if the agents minor version is at least two proper versions older than the server + // - *proper* -> not a prerelease version. Meaning that server prereleases (with `-rcN`) suffix are counted as the previous minor version + // + // Note that it does NOT support if the agent is newer than the server version. isVersionOutdated(agent) { if (!agent.versions.length) return false; - const [agentMajorVersion, agentMinorVersion] = this.getAgentVersionString(agent).split('.'); - const [serverMajorVersion, serverMinorVersion] = this.serverVersion.split('.'); + const agentVersion = this.getAgentVersionString(agent); + let allowableAgentVersion = semverInc(agentVersion, 'minor'); - const majorVersionMismatch = agentMajorVersion !== serverMajorVersion; - - // We should warn user if their current GitLab and agent versions are more than 1 minor version apart: - const minorVersionMismatch = Math.abs(agentMinorVersion - serverMinorVersion) > 1; + const isServerPrerelease = Boolean(semverPrerelease(this.serverVersion)); + if (isServerPrerelease) { + allowableAgentVersion = semverInc(allowableAgentVersion, 'minor'); + } - return majorVersionMismatch || minorVersionMismatch; + return semverLt(allowableAgentVersion, this.serverVersion); }, getVersionPopoverTitle(agent) { diff --git a/app/assets/javascripts/notebook/cells/code/index.vue b/app/assets/javascripts/notebook/cells/code/index.vue index 64e801a7516..211a12208c1 100644 --- a/app/assets/javascripts/notebook/cells/code/index.vue +++ b/app/assets/javascripts/notebook/cells/code/index.vue @@ -51,7 +51,7 @@ export default { language="python" :code="code" :max-height="maxHeight" - class="gl-border" + class="gl-border gl-p-4!" /> </div> </template> diff --git a/app/assets/javascripts/notebook/cells/output/dataframe.vue b/app/assets/javascripts/notebook/cells/output/dataframe.vue new file mode 100644 index 00000000000..4fe02ee6edf --- /dev/null +++ b/app/assets/javascripts/notebook/cells/output/dataframe.vue @@ -0,0 +1,46 @@ +<script> +import JSONTable from '~/behaviors/components/json_table.vue'; +import Prompt from '../prompt.vue'; +import { convertHtmlTableToJson } from './dataframe_util'; + +export default { + name: 'DataframeOutput', + components: { + Prompt, + JSONTable, + }, + props: { + count: { + type: Number, + required: true, + }, + rawCode: { + type: String, + required: true, + }, + index: { + type: Number, + required: true, + }, + }, + computed: { + showOutput() { + return this.index === 0; + }, + dataframeAsJSONTable() { + return { + ...convertHtmlTableToJson(this.rawCode), + caption: '', + hasFilter: true, + }; + }, + }, +}; +</script> + +<template> + <div class="output"> + <prompt type="Out" :count="count" :show-output="showOutput" /> + <j-s-o-n-table v-bind="dataframeAsJSONTable" class="gl-overflow-auto" /> + </div> +</template> diff --git a/app/assets/javascripts/notebook/cells/output/dataframe_util.js b/app/assets/javascripts/notebook/cells/output/dataframe_util.js new file mode 100644 index 00000000000..2fdaaced0b9 --- /dev/null +++ b/app/assets/javascripts/notebook/cells/output/dataframe_util.js @@ -0,0 +1,44 @@ +import { sanitize } from '~/lib/dompurify'; + +/** + * Converts a dataframe in the output of a Jupyter Notebook cell to a json object + * + * @param {string} input - the dataframe + * @param {DOMParser} parser - the html parser + * @returns {Object} The converted JSON object with an `items` property containing the rows. + */ +export function convertHtmlTableToJson(input, domParser) { + const parser = domParser || new DOMParser(); + const htmlDoc = parser.parseFromString(sanitize(input), 'text/html'); + + if (!htmlDoc) return { fields: [], items: [] }; + + const columnNames = [...htmlDoc.querySelectorAll('table > thead th')].map( + (head) => head.innerText, + ); + + if (!columnNames) return { fields: [], items: [] }; + + const itemValues = [...htmlDoc.querySelectorAll('table > tbody > tr')].map((row) => + [...row.querySelectorAll('td')].map((item) => item.innerText), + ); + + return { + fields: columnNames.map((column) => ({ + key: column === '' ? 'index' : column, + label: column, + sortable: true, + })), + items: itemValues.map((values, itemIndex) => ({ + index: itemIndex, + ...Object.fromEntries(values.map((value, index) => [columnNames[index + 1], value])), + })), + }; +} + +export function isDataframe(output) { + const htmlData = output.data['text/html']; + if (!htmlData) return false; + + return htmlData.slice(0, 20).some((line) => line.includes('dataframe')); +} diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue index 22bcb5dd66a..0437b85913b 100644 --- a/app/assets/javascripts/notebook/cells/output/index.vue +++ b/app/assets/javascripts/notebook/cells/output/index.vue @@ -5,6 +5,8 @@ import ImageOutput from './image.vue'; import LatexOutput from './latex.vue'; import MarkdownOutput from './markdown.vue'; import ErrorOutput from './error.vue'; +import DataframeOutput from './dataframe.vue'; +import { isDataframe } from './dataframe_util'; const TEXT_MARKDOWN = 'text/markdown'; const ERROR_OUTPUT_TYPE = 'error'; @@ -66,6 +68,8 @@ export default { return ImageOutput; } else if (output.data['image/jpeg']) { return ImageOutput; + } else if (isDataframe(output)) { + return DataframeOutput; } else if (output.data['text/html']) { return HtmlOutput; } else if (output.data['text/latex']) { diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 83237198f1e..e4025eb8b8d 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -491,7 +491,7 @@ span.idiff { // element stretching over multiple rows we instead create a repeating background image // for the line background: repeating-linear-gradient(to right, var(--gray-100, $gray-100), var(--gray-100, $gray-100) 1px, transparent 1px, transparent 14px); - background-size: calc(var(--level) * 14px); + background-size: calc(var(--level) * 14px) 100%; background-repeat: no-repeat; background-position: 14px; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index ee91d955019..e6c7e265cdb 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -291,9 +291,9 @@ .project-cell { @include gl-display-table-cell; - @include gl-border-b; @include gl-vertical-align-top; @include gl-py-4; + border-bottom: 1px solid $gray-50; } .project-row:last-of-type { diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 3c288c1d496..94470290082 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -295,6 +295,13 @@ module Types def detailed_merge_status ::MergeRequests::Mergeability::DetailedMergeStatusService.new(merge_request: object).execute end + + # This is temporary to fix a bug where `committers` is already loaded and memoized + # and calling it again with a certain GraphQL query can cause the Rails to to throw + # a ActiveRecord::ImmutableRelation error + def committers + object.commits.committers + end end end diff --git a/app/views/notify/access_token_created_email.html.haml b/app/views/notify/access_token_created_email.html.haml index 9eea8f44142..8216994f8fa 100644 --- a/app/views/notify/access_token_created_email.html.haml +++ b/app/views/notify/access_token_created_email.html.haml @@ -1,7 +1,7 @@ %p = _('Hi %{username}!') % { username: sanitize_name(@user.name) } %p - = html_escape(_('A new personal access token, named %{token_name}, has been created.')) % { token_name: @token_name } + = html_escape(_('A new personal access token, named %{code_start}%{token_name}%{code_end}, has been created.')) % { code_start: '<code>'.html_safe, token_name: @token_name, code_end: '</code>'.html_safe } %p - pat_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url } = html_escape(_('You can check it in your %{pat_link_start}personal access tokens%{pat_link_end} settings.')) % { pat_link_start: pat_link_start, pat_link_end: '</a>'.html_safe } |