Commit 4c39dd11 authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 95ff19a6
......@@ -14,3 +14,41 @@ export const MutationOperationMode = {
Remove: 'REMOVE',
Replace: 'REPLACE',
};
/**
* Possible GraphQL entity types.
*/
export const TYPE_GROUP = 'Group';
/**
* Ids generated by GraphQL endpoints are usually in the format
* gid://gitlab/Groups/123. This method takes a type and an id
* and interpolates the 2 values into the expected GraphQL ID format.
*
* @param {String} type The entity type
* @param {String|Number} id The id value
* @returns {String}
*/
export const convertToGraphQLId = (type, id) => {
if (typeof type !== 'string') {
throw new TypeError(`type must be a string; got ${typeof type}`);
}
if (!['number', 'string'].includes(typeof id)) {
throw new TypeError(`id must be a number or string; got ${typeof id}`);
}
return `gid://gitlab/${type}/${id}`;
};
/**
* Ids generated by GraphQL endpoints are usually in the format
* gid://gitlab/Groups/123. This method takes a type and an
* array of ids and tranforms the array values into the expected
* GraphQL ID format.
*
* @param {String} type The entity type
* @param {Array} ids An array of id values
* @returns {Array}
*/
export const convertToGraphQLIds = (type, ids) => ids.map(id => convertToGraphQLId(type, id));
......@@ -5,29 +5,26 @@ import {
GlModal,
GlModalDirective,
GlTooltipDirective,
GlLink,
GlEmptyState,
GlTab,
GlTabs,
GlTable,
GlSprintf,
} from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import Tracking from '~/tracking';
import { s__ } from '~/locale';
import { objectToQueryString } from '~/lib/utils/common_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import PackageHistory from './package_history.vue';
import PackageTitle from './package_title.vue';
import PackagesListLoader from '../../shared/components/packages_list_loader.vue';
import PackageListRow from '../../shared/components/package_list_row.vue';
import { packageTypeToTrackCategory } from '../../shared/utils';
import { PackageType, TrackingActions, SHOW_DELETE_SUCCESS_ALERT } from '../../shared/constants';
import DependencyRow from './dependency_row.vue';
import AdditionalMetadata from './additional_metadata.vue';
import InstallationCommands from './installation_commands.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { __, s__ } from '~/locale';
import { PackageType, TrackingActions, SHOW_DELETE_SUCCESS_ALERT } from '../../shared/constants';
import { packageTypeToTrackCategory } from '../../shared/utils';
import { objectToQueryString } from '~/lib/utils/common_utils';
import PackageFiles from './package_files.vue';
export default {
name: 'PackagesApp',
......@@ -35,12 +32,9 @@ export default {
GlBadge,
GlButton,
GlEmptyState,
GlLink,
GlModal,
GlTab,
GlTabs,
GlTable,
FileIcon,
GlSprintf,
PackageTitle,
PackagesListLoader,
......@@ -49,12 +43,13 @@ export default {
PackageHistory,
AdditionalMetadata,
InstallationCommands,
PackageFiles,
},
directives: {
GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
},
mixins: [timeagoMixin, Tracking.mixin()],
mixins: [Tracking.mixin()],
trackingActions: { ...TrackingActions },
computed: {
...mapState([
......@@ -72,14 +67,6 @@ export default {
isValidPackage() {
return Boolean(this.packageEntity.name);
},
filesTableRows() {
return this.packageFiles.map(x => ({
name: x.file_name,
downloadPath: x.download_path,
size: this.formatSize(x.size),
created: x.created_at,
}));
},
tracking() {
return {
category: packageTypeToTrackCategory(this.packageEntity.package_type),
......@@ -128,22 +115,6 @@ export default {
`PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?`,
),
},
filesTableHeaderFields: [
{
key: 'name',
label: __('Name'),
tdClass: 'd-flex align-items-center',
},
{
key: 'size',
label: __('Size'),
},
{
key: 'created',
label: __('Created'),
class: 'text-right',
},
],
};
</script>
......@@ -185,35 +156,11 @@ export default {
<additional-metadata :package-entity="packageEntity" />
</div>
<template v-if="showFiles">
<h3 class="gl-font-lg gl-mt-5">{{ __('Files') }}</h3>
<gl-table
:fields="$options.filesTableHeaderFields"
:items="filesTableRows"
tbody-tr-class="js-file-row"
>
<template #cell(name)="{ item }">
<gl-link
:href="item.downloadPath"
class="js-file-download gl-relative"
@click="track($options.trackingActions.PULL_PACKAGE)"
>
<file-icon
:file-name="item.name"
css-classes="gl-relative file-icon"
class="gl-mr-1 gl-relative"
/>
<span class="gl-relative">{{ item.name }}</span>
</gl-link>
</template>
<template #cell(created)="{ item }">
<span v-gl-tooltip :title="tooltipTitle(item.created)">{{
timeFormatted(item.created)
}}</span>
</template>
</gl-table>
</template>
<package-files
v-if="showFiles"
:package-files="packageFiles"
@download-file="track($options.trackingActions.PULL_PACKAGE)"
/>
</gl-tab>
<gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab">
......
<script>
import { GlLink, GlTable } from '@gitlab/ui';
import { __ } from '~/locale';
import Tracking from '~/tracking';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
export default {
name: 'PackageFiles',
components: {
GlLink,
GlTable,
FileIcon,
TimeAgoTooltip,
},
mixins: [Tracking.mixin()],
props: {
packageFiles: {
type: Array,
required: false,
default: () => [],
},
},
computed: {
filesTableRows() {
return this.packageFiles.map(pf => ({
...pf,
size: this.formatSize(pf.size),
}));
},
},
methods: {
formatSize(size) {
return numberToHumanSize(size);
},
},
filesTableHeaderFields: [
{
key: 'name',
label: __('Name'),
tdClass: 'gl-display-flex gl-align-items-center',
},
{
key: 'size',
label: __('Size'),
},
{
key: 'created',
label: __('Created'),
class: 'gl-text-right',
},
],
};
</script>
<template>
<div>
<h3 class="gl-font-lg gl-mt-5">{{ __('Files') }}</h3>
<gl-table
:fields="$options.filesTableHeaderFields"
:items="filesTableRows"
:tbody-tr-attr="{ 'data-testid': 'file-row' }"
>
<template #cell(name)="{ item }">
<gl-link
:href="item.download_path"
class="gl-relative"
data-testid="download-link"
@click="$emit('download-file')"
>
<file-icon
:file-name="item.file_name"
css-classes="gl-relative file-icon"
class="gl-mr-1 gl-relative"
/>
<span class="gl-relative">{{ item.file_name }}</span>
</gl-link>
</template>
<template #cell(created)="{ item }">
<time-ago-tooltip :time="item.created_at" />
</template>
</gl-table>
</div>
</template>
<script>
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import { DEFAULT, LOAD_FAILURE } from '../../constants';
import getPipelineDetails from '../../graphql/queries/get_pipeline_details.query.graphql';
import PipelineGraph from './graph_component.vue';
import { unwrapPipelineData } from './utils';
export default {
name: 'PipelineGraphWrapper',
components: {
GlAlert,
GlLoadingIcon,
PipelineGraph,
},
inject: {
pipelineIid: {
default: '',
},
pipelineProjectPath: {
default: '',
},
},
data() {
return {
pipeline: null,
alertType: null,
showAlert: false,
};
},
errorTexts: {
[LOAD_FAILURE]: __('We are currently unable to fetch data for this pipeline.'),
[DEFAULT]: __('An unknown error occurred while loading this graph.'),
},
apollo: {
pipeline: {
query: getPipelineDetails,
variables() {
return {
projectPath: this.pipelineProjectPath,
iid: this.pipelineIid,
};
},
update(data) {
return unwrapPipelineData(this.pipelineIid, data);
},
error() {
this.reportFailure(LOAD_FAILURE);
},
},
},
computed: {
alert() {
switch (this.alertType) {
case LOAD_FAILURE:
return {
text: this.$options.errorTexts[LOAD_FAILURE],
variant: 'danger',
};
default:
return {
text: this.$options.errorTexts[DEFAULT],
variant: 'danger',
};
}
},
},
methods: {
hideAlert() {
this.showAlert = false;
},
reportFailure(type) {
this.showAlert = true;
this.failureType = type;
},
},
};
</script>
<template>
<gl-alert v-if="showAlert" :variant="alert.variant" @dismiss="hideAlert">
{{ alert.text }}
</gl-alert>
<gl-loading-icon
v-else-if="$apollo.queries.pipeline.loading"
class="gl-mx-auto gl-my-4"
size="lg"
/>
<pipeline-graph v-else :pipeline="pipeline" />
</template>
......@@ -29,11 +29,13 @@ export default {
</div>
<div class="col-12">
<div class="text-content">
<div class="gl-text-content">
<template v-if="canSetCi">
<h4 class="text-center">{{ s__('Pipelines|Build with confidence') }}</h4>
<h4 class="gl-text-center" data-testid="header-text">
{{ s__('Pipelines|Build with confidence') }}
</h4>
<p>
<p data-testid="info-text">
{{
s__(`Pipelines|Continuous Integration can help
catch bugs by running your tests automatically,
......@@ -42,12 +44,11 @@ export default {
}}
</p>
<div class="text-center">
<div class="gl-text-center">
<gl-button
:href="helpPagePath"
variant="info"
category="primary"
class="js-get-started-pipelines"
data-testid="get-started-pipelines"
>
{{ s__('Pipelines|Get started with Pipelines') }}
......@@ -55,7 +56,7 @@ export default {
</div>
</template>
<p v-else class="text-center">
<p v-else class="gl-text-center">
{{ s__('Pipelines|This project is not currently set up to run pipelines.') }}
</p>
</div>
......
query getPipelineDetails($projectPath: ID!, $iid: ID!) {
project(fullPath: $projectPath) {
pipeline(iid: $iid) {
id: iid
stages {
nodes {
name
status: detailedStatus {
action {
icon
path
title
}
}
groups {
nodes {
status: detailedStatus {
label
group
icon
}
name
size
jobs {
nodes {
name
scheduledAt
needs {
nodes {
name
}
}
status: detailedStatus {
icon
tooltip
hasDetails
detailsPath
group
action {
buttonTitle
icon
path
title
}
}
}
}
}
}
}
}
}
}
}
......@@ -149,7 +149,9 @@ export default async function() {
const { createPipelinesDetailApp } = await import(
/* webpackChunkName: 'createPipelinesDetailApp' */ './pipeline_details_graph'
);
createPipelinesDetailApp();
const { pipelineProjectPath, pipelineIid } = dataset;
createPipelinesDetailApp(SELECTORS.PIPELINE_DETAILS, pipelineProjectPath, pipelineIid);
} catch {
Flash(__('An error occurred while loading the pipeline.'));
}
......
const createPipelinesDetailApp = () => {
// Placeholder. See: https://gitlab.com/gitlab-org/gitlab/-/issues/223262
// eslint-disable-next-line no-useless-return
return;
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import PipelineGraphWrapper from './components/graph/graph_component_wrapper.vue';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
const createPipelinesDetailApp = (selector, pipelineProjectPath, pipelineIid) => {
// eslint-disable-next-line no-new
new Vue({
el: selector,
components: {
PipelineGraphWrapper,
},
apolloProvider,
provide: {
pipelineProjectPath,
pipelineIid,
},
render(createElement) {
return createElement(PipelineGraphWrapper);
},
});
};
export { createPipelinesDetailApp };
import { joinPaths } from '~/lib/utils/url_utility';
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
......@@ -12,5 +14,12 @@ export const pathGenerator = (imageDetails, ending = '?format=json') => {
return acc;
}, [])
.join('/');
return `/${basePath}/registry/repository/${imageDetails.id}/tags${ending}`;
return joinPaths(
window.gon.relative_url_root,
`/${basePath}`,
'/registry/repository/',
`${imageDetails.id}`,
`tags${ending}`,
);
};
......@@ -774,6 +774,15 @@ def predefined_variables
variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s)
variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s)
variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s)
if Feature.enabled?(:ci_mr_diff_variables, project)
diff = self.merge_request_diff
if diff.present?
variables.append(key: 'CI_MERGE_REQUEST_DIFF_ID', value: diff.id.to_s)
variables.append(key: 'CI_MERGE_REQUEST_DIFF_BASE_SHA', value: diff.base_commit_sha)
end
end
variables.concat(merge_request.predefined_variables)
end
......@@ -845,9 +854,15 @@ def all_merge_requests_by_recency
end
def same_family_pipeline_ids
::Gitlab::Ci::PipelineObjectHierarchy.new(
base_and_ancestors(same_project: true), options: { same_project: true }
).base_and_descendants.select(:id)
if Feature.enabled?(:ci_root_ancestor_for_pipeline_family, project, default_enabled: false)
::Gitlab::Ci::PipelineObjectHierarchy.new(
self.class.where(id: root_ancestor), options: { same_project: true }
).base_and_descendants.select(:id)
else
::Gitlab::Ci::PipelineObjectHierarchy.new(
base_and_ancestors(same_project: true), options: { same_project: true }
).base_and_descendants.select(:id)
end
end
def build_with_artifacts_in_self_and_descendants(name)
......@@ -869,6 +884,15 @@ def self_and_descendants
.base_and_descendants
end
def root_ancestor
return self unless child?
Gitlab::Ci::PipelineObjectHierarchy
.new(self.class.unscoped.where(id: id), options: { same_project: true })
.base_and_ancestors(hierarchy_order: :desc)
.first
end
def bridge_triggered?
source_bridge.present?
end
......@@ -878,7 +902,8 @@ def bridge_waiting?
end
def child?