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
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/organizations')
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/components/app.vue202
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue43
-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.js29
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/graphql/queries/groups.query.graphql22
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/graphql/queries/projects.query.graphql1
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js12
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/index.js17
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/utils.js23
9 files changed, 350 insertions, 45 deletions
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..10471cc1fdd 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,127 @@
<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 { GlCollapsibleListbox, GlSorting, GlSortingItem } from '@gitlab/ui';
+import { isEqual } from 'lodash';
+import { s__, __ } from '~/locale';
+import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
+import {
+ filterToQueryObject,
+ processFilters,
+ urlQueryToFilter,
+ prepareTokens,
+} from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
+import {
+ FILTERED_SEARCH_TERM,
+ TOKEN_EMPTY_SEARCH_TERM,
+} from '~/vue_shared/components/filtered_search_bar/constants';
+import {
+ DISPLAY_QUERY_GROUPS,
+ DISPLAY_QUERY_PROJECTS,
+ DISPLAY_LISTBOX_ITEMS,
+ SORT_DIRECTION_ASC,
+ SORT_DIRECTION_DESC,
+ SORT_ITEMS,
+ SORT_ITEM_CREATED,
+ FILTERED_SEARCH_TERM_KEY,
+} 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.',
- ),
+ searchInputPlaceholder: s__('Organization|Search or filter list'),
+ displayListboxHeaderText: __('Display'),
},
- components: {
- ProjectsList,
- GlLoadingIcon,
+ components: { FilteredSearchBar, GlCollapsibleListbox, GlSorting, GlSortingItem },
+ filteredSearch: {
+ tokens: [],
+ namespace: 'organization_groups_and_projects',
+ recentSearchesStorageKey: 'organization_groups_and_projects',
},
- 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 });
- },
+ displayListboxItems: DISPLAY_LISTBOX_ITEMS,
+ sortItems: SORT_ITEMS,
+ computed: {
+ routerView() {
+ const { display } = this.$route.query;
+
+ switch (display) {
+ case DISPLAY_QUERY_GROUPS:
+ return GroupsPage;
+
+ case DISPLAY_QUERY_PROJECTS:
+ return ProjectsPage;
+
+ default:
+ return GroupsPage;
+ }
+ },
+ activeSortItem() {
+ return this.$options.sortItems.find((sortItem) => sortItem.name === this.sortName);
+ },
+ sortName() {
+ return this.$route.query.sort_name || SORT_ITEM_CREATED.name;
+ },
+ isAscending() {
+ return this.$route.query.sort_direction !== SORT_DIRECTION_DESC;
+ },
+ sortText() {
+ return this.activeSortItem.text;
+ },
+ filteredSearchValue() {
+ const tokens = prepareTokens(
+ urlQueryToFilter(this.$route.query, {
+ filteredSearchTermKey: FILTERED_SEARCH_TERM_KEY,
+ filterNamesAllowList: [FILTERED_SEARCH_TERM],
+ }),
+ );
+
+ return tokens.length ? tokens : [TOKEN_EMPTY_SEARCH_TERM];
+ },
+ displayListboxSelected() {
+ const { display } = this.$route.query;
+
+ return [DISPLAY_QUERY_GROUPS, DISPLAY_QUERY_PROJECTS].includes(display)
+ ? display
+ : DISPLAY_QUERY_GROUPS;
},
},
- 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;
+ methods: {
+ pushQuery(query) {
+ const currentQuery = this.$route.query;
+
+ if (isEqual(currentQuery, query)) {
+ return;
+ }
+
+ this.$router.push({ query });
+ },
+ onDisplayListboxSelect(display) {
+ this.pushQuery({ display });
+ },
+ onSortItemClick(sortItem) {
+ if (this.$route.query.sort_name === sortItem.name) {
+ return;
+ }
+
+ this.pushQuery({ ...this.$route.query, sort_name: sortItem.name });
+ },
+ onSortDirectionChange(isAscending) {
+ this.pushQuery({
+ ...this.$route.query,
+ sort_direction: isAscending ? SORT_DIRECTION_ASC : SORT_DIRECTION_DESC,
+ });
+ },
+ onFilter(filters) {
+ const { display, sort_name, sort_direction } = this.$route.query;
+
+ this.pushQuery({
+ display,
+ sort_name,
+ sort_direction,
+ ...filterToQueryObject(processFilters(filters), {
+ filteredSearchTermKey: FILTERED_SEARCH_TERM_KEY,
+ }),
+ });
},
},
};
@@ -56,7 +130,49 @@ 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 />
+ <div class="gl-p-5 gl-bg-gray-10 gl-border-t gl-border-b">
+ <div class="gl-mx-n2 gl-my-n2 gl-md-display-flex">
+ <div class="gl-p-2 gl-flex-grow-1">
+ <filtered-search-bar
+ :namespace="$options.filteredSearch.namespace"
+ :tokens="$options.filteredSearch.tokens"
+ :initial-filter-value="filteredSearchValue"
+ sync-filter-and-sort
+ :recent-searches-storage-key="$options.filteredSearch.recentSearchesStorageKey"
+ :search-input-placeholder="$options.i18n.searchInputPlaceholder"
+ @onFilter="onFilter"
+ />
+ </div>
+ <div class="gl-p-2">
+ <gl-collapsible-listbox
+ :selected="displayListboxSelected"
+ :items="$options.displayListboxItems"
+ :header-text="$options.i18n.displayListboxHeaderText"
+ block
+ toggle-class="gl-md-w-30"
+ @select="onDisplayListboxSelect"
+ />
+ </div>
+ <div class="gl-p-2">
+ <gl-sorting
+ class="gl-display-flex"
+ dropdown-class="gl-w-full"
+ :text="sortText"
+ :is-ascending="isAscending"
+ @sortDirectionChange="onSortDirectionChange"
+ >
+ <gl-sorting-item
+ v-for="sortItem in $options.sortItems"
+ :key="sortItem.name"
+ :active="activeSortItem.name === sortItem.name"
+ @click="onSortItemClick(sortItem)"
+ >
+ {{ sortItem.text }}
+ </gl-sorting-item>
+ </gl-sorting>
+ </div>
+ </div>
+ </div>
+ <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..20db38403f7
--- /dev/null
+++ b/app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue
@@ -0,0 +1,43 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { createAlert } from '~/alert';
+import { s__ } from '~/locale';
+import GroupsList from '~/vue_shared/components/groups_list/groups_list.vue';
+import groupsQuery from '../graphql/queries/groups.query.graphql';
+import { formatGroups } from '../utils';
+
+export default {
+ i18n: {
+ errorMessage: s__(
+ 'Organization|An error occurred loading the groups. Please refresh the page to try again.',
+ ),
+ },
+ components: { GlLoadingIcon, GroupsList },
+ data() {
+ return {
+ groups: [],
+ };
+ },
+ apollo: {
+ groups: {
+ query: groupsQuery,
+ update(data) {
+ return formatGroups(data.organization.groups.nodes);
+ },
+ error(error) {
+ createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
+ },
+ },
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.groups.loading;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-loading-icon v-if="isLoading" class="gl-mt-5" size="md" />
+ <groups-list v-else :groups="groups" show-group-icon />
+</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..529caa666a0
--- /dev/null
+++ b/app/assets/javascripts/organizations/groups_and_projects/constants.js
@@ -0,0 +1,29 @@
+import { __ } from '~/locale';
+
+export const DISPLAY_QUERY_GROUPS = 'groups';
+export const DISPLAY_QUERY_PROJECTS = 'projects';
+
+export const ORGANIZATION_ROOT_ROUTE_NAME = 'root';
+
+export const FILTERED_SEARCH_TERM_KEY = 'search';
+
+export const DISPLAY_LISTBOX_ITEMS = [
+ {
+ value: DISPLAY_QUERY_GROUPS,
+ text: __('Groups'),
+ },
+ {
+ value: DISPLAY_QUERY_PROJECTS,
+ text: __('Projects'),
+ },
+];
+
+export const SORT_DIRECTION_ASC = 'asc';
+export const SORT_DIRECTION_DESC = 'desc';
+
+export const SORT_ITEM_CREATED = {
+ name: 'created',
+ text: __('Created'),
+};
+
+export const SORT_ITEMS = [SORT_ITEM_CREATED];
diff --git a/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/groups.query.graphql b/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/groups.query.graphql
new file mode 100644
index 00000000000..842c601e326
--- /dev/null
+++ b/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/groups.query.graphql
@@ -0,0 +1,22 @@
+query getOrganizationGroups {
+ organization @client {
+ id
+ groups {
+ nodes {
+ id
+ fullName
+ parent
+ webUrl
+ descriptionHtml
+ avatarUrl
+ descendantGroupsCount
+ projectsCount
+ groupMembersCount
+ visibility
+ accessLevel {
+ integerValue
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/projects.query.graphql b/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/projects.query.graphql
index b4cb8c607d4..2a7971e1106 100644
--- a/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/projects.query.graphql
+++ b/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/projects.query.graphql
@@ -15,6 +15,7 @@ query getOrganizationProjects {
descriptionHtml
issuesAccessLevel
forkingAccessLevel
+ isForked
accessLevel {
integerValue
}
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..8a375b28797 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,8 @@
-import { organizationProjects } from 'jest/organizations/groups_and_projects/components/mock_data';
+import {
+ organization,
+ organizationProjects,
+ organizationGroups,
+} from 'jest/organizations/groups_and_projects/mock_data';
export default {
Query: {
@@ -8,7 +12,11 @@ export default {
setTimeout(resolve, 1000);
});
- return organizationProjects;
+ return {
+ ...organization,
+ projects: organizationProjects,
+ groups: organizationGroups,
+ };
},
},
};
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..d2a4e05e806
--- /dev/null
+++ b/app/assets/javascripts/organizations/groups_and_projects/utils.js
@@ -0,0 +1,23 @@
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { ACTION_EDIT, ACTION_DELETE } from '~/vue_shared/components/projects_list/constants';
+
+export const formatProjects = (projects) =>
+ projects.map(({ id, nameWithNamespace, accessLevel, webUrl, ...project }) => ({
+ ...project,
+ id: getIdFromGraphQLId(id),
+ name: nameWithNamespace,
+ permissions: {
+ projectAccess: {
+ accessLevel: accessLevel.integerValue,
+ },
+ },
+ webUrl,
+ editPath: `${webUrl}/edit`,
+ actions: [ACTION_EDIT, ACTION_DELETE],
+ }));
+
+export const formatGroups = (groups) =>
+ groups.map(({ id, ...group }) => ({
+ ...group,
+ id: getIdFromGraphQLId(id),
+ }));