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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/admin/deploy_keys/components/table.vue175
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/components/app.vue63
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue9
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/components/projects_page.vue46
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/constants.js4
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js2
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/index.js17
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/utils.js13
-rw-r--r--app/assets/javascripts/pages/groups/work_items/index.js3
-rw-r--r--app/assets/javascripts/projects/components/shared/delete_button.vue148
-rw-r--r--app/assets/javascripts/projects/components/shared/delete_modal.vue163
-rw-r--r--app/assets/javascripts/work_items/list/components/work_items_list_app.vue37
-rw-r--r--app/assets/javascripts/work_items/list/index.js16
-rw-r--r--app/controllers/graphql_controller.rb6
-rw-r--r--app/controllers/projects/tracing_controller.rb4
-rw-r--r--app/finders/deployments_finder.rb3
-rw-r--r--app/graphql/resolvers/concerns/resolves_merge_requests.rb2
-rw-r--r--app/graphql/types/ci/detailed_status_type.rb20
-rw-r--r--app/helpers/projects/observability_helper.rb10
-rw-r--r--app/policies/ci/build_policy.rb11
-rw-r--r--app/views/groups/work_items/index.html.haml2
-rw-r--r--app/views/projects/tracing/show.html.haml5
22 files changed, 506 insertions, 253 deletions
diff --git a/app/assets/javascripts/admin/deploy_keys/components/table.vue b/app/assets/javascripts/admin/deploy_keys/components/table.vue
index 134498af348..ac963a12afb 100644
--- a/app/assets/javascripts/admin/deploy_keys/components/table.vue
+++ b/app/assets/javascripts/admin/deploy_keys/components/table.vue
@@ -1,5 +1,14 @@
<script>
-import { GlTable, GlButton, GlPagination, GlLoadingIcon, GlEmptyState, GlModal } from '@gitlab/ui';
+import {
+ GlCard,
+ GlTable,
+ GlButton,
+ GlPagination,
+ GlIcon,
+ GlLoadingIcon,
+ GlEmptyState,
+ GlModal,
+} from '@gitlab/ui';
import { __ } from '~/locale';
import Api, { DEFAULT_PER_PAGE } from '~/api';
@@ -15,7 +24,7 @@ export default {
newDeployKeyButtonText: __('New deploy key'),
emptyStateTitle: __('No public deploy keys'),
emptyStateDescription: __(
- 'Deploy keys grant read/write access to all repositories in your instance',
+ 'Deploy keys grant read/write access to all repositories in your instance, start by creating a new one above.',
),
delete: __('Delete deploy key'),
edit: __('Edit deploy key'),
@@ -37,10 +46,12 @@ export default {
{
key: 'fingerprint_sha256',
label: __('Fingerprint (SHA256)'),
+ tdClass: 'gl-md-max-w-26',
},
{
key: 'fingerprint',
label: __('Fingerprint (MD5)'),
+ tdClass: 'gl-md-max-w-26',
},
{
key: 'projects',
@@ -75,10 +86,12 @@ export default {
csrf,
DEFAULT_PER_PAGE,
components: {
+ GlCard,
GlTable,
GlButton,
GlPagination,
TimeAgoTooltip,
+ GlIcon,
GlLoadingIcon,
GlEmptyState,
GlModal,
@@ -177,85 +190,105 @@ export default {
</script>
<template>
- <div>
- <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-py-5">
- <h4 class="gl-m-0">
- {{ $options.i18n.pageTitle }}
- </h4>
- <gl-button variant="confirm" :href="createPath" data-testid="new-deploy-key-button">{{
- $options.i18n.newDeployKeyButtonText
- }}</gl-button>
- </div>
- <template v-if="shouldShowTable">
- <gl-table
- :busy="loading"
- :items="items"
- :fields="$options.fields"
- stacked="lg"
- data-testid="deploy-keys-list"
- >
- <template #table-busy>
- <gl-loading-icon size="lg" class="gl-my-5" />
- </template>
+ <gl-card
+ class="gl-new-card gl-overflow-hidden"
+ header-class="gl-new-card-header"
+ body-class="gl-new-card-body gl-overflow-hidden gl-px-0"
+ >
+ <template #header>
+ <div class="gl-new-card-title-wrapper">
+ <h3 class="gl-new-card-title">{{ $options.i18n.pageTitle }}</h3>
+ <span class="gl-new-card-count">
+ <gl-icon name="key" class="gl-mr-2" />
+ {{ totalItems }}
+ </span>
+ </div>
+ <div class="gl-new-card-actions">
+ <gl-button size="small" :href="createPath" data-testid="new-deploy-key-button">{{
+ $options.i18n.newDeployKeyButtonText
+ }}</gl-button>
+ </div>
+ </template>
- <template #cell(projects)="{ item: { projects } }">
- <a
- v-for="project in projects"
- :key="project.id"
- :href="projectHref(project)"
- class="gl-display-block"
- >{{ project.name_with_namespace }}</a
- >
- </template>
+ <gl-table
+ v-if="shouldShowTable"
+ :busy="loading"
+ :items="items"
+ :fields="$options.fields"
+ stacked="md"
+ data-testid="deploy-keys-list"
+ class="gl-mt-n1 gl-mb-n2"
+ >
+ <template #table-busy>
+ <gl-loading-icon size="sm" class="gl-my-5" />
+ </template>
- <template #cell(fingerprint_sha256)="{ item: { fingerprint_sha256 } }">
- <span v-if="fingerprint_sha256" class="monospace">{{ fingerprint_sha256 }}</span>
- </template>
+ <template #cell(projects)="{ item: { projects } }">
+ <a
+ v-for="project in projects"
+ :key="project.id"
+ :href="projectHref(project)"
+ class="gl-display-block"
+ >{{ project.name_with_namespace }}</a
+ >
+ </template>
+ <template #cell(fingerprint_sha256)="{ item: { fingerprint_sha256 } }">
+ <div
+ v-if="fingerprint_sha256"
+ class="gl-font-monospace gl-text-truncate"
+ :title="fingerprint_sha256"
+ >
+ {{ fingerprint_sha256 }}
+ </div>
+ </template>
- <template #cell(fingerprint)="{ item: { fingerprint } }">
- <span v-if="fingerprint" class="monospace">{{ fingerprint }}</span>
- </template>
+ <template #cell(fingerprint)="{ item: { fingerprint } }">
+ <div v-if="fingerprint" class="gl-font-monospace gl-text-truncate" :title="fingerprint">
+ {{ fingerprint }}
+ </div>
+ </template>
- <template #cell(created)="{ item: { created } }">
- <time-ago-tooltip :time="created" />
- </template>
+ <template #cell(created)="{ item: { created } }">
+ <time-ago-tooltip :time="created" />
+ </template>
- <template #head(actions)="{ label }">
- <span class="gl-sr-only">{{ label }}</span>
- </template>
+ <template #head(actions)="{ label }">
+ <span class="gl-sr-only">{{ label }}</span>
+ </template>
- <template #cell(actions)="{ item: { id } }">
- <gl-button
- icon="pencil"
- :aria-label="$options.i18n.edit"
- :href="editHref(id)"
- class="gl-mr-2"
- />
- <gl-button
- variant="danger"
- icon="remove"
- :aria-label="$options.i18n.delete"
- @click="handleDeleteClick(id)"
- />
- </template>
- </gl-table>
- <gl-pagination
- v-if="!loading"
- v-model="page"
- :per-page="$options.DEFAULT_PER_PAGE"
- :total-items="totalItems"
- :next-text="$options.i18n.pagination.next"
- :prev-text="$options.i18n.pagination.prev"
- align="center"
- />
- </template>
+ <template #cell(actions)="{ item: { id } }">
+ <gl-button
+ icon="pencil"
+ size="small"
+ :aria-label="$options.i18n.edit"
+ :href="editHref(id)"
+ class="gl-mr-2"
+ />
+ <gl-button
+ variant="danger"
+ category="secondary"
+ icon="remove"
+ size="small"
+ :aria-label="$options.i18n.delete"
+ @click="handleDeleteClick(id)"
+ />
+ </template>
+ </gl-table>
<gl-empty-state
v-else
:svg-path="emptyStateSvgPath"
:title="$options.i18n.emptyStateTitle"
:description="$options.i18n.emptyStateDescription"
- :primary-button-text="$options.i18n.newDeployKeyButtonText"
- :primary-button-link="createPath"
+ />
+ <gl-pagination
+ v-if="!loading"
+ v-model="page"
+ :per-page="$options.DEFAULT_PER_PAGE"
+ :total-items="totalItems"
+ :next-text="$options.i18n.pagination.next"
+ :prev-text="$options.i18n.pagination.prev"
+ align="center"
+ class="gl-mt-5"
/>
<gl-modal
:modal-id="$options.modal.id"
@@ -273,5 +306,5 @@ export default {
</form>
{{ $options.i18n.modal.body }}
</gl-modal>
- </div>
+ </gl-card>
</template>
diff --git a/app/assets/javascripts/organizations/groups_and_projects/components/app.vue b/app/assets/javascripts/organizations/groups_and_projects/components/app.vue
index 2b42c821cd5..43620872cd7 100644
--- a/app/assets/javascripts/organizations/groups_and_projects/components/app.vue
+++ b/app/assets/javascripts/organizations/groups_and_projects/components/app.vue
@@ -1,53 +1,27 @@
<script>
-import { GlLoadingIcon } from '@gitlab/ui';
-import { __, s__ } from '~/locale';
-import ProjectsList from '~/vue_shared/components/projects_list/projects_list.vue';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { createAlert } from '~/alert';
-import projectsQuery from '../graphql/queries/projects.query.graphql';
+import { __ } from '~/locale';
+import { DISPLAY_QUERY_GROUPS, DISPLAY_QUERY_PROJECTS } from '../constants';
+import GroupsPage from './groups_page.vue';
+import ProjectsPage from './projects_page.vue';
export default {
i18n: {
pageTitle: __('Groups and projects'),
- errorMessage: s__(
- 'Organization|An error occurred loading the projects. Please refresh the page to try again.',
- ),
- },
- components: {
- ProjectsList,
- GlLoadingIcon,
- },
- data() {
- return {
- projects: [],
- };
- },
- apollo: {
- projects: {
- query: projectsQuery,
- update(data) {
- return data.organization.projects.nodes;
- },
- error(error) {
- createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
- },
- },
},
computed: {
- formattedProjects() {
- return this.projects.map(({ id, nameWithNamespace, accessLevel, ...project }) => ({
- ...project,
- id: getIdFromGraphQLId(id),
- name: nameWithNamespace,
- permissions: {
- projectAccess: {
- accessLevel: accessLevel.integerValue,
- },
- },
- }));
- },
- isLoading() {
- return this.$apollo.queries.projects?.loading;
+ routerView() {
+ const { display } = this.$route.query;
+
+ switch (display) {
+ case DISPLAY_QUERY_GROUPS:
+ return GroupsPage;
+
+ case DISPLAY_QUERY_PROJECTS:
+ return ProjectsPage;
+
+ default:
+ return GroupsPage;
+ }
},
},
};
@@ -56,7 +30,6 @@ export default {
<template>
<div>
<h1 class="gl-font-size-h-display">{{ $options.i18n.pageTitle }}</h1>
- <gl-loading-icon v-if="isLoading" class="gl-mt-5" size="md" />
- <projects-list v-else :projects="formattedProjects" show-project-icon />
+ <component :is="routerView" />
</div>
</template>
diff --git a/app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue b/app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue
new file mode 100644
index 00000000000..b723cd98ce4
--- /dev/null
+++ b/app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue
@@ -0,0 +1,9 @@
+<script>
+export default {};
+</script>
+
+<template>
+ <div>
+ <!-- Intentionally empty. Will be implemented in future commits. -->
+ </div>
+</template>
diff --git a/app/assets/javascripts/organizations/groups_and_projects/components/projects_page.vue b/app/assets/javascripts/organizations/groups_and_projects/components/projects_page.vue
new file mode 100644
index 00000000000..d6958ee996e
--- /dev/null
+++ b/app/assets/javascripts/organizations/groups_and_projects/components/projects_page.vue
@@ -0,0 +1,46 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import ProjectsList from '~/vue_shared/components/projects_list/projects_list.vue';
+import { createAlert } from '~/alert';
+import projectsQuery from '../graphql/queries/projects.query.graphql';
+import { formatProjects } from '../utils';
+
+export default {
+ i18n: {
+ errorMessage: s__(
+ 'Organization|An error occurred loading the projects. Please refresh the page to try again.',
+ ),
+ },
+ components: {
+ ProjectsList,
+ GlLoadingIcon,
+ },
+ data() {
+ return {
+ projects: [],
+ };
+ },
+ apollo: {
+ projects: {
+ query: projectsQuery,
+ update(data) {
+ return formatProjects(data.organization.projects.nodes);
+ },
+ error(error) {
+ createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
+ },
+ },
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.projects.loading;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-loading-icon v-if="isLoading" class="gl-mt-5" size="md" />
+ <projects-list v-else :projects="projects" show-project-icon />
+</template>
diff --git a/app/assets/javascripts/organizations/groups_and_projects/constants.js b/app/assets/javascripts/organizations/groups_and_projects/constants.js
new file mode 100644
index 00000000000..c816ea27342
--- /dev/null
+++ b/app/assets/javascripts/organizations/groups_and_projects/constants.js
@@ -0,0 +1,4 @@
+export const DISPLAY_QUERY_GROUPS = 'groups';
+export const DISPLAY_QUERY_PROJECTS = 'projects';
+
+export const ORGANIZATION_ROOT_ROUTE_NAME = 'root';
diff --git a/app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js b/app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js
index 794410c2a78..e3e0529d6d9 100644
--- a/app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js
+++ b/app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js
@@ -1,4 +1,4 @@
-import { organizationProjects } from 'jest/organizations/groups_and_projects/components/mock_data';
+import { organizationProjects } from 'jest/organizations/groups_and_projects/mock_data';
export default {
Query: {
diff --git a/app/assets/javascripts/organizations/groups_and_projects/index.js b/app/assets/javascripts/organizations/groups_and_projects/index.js
index d0790bcc040..f3f15c635f1 100644
--- a/app/assets/javascripts/organizations/groups_and_projects/index.js
+++ b/app/assets/javascripts/organizations/groups_and_projects/index.js
@@ -1,22 +1,39 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import VueRouter from 'vue-router';
import createDefaultClient from '~/lib/graphql';
import resolvers from './graphql/resolvers';
import App from './components/app.vue';
+import { ORGANIZATION_ROOT_ROUTE_NAME } from './constants';
+
+export const createRouter = () => {
+ const routes = [{ path: '/', name: ORGANIZATION_ROOT_ROUTE_NAME }];
+
+ const router = new VueRouter({
+ routes,
+ base: '/',
+ mode: 'history',
+ });
+
+ return router;
+};
export const initOrganizationsGroupsAndProjects = () => {
const el = document.getElementById('js-organizations-groups-and-projects');
if (!el) return false;
+ Vue.use(VueRouter);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(resolvers),
});
+ const router = createRouter();
return new Vue({
el,
name: 'OrganizationsGroupsAndProjects',
apolloProvider,
+ router,
render(createElement) {
return createElement(App);
},
diff --git a/app/assets/javascripts/organizations/groups_and_projects/utils.js b/app/assets/javascripts/organizations/groups_and_projects/utils.js
new file mode 100644
index 00000000000..853a8543c1b
--- /dev/null
+++ b/app/assets/javascripts/organizations/groups_and_projects/utils.js
@@ -0,0 +1,13 @@
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+
+export const formatProjects = (projects) =>
+ projects.map(({ id, nameWithNamespace, accessLevel, ...project }) => ({
+ ...project,
+ id: getIdFromGraphQLId(id),
+ name: nameWithNamespace,
+ permissions: {
+ projectAccess: {
+ accessLevel: accessLevel.integerValue,
+ },
+ },
+ }));
diff --git a/app/assets/javascripts/pages/groups/work_items/index.js b/app/assets/javascripts/pages/groups/work_items/index.js
new file mode 100644
index 00000000000..a95070b1857
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/work_items/index.js
@@ -0,0 +1,3 @@
+import { mountWorkItemsListApp } from '~/work_items/list';
+
+mountWorkItemsListApp();
diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue
index 06c0230c8e0..c749034d2a8 100644
--- a/app/assets/javascripts/projects/components/shared/delete_button.vue
+++ b/app/assets/javascripts/projects/components/shared/delete_button.vue
@@ -1,19 +1,14 @@
<script>
-import { GlModal, GlModalDirective, GlFormInput, GlButton, GlAlert, GlSprintf } from '@gitlab/ui';
-import { uniqueId } from 'lodash';
+import { GlButton, GlForm } from '@gitlab/ui';
import csrf from '~/lib/utils/csrf';
import { __ } from '~/locale';
+import DeleteModal from './delete_modal.vue';
export default {
components: {
- GlAlert,
- GlModal,
- GlFormInput,
GlButton,
- GlSprintf,
- },
- directives: {
- GlModal: GlModalDirective,
+ GlForm,
+ DeleteModal,
},
props: {
confirmPhrase: {
@@ -47,139 +42,54 @@ export default {
},
data() {
return {
- userInput: null,
- modalId: uniqueId('delete-project-modal-'),
+ isModalVisible: false,
};
},
computed: {
- confirmDisabled() {
- return this.userInput !== this.confirmPhrase;
- },
csrfToken() {
return csrf.token;
},
- modalActionProps() {
- return {
- primary: {
- text: __('Yes, delete project'),
- attributes: {
- variant: 'danger',
- disabled: this.confirmDisabled,
- 'data-qa-selector': 'confirm_delete_button',
- },
- },
- cancel: {
- text: __('Cancel, keep project'),
- },
- };
- },
},
methods: {
submitForm() {
- this.$refs.form.submit();
+ this.$refs.form.$el.submit();
+ },
+ onButtonClick() {
+ this.isModalVisible = true;
},
},
- strings: {
+ i18n: {
deleteProject: __('Delete project'),
- title: __('Are you absolutely sure?'),
- confirmText: __('Enter the following to confirm:'),
- isForkAlertTitle: __('You are about to delete this forked project containing:'),
- isNotForkAlertTitle: __('You are about to delete this project containing:'),
- isForkAlertBody: __('This process deletes the project repository and all related resources.'),
- isNotForkAlertBody: __(
- 'This project is %{strongStart}NOT%{strongEnd} a fork. This process deletes the project repository and all related resources.',
- ),
- isNotForkMessage: __(
- 'This project is %{strongStart}NOT%{strongEnd} a fork, and has the following:',
- ),
},
};
</script>
<template>
- <form ref="form" :action="formPath" method="post">
+ <gl-form ref="form" :action="formPath" method="post">
<input type="hidden" name="_method" value="delete" />
<input :value="csrfToken" type="hidden" name="authenticity_token" />
+ <delete-modal
+ v-model="isModalVisible"
+ :confirm-phrase="confirmPhrase"
+ :is-fork="isFork"
+ :issues-count="issuesCount"
+ :merge-requests-count="mergeRequestsCount"
+ :forks-count="forksCount"
+ :stars-count="starsCount"
+ @primary="submitForm"
+ >
+ <template #modal-footer>
+ <slot name="modal-footer"></slot>
+ </template>
+ </delete-modal>
+
<gl-button
- v-gl-modal="modalId"
category="primary"
variant="danger"
data-qa-selector="delete_button"
- >{{ $options.strings.deleteProject }}</gl-button
+ @click="onButtonClick"
+ >{{ $options.i18n.deleteProject }}</gl-button
>
-
- <gl-modal
- ref="removeModal"
- :modal-id="modalId"
- ok-variant="danger"
- footer-class="gl-bg-gray-10 gl-p-5"
- title-class="gl-text-red-500"
- :action-primary="modalActionProps.primary"
- :action-cancel="modalActionProps.cancel"
- @ok="submitForm"
- >
- <template #modal-title>{{ $options.strings.title }}</template>
- <div>
- <gl-alert class="gl-mb-5" variant="danger" :dismissible="false">
- <h4 v-if="isFork" data-testid="delete-alert-title" class="gl-alert-title">
- {{ $options.strings.isForkAlertTitle }}
- </h4>
- <h4 v-else data-testid="delete-alert-title" class="gl-alert-title">
- {{ $options.strings.isNotForkAlertTitle }}
- </h4>
- <ul>
- <li>
- <gl-sprintf :message="n__('%d issue', '%d issues', issuesCount)">
- <template #issuesCount>{{ issuesCount }}</template>
- </gl-sprintf>
- </li>
- <li>
- <gl-sprintf
- :message="n__('%d merge requests', '%d merge requests', mergeRequestsCount)"
- >
- <template #mergeRequestsCount>{{ mergeRequestsCount }}</template>
- </gl-sprintf>
- </li>
- <li>
- <gl-sprintf :message="n__('%d fork', '%d forks', forksCount)">
- <template #forksCount>{{ forksCount }}</template>
- </gl-sprintf>
- </li>
- <li>
- <gl-sprintf :message="n__('%d star', '%d stars', starsCount)">
- <template #starsCount>{{ starsCount }}</template>
- </gl-sprintf>
- </li>
- </ul>
- <gl-sprintf
- v-if="isFork"
- data-testid="delete-alert-body"
- :message="$options.strings.isForkAlertBody"
- />
- <gl-sprintf
- v-else
- data-testid="delete-alert-body"
- :message="$options.strings.isNotForkAlertBody"
- >
- <template #strong="{ content }">
- <strong>{{ content }}</strong>
- </template>
- </gl-sprintf>
- </gl-alert>
- <p class="gl-mb-1">{{ $options.strings.confirmText }}</p>
- <p>
- <code class="gl-white-space-pre-wrap">{{ confirmPhrase }}</code>
- </p>
- <gl-form-input
- id="confirm_name_input"
- v-model="userInput"
- name="confirm_name_input"
- type="text"
- data-qa-selector="confirm_name_field"
- />
- <slot name="modal-footer"></slot>
- </div>
- </gl-modal>
- </form>
+ </gl-form>
</template>
diff --git a/app/assets/javascripts/projects/components/shared/delete_modal.vue b/app/assets/javascripts/projects/components/shared/delete_modal.vue
new file mode 100644
index 00000000000..aded11ca92c
--- /dev/null
+++ b/app/assets/javascripts/projects/components/shared/delete_modal.vue
@@ -0,0 +1,163 @@
+<script>
+import { GlModal, GlAlert, GlSprintf, GlFormInput } from '@gitlab/ui';
+import uniqueId from 'lodash/uniqueId';
+import { __ } from '~/locale';
+
+export default {
+ i18n: {
+ deleteProject: __('Delete project'),
+ title: __('Are you absolutely sure?'),
+ confirmText: __('Enter the following to confirm:'),
+ isForkAlertTitle: __('You are about to delete this forked project containing:'),
+ isNotForkAlertTitle: __('You are about to delete this project containing:'),
+ isForkAlertBody: __('This process deletes the project repository and all related resources.'),
+ isNotForkAlertBody: __(
+ 'This project is %{strongStart}NOT%{strongEnd} a fork. This process deletes the project repository and all related resources.',
+ ),
+ isNotForkMessage: __(
+ 'This project is %{strongStart}NOT%{strongEnd} a fork, and has the following:',
+ ),
+ },
+ components: { GlModal, GlAlert, GlSprintf, GlFormInput },
+ model: {
+ prop: 'visible',
+ event: 'change',
+ },
+ props: {
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ confirmPhrase: {
+ type: String,
+ required: true,
+ },
+ isFork: {
+ type: Boolean,
+ required: true,
+ },
+ issuesCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ mergeRequestsCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ forksCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ starsCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ userInput: null,
+ modalId: uniqueId('delete-project-modal-'),
+ };
+ },
+ computed: {
+ confirmDisabled() {
+ return this.userInput !== this.confirmPhrase;
+ },
+ modalActionProps() {
+ return {
+ primary: {
+ text: __('Yes, delete project'),
+ attributes: {
+ variant: 'danger',
+ disabled: this.confirmDisabled,
+ 'data-qa-selector': 'confirm_delete_button',
+ },
+ },
+ cancel: {
+ text: __('Cancel, keep project'),
+ },
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ :visible="visible"
+ :modal-id="modalId"
+ footer-class="gl-bg-gray-10 gl-p-5"
+ title-class="gl-text-red-500"
+ :action-primary="modalActionProps.primary"
+ :action-cancel="modalActionProps.cancel"
+ @primary="$emit('primary', $event)"
+ @change="$emit('change', $event)"
+ >
+ <template #modal-title>{{ $options.i18n.title }}</template>
+ <div>
+ <gl-alert class="gl-mb-5" variant="danger" :dismissible="false">
+ <h4 v-if="isFork" data-testid="delete-alert-title" class="gl-alert-title">
+ {{ $options.i18n.isForkAlertTitle }}
+ </h4>
+ <h4 v-else data-testid="delete-alert-title" class="gl-alert-title">
+ {{ $options.i18n.isNotForkAlertTitle }}
+ </h4>
+ <ul>
+ <li v-if="issuesCount !== null">
+ <gl-sprintf :message="n__('%d issue', '%d issues', issuesCount)">
+ <template #issuesCount>{{ issuesCount }}</template>
+ </gl-sprintf>
+ </li>
+ <li v-if="mergeRequestsCount !== null">
+ <gl-sprintf
+ :message="n__('%d merge requests', '%d merge requests', mergeRequestsCount)"
+ >
+ <template #mergeRequestsCount>{{ mergeRequestsCount }}</template>
+ </gl-sprintf>
+ </li>
+ <li v-if="forksCount !== null">
+ <gl-sprintf :message="n__('%d fork', '%d forks', forksCount)">
+ <template #forksCount>{{ forksCount }}</template>
+ </gl-sprintf>
+ </li>
+ <li v-if="starsCount !== null">
+ <gl-sprintf :message="n__('%d star', '%d stars', starsCount)">
+ <template #starsCount>{{ starsCount }}</template>
+ </gl-sprintf>
+ </li>
+ </ul>
+ <gl-sprintf
+ v-if="isFork"
+ data-testid="delete-alert-body"
+ :message="$options.i18n.isForkAlertBody"
+ />
+ <gl-sprintf
+ v-else
+ data-testid="delete-alert-body"
+ :message="$options.i18n.isNotForkAlertBody"
+ >
+ <template #strong="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
+ <p class="gl-mb-1">{{ $options.i18n.confirmText }}</p>
+ <p>
+ <code class="gl-white-space-pre-wrap">{{ confirmPhrase }}</code>
+ </p>
+
+ <gl-form-input
+ id="confirm_name_input"
+ v-model="userInput"
+ name="confirm_name_input"
+ type="text"
+ data-qa-selector="confirm_name_field"
+ />
+ <slot name="modal-footer"></slot>
+ </div>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/work_items/list/components/work_items_list_app.vue b/app/assets/javascripts/work_items/list/components/work_items_list_app.vue
new file mode 100644
index 00000000000..4180d484357
--- /dev/null
+++ b/app/assets/javascripts/work_items/list/components/work_items_list_app.vue
@@ -0,0 +1,37 @@
+<script>
+import { STATUS_OPEN } from '~/issues/constants';
+import { __ } from '~/locale';
+import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
+import { issuableListTabs } from '~/vue_shared/issuable/list/constants';
+
+export default {
+ i18n: {
+ searchPlaceholder: __('Search or filter results...'),
+ },
+ issuableListTabs,
+ components: {
+ IssuableList,
+ },
+ data() {
+ return {
+ issues: [],
+ searchTokens: [],
+ sortOptions: [],
+ state: STATUS_OPEN,
+ };
+ },
+};
+</script>
+
+<template>
+ <issuable-list
+ :current-tab="state"
+ :issuables="issues"
+ namespace="work-items"
+ recent-searches-storage-key="issues"
+ :search-input-placeholder="$options.i18n.searchPlaceholder"
+ :search-tokens="searchTokens"
+ :sort-options="sortOptions"
+ :tabs="$options.issuableListTabs"
+ />
+</template>
diff --git a/app/assets/javascripts/work_items/list/index.js b/app/assets/javascripts/work_items/list/index.js
new file mode 100644
index 00000000000..5b701893471
--- /dev/null
+++ b/app/assets/javascripts/work_items/list/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import WorkItemsListApp from '~/work_items/list/components/work_items_list_app.vue';
+
+export const mountWorkItemsListApp = () => {
+ const el = document.querySelector('.js-work-items-list-root');
+
+ if (!el) {
+ return null;
+ }
+
+ return new Vue({
+ el,
+ name: 'WorkItemsListRoot',
+ render: (createComponent) => createComponent(WorkItemsListApp),
+ });
+};
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index e6e8cfc4020..29bc48f93e9 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -39,6 +39,7 @@ class GraphqlController < ApplicationController
before_action :track_jetbrains_bundled_usage
before_action :track_gitlab_cli_usage
before_action :track_visual_studio_usage
+ before_action :track_neovim_plugin_usage
before_action :disable_query_limiting
before_action :limit_query_size
@@ -190,6 +191,11 @@ class GraphqlController < ApplicationController
.track_api_request_when_trackable(user_agent: request.user_agent, user: current_user)
end
+ def track_neovim_plugin_usage
+ Gitlab::UsageDataCounters::NeovimPluginActivityUniqueCounter
+ .track_api_request_when_trackable(user_agent: request.user_agent, user: current_user)
+ end
+
def track_gitlab_cli_usage
Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter
.track_api_request_when_trackable(user_agent: request.user_agent, user: current_user)
diff --git a/app/controllers/projects/tracing_controller.rb b/app/controllers/projects/tracing_controller.rb
index d1218ebf344..45e773bf62b 100644
--- a/app/controllers/projects/tracing_controller.rb
+++ b/app/controllers/projects/tracing_controller.rb
@@ -10,6 +10,10 @@ module Projects
def index; end
+ def show
+ @trace_id = params[:id]
+ end
+
private
def check_tracing_enabled
diff --git a/app/finders/deployments_finder.rb b/app/finders/deployments_finder.rb
index 800158dfd0a..9881cb3fc74 100644
--- a/app/finders/deployments_finder.rb
+++ b/app/finders/deployments_finder.rb
@@ -25,7 +25,7 @@ class DeploymentsFinder
# performant with the other filtering/sorting parameters.
# The composed query could be significantly slower when the filtering and sorting columns are different.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/325627 for example.
- ALLOWED_SORT_VALUES = %w[id iid created_at updated_at ref finished_at].freeze
+ ALLOWED_SORT_VALUES = %w[id iid created_at updated_at finished_at].freeze
DEFAULT_SORT_VALUE = 'id'
ALLOWED_SORT_DIRECTIONS = %w[asc desc].freeze
@@ -128,7 +128,6 @@ class DeploymentsFinder
def build_sort_params
order_by = ALLOWED_SORT_VALUES.include?(params[:order_by]) ? params[:order_by] : DEFAULT_SORT_VALUE
- order_by = DEFAULT_SORT_VALUE if order_by == 'ref' && Feature.enabled?(:remove_deployments_api_ref_sort)
order_direction = ALLOWED_SORT_DIRECTIONS.include?(params[:sort]) ? params[:sort] : DEFAULT_SORT_DIRECTION
{ order_by => order_direction }
diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
index 1470129187d..e9e7ea9f77f 100644
--- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb
+++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
@@ -60,7 +60,7 @@ module ResolvesMergeRequests
pipelines: [:merge_request_diffs], # used by `recent_diff_head_shas` to load pipelines
committers: [merge_request_diff: [:merge_request_diff_commits]],
suggested_reviewers: [:predictions],
- diff_stats: [:latest_merge_request_diff]
+ diff_stats: [latest_merge_request_diff: [:merge_request_diff_commits]]
}
end
end
diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb
index 8bc50e974bb..e18770c2708 100644
--- a/app/graphql/types/ci/detailed_status_type.rb
+++ b/app/graphql/types/ci/detailed_status_type.rb
@@ -39,17 +39,15 @@ module Types
end
def action
- if object.has_action?
- {
- button_title: object.action_button_title,
- icon: object.action_icon,
- method: object.action_method,
- path: object.action_path,
- title: object.action_title
- }
- else
- nil
- end
+ return unless object.has_action?
+
+ {
+ button_title: object.action_button_title,
+ icon: object.action_icon,
+ method: object.action_method,
+ path: object.action_path,
+ title: object.action_title
+ }
end
end
# rubocop: enable Graphql/AuthorizeTypes
diff --git a/app/helpers/projects/observability_helper.rb b/app/helpers/projects/observability_helper.rb
index 24bc1928a36..4515fdb1bc3 100644
--- a/app/helpers/projects/observability_helper.rb
+++ b/app/helpers/projects/observability_helper.rb
@@ -9,5 +9,15 @@ module Projects
oauthUrl: Gitlab::Observability.oauth_url
})
end
+
+ def observability_tracing_details_model(project, trace_id)
+ Gitlab::Json.generate({
+ tracingIndexUrl: namespace_project_tracing_index_path(project.group, project),
+ traceId: trace_id,
+ tracingUrl: Gitlab::Observability.tracing_url(project),
+ provisioningUrl: Gitlab::Observability.provisioning_url(project),
+ oauthUrl: Gitlab::Observability.oauth_url
+ })
+ end
end
end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 73e4cbee54a..0f551234efe 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -73,17 +73,22 @@ module Ci
# Use admin_ci_minutes for detailed quota and usage reporting
# this is limited to total usage and total quota for a builds namespace
- rule { can_read_project_build }.enable :read_ci_minutes_limited_summary
+ rule { can_read_project_build }.policy do
+ enable :read_ci_minutes_limited_summary
+ enable :read_build_trace
+ end
- rule { can_read_project_build }.enable :read_build_trace
rule { debug_mode & ~project_update_build }.prevent :read_build_trace
# Authorizing the user to access to protected entities.
# There is a "jailbreak" mode to exceptionally bypass the authorization,
# however, you should NEVER allow it, rather suspect it's a wrong feature/product design.
rule { ~can?(:jailbreak) & (archived | (protected_ref & ~admin) | protected_environment) }.policy do
- prevent :update_build
prevent :update_commit_status
+ end
+
+ rule { ~can?(:jailbreak) & (archived | protected_ref | protected_environment) }.policy do
+ prevent :update_build
prevent :erase_build
end
diff --git a/app/views/groups/work_items/index.html.haml b/app/views/groups/work_items/index.html.haml
index 9aa90b913a2..1d508289b21 100644
--- a/app/views/groups/work_items/index.html.haml
+++ b/app/views/groups/work_items/index.html.haml
@@ -1 +1,3 @@
- page_title s_('WorkItem|Work items')
+
+.js-work-items-list-root
diff --git a/app/views/projects/tracing/show.html.haml b/app/views/projects/tracing/show.html.haml
new file mode 100644
index 00000000000..4ba316a0b5c
--- /dev/null
+++ b/app/views/projects/tracing/show.html.haml
@@ -0,0 +1,5 @@
+- page_title _('Trace Details')
+- add_to_breadcrumbs _('Tracing'), project_tracing_index_path(@project)
+
+#js-tracing-details{ data: { view_model: observability_tracing_details_model(@project, @trace_id) } }
+