Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6ab715991f
commit
603425e1f4
|
|
@ -0,0 +1,4 @@
|
|||
export const RESOURCE_TYPE_GROUPS = 'groups';
|
||||
export const RESOURCE_TYPE_PROJECTS = 'projects';
|
||||
|
||||
export const ORGANIZATION_ROOT_ROUTE_NAME = 'root';
|
||||
|
|
@ -13,9 +13,8 @@ import {
|
|||
FILTERED_SEARCH_TERM,
|
||||
TOKEN_EMPTY_SEARCH_TERM,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import { RESOURCE_TYPE_GROUPS, RESOURCE_TYPE_PROJECTS } from '../../constants';
|
||||
import {
|
||||
DISPLAY_QUERY_GROUPS,
|
||||
DISPLAY_QUERY_PROJECTS,
|
||||
DISPLAY_LISTBOX_ITEMS,
|
||||
SORT_DIRECTION_ASC,
|
||||
SORT_DIRECTION_DESC,
|
||||
|
|
@ -45,10 +44,10 @@ export default {
|
|||
const { display } = this.$route.query;
|
||||
|
||||
switch (display) {
|
||||
case DISPLAY_QUERY_GROUPS:
|
||||
case RESOURCE_TYPE_GROUPS:
|
||||
return GroupsPage;
|
||||
|
||||
case DISPLAY_QUERY_PROJECTS:
|
||||
case RESOURCE_TYPE_PROJECTS:
|
||||
return ProjectsPage;
|
||||
|
||||
default:
|
||||
|
|
@ -80,9 +79,9 @@ export default {
|
|||
displayListboxSelected() {
|
||||
const { display } = this.$route.query;
|
||||
|
||||
return [DISPLAY_QUERY_GROUPS, DISPLAY_QUERY_PROJECTS].includes(display)
|
||||
return [RESOURCE_TYPE_GROUPS, RESOURCE_TYPE_PROJECTS].includes(display)
|
||||
? display
|
||||
: DISPLAY_QUERY_GROUPS;
|
||||
: RESOURCE_TYPE_GROUPS;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ 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 = [
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import Vue from 'vue';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import VueRouter from 'vue-router';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import { ORGANIZATION_ROOT_ROUTE_NAME } from '../constants';
|
||||
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 }];
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
<script>
|
||||
import OrganizationAvatar from './organization_avatar.vue';
|
||||
import GroupsAndProjects from './groups_and_projects.vue';
|
||||
|
||||
export default {
|
||||
name: 'OrganizationShowApp',
|
||||
components: { OrganizationAvatar },
|
||||
components: { OrganizationAvatar, GroupsAndProjects },
|
||||
props: {
|
||||
organization: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
groupsAndProjectsOrganizationPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -16,5 +21,8 @@ export default {
|
|||
<template>
|
||||
<div class="gl-py-6">
|
||||
<organization-avatar :organization="organization" />
|
||||
<groups-and-projects
|
||||
:groups-and-projects-organization-path="groupsAndProjectsOrganizationPath"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
<script>
|
||||
import { GlCollapsibleListbox, GlLink } from '@gitlab/ui';
|
||||
import { isEqual } from 'lodash';
|
||||
import { s__, __ } from '~/locale';
|
||||
import { RESOURCE_TYPE_GROUPS, RESOURCE_TYPE_PROJECTS } from '../../constants';
|
||||
import { FILTER_FREQUENTLY_VISITED } from '../constants';
|
||||
import { buildDisplayListboxItem } from '../utils';
|
||||
|
||||
export default {
|
||||
name: 'OrganizationFrontPageGroupsAndProjects',
|
||||
i18n: {
|
||||
displayListboxLabel: __('Display'),
|
||||
viewAll: s__('Organization|View all'),
|
||||
},
|
||||
displayListboxLabelId: 'display-listbox-label',
|
||||
components: { GlCollapsibleListbox, GlLink },
|
||||
displayListboxItems: [
|
||||
buildDisplayListboxItem({
|
||||
filter: FILTER_FREQUENTLY_VISITED,
|
||||
resourceType: RESOURCE_TYPE_PROJECTS,
|
||||
text: s__('Organization|Frequently visited projects'),
|
||||
}),
|
||||
buildDisplayListboxItem({
|
||||
filter: FILTER_FREQUENTLY_VISITED,
|
||||
resourceType: RESOURCE_TYPE_GROUPS,
|
||||
text: s__('Organization|Frequently visited groups'),
|
||||
}),
|
||||
],
|
||||
props: {
|
||||
groupsAndProjectsOrganizationPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
displayListboxSelected() {
|
||||
const { display } = this.$route.query;
|
||||
const [{ value: fallbackSelected }] = this.$options.displayListboxItems;
|
||||
|
||||
return (
|
||||
this.$options.displayListboxItems.find(({ value }) => value === display)?.value ||
|
||||
fallbackSelected
|
||||
);
|
||||
},
|
||||
resourceTypeSelected() {
|
||||
return [RESOURCE_TYPE_PROJECTS, RESOURCE_TYPE_GROUPS].find((resourceType) =>
|
||||
this.displayListboxSelected.endsWith(resourceType),
|
||||
);
|
||||
},
|
||||
groupsAndProjectsOrganizationPathWithQueryParam() {
|
||||
return `${this.groupsAndProjectsOrganizationPath}?display=${this.resourceTypeSelected}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pushQuery(query) {
|
||||
const currentQuery = this.$route.query;
|
||||
|
||||
if (isEqual(currentQuery, query)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$router.push({ query });
|
||||
},
|
||||
onDisplayListboxSelect(display) {
|
||||
this.pushQuery({ display });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-mt-7">
|
||||
<div class="gl-display-flex gl-justify-content-space-between gl-align-items-center">
|
||||
<div>
|
||||
<label
|
||||
:id="$options.displayListboxLabelId"
|
||||
class="gl-display-block gl-mb-2"
|
||||
data-testid="label"
|
||||
>{{ $options.i18n.displayListboxLabel }}</label
|
||||
>
|
||||
<gl-collapsible-listbox
|
||||
block
|
||||
toggle-class="gl-w-30"
|
||||
:selected="displayListboxSelected"
|
||||
:items="$options.displayListboxItems"
|
||||
:toggle-aria-labelled-by="$options.displayListboxLabelId"
|
||||
@select="onDisplayListboxSelect"
|
||||
/>
|
||||
</div>
|
||||
<gl-link class="gl-mt-5" :href="groupsAndProjectsOrganizationPathWithQueryParam">{{
|
||||
$options.i18n.viewAll
|
||||
}}</gl-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const FILTER_FREQUENTLY_VISITED = 'frequently_visited';
|
||||
|
|
@ -1,6 +1,21 @@
|
|||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import { ORGANIZATION_ROOT_ROUTE_NAME } from '../constants';
|
||||
import App from './components/app.vue';
|
||||
|
||||
export const createRouter = () => {
|
||||
const routes = [{ path: '/', name: ORGANIZATION_ROOT_ROUTE_NAME }];
|
||||
|
||||
const router = new VueRouter({
|
||||
routes,
|
||||
base: '/',
|
||||
mode: 'history',
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
export const initOrganizationsShow = () => {
|
||||
const el = document.getElementById('js-organizations-show');
|
||||
|
||||
|
|
@ -9,13 +24,19 @@ export const initOrganizationsShow = () => {
|
|||
const {
|
||||
dataset: { appData },
|
||||
} = el;
|
||||
const { organization } = JSON.parse(appData);
|
||||
const { organization, groupsAndProjectsOrganizationPath } = convertObjectPropsToCamelCase(
|
||||
JSON.parse(appData),
|
||||
);
|
||||
|
||||
Vue.use(VueRouter);
|
||||
const router = createRouter();
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'OrganizationShowRoot',
|
||||
router,
|
||||
render(createElement) {
|
||||
return createElement(App, { props: { organization } });
|
||||
return createElement(App, { props: { organization, groupsAndProjectsOrganizationPath } });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
export const buildDisplayListboxItem = ({ filter, resourceType, text }) => ({
|
||||
text,
|
||||
value: `${filter}_${resourceType}`,
|
||||
});
|
||||
|
|
@ -38,7 +38,6 @@ class ApplicationController < ActionController::Base
|
|||
before_action :active_user_check, unless: :devise_controller?
|
||||
before_action :set_usage_stats_consent_flag
|
||||
before_action :check_impersonation_availability
|
||||
before_action :required_signup_info
|
||||
|
||||
# Make sure the `auth_user` is memoized so it can be logged, we do this after
|
||||
# all other before filters that could have set the user.
|
||||
|
|
@ -555,15 +554,6 @@ class ApplicationController < ActionController::Base
|
|||
def context_user
|
||||
auth_user if strong_memoized?(:auth_user)
|
||||
end
|
||||
|
||||
def required_signup_info
|
||||
return unless current_user
|
||||
return unless current_user.role_required?
|
||||
|
||||
store_location_for :user, request.fullpath
|
||||
|
||||
redirect_to users_sign_up_welcome_path
|
||||
end
|
||||
end
|
||||
|
||||
ApplicationController.prepend_mod
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ module VerifiesWithEmail
|
|||
|
||||
included do
|
||||
prepend_before_action :verify_with_email, only: :create, unless: -> { skip_verify_with_email? }
|
||||
skip_before_action :required_signup_info, only: :successful_verification
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ class ConfirmationsController < Devise::ConfirmationsController
|
|||
include GoogleAnalyticsCSP
|
||||
include GoogleSyndicationCSP
|
||||
|
||||
skip_before_action :required_signup_info
|
||||
prepend_before_action :check_recaptcha, only: :create
|
||||
before_action :load_recaptcha, only: :new
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ class PwaController < ApplicationController # rubocop:disable Gitlab/NamespacedC
|
|||
feature_category :navigation
|
||||
urgency :low
|
||||
|
||||
skip_before_action :authenticate_user!, :required_signup_info
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
def manifest
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module Registrations
|
|||
include ::Gitlab::Utils::StrongMemoize
|
||||
|
||||
layout 'minimal'
|
||||
skip_before_action :required_signup_info, :check_two_factor_requirement
|
||||
skip_before_action :check_two_factor_requirement
|
||||
|
||||
helper_method :welcome_update_params
|
||||
helper_method :onboarding_status
|
||||
|
|
@ -43,7 +43,7 @@ module Registrations
|
|||
end
|
||||
|
||||
def completed_welcome_step?
|
||||
current_user.role.present? && !current_user.setup_for_company.nil?
|
||||
!current_user.setup_for_company.nil?
|
||||
end
|
||||
|
||||
def update_params
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
accept_pending_invitations if new_user.persisted?
|
||||
|
||||
persist_accepted_terms_if_required(new_user)
|
||||
set_role_required(new_user)
|
||||
send_custom_confirmation_instructions
|
||||
track_weak_password_error(new_user, self.class.name, 'create')
|
||||
|
||||
|
|
@ -90,10 +89,6 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
Users::RespondToTermsService.new(new_user, terms).execute(accepted: true)
|
||||
end
|
||||
|
||||
def set_role_required(new_user)
|
||||
new_user.set_role_required! if new_user.persisted?
|
||||
end
|
||||
|
||||
def destroy_confirmation_valid?
|
||||
if current_user.confirm_deletion_with_password?
|
||||
current_user.valid_password?(params[:password])
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ module Organizations
|
|||
module OrganizationHelper
|
||||
def organization_show_app_data(organization)
|
||||
{
|
||||
organization: organization.slice(:id, :name)
|
||||
organization: organization.slice(:id, :name),
|
||||
groups_and_projects_organization_path: groups_and_projects_organization_path(organization)
|
||||
}.to_json
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2085,16 +2085,6 @@ class User < MainClusterwide::ApplicationRecord
|
|||
[last_activity, last_sign_in].compact.max
|
||||
end
|
||||
|
||||
REQUIRES_ROLE_VALUE = 99
|
||||
|
||||
def role_required?
|
||||
role_before_type_cast == REQUIRES_ROLE_VALUE
|
||||
end
|
||||
|
||||
def set_role_required!
|
||||
update_column(:role, REQUIRES_ROLE_VALUE)
|
||||
end
|
||||
|
||||
def dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil)
|
||||
callout = callouts_by_feature_name[feature_name]
|
||||
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ In some circumstances, like during [upgrades](replication/upgrading_the_geo_site
|
|||
|
||||
Pausing and resuming replication is done via a command line tool from the node in the secondary site where the `postgresql` service is enabled.
|
||||
|
||||
If `postgresql` is on a standalone database node, ensure that `gitlab.rb` on that node contains the configuration line `gitlab_rails['geo_node_name'] = 'node_name'`, where `node_name` is the same as the `geo_name_name` on the application node.
|
||||
If `postgresql` is on a standalone database node, ensure that `gitlab.rb` on that node contains the configuration line `gitlab_rails['geo_node_name'] = 'node_name'`, where `node_name` is the same as the `geo_node_name` on the application node.
|
||||
|
||||
**To Pause: (from secondary)**
|
||||
|
||||
|
|
|
|||
|
|
@ -44,9 +44,6 @@ Each image runs a specific version of macOS and Xcode.
|
|||
| `macos-12-xcode-14` | `GA` |
|
||||
| `macos-13-xcode-14` | `Beta` |
|
||||
|
||||
NOTE:
|
||||
If your job requires tooling or dependencies not available in our available images, those can only be installed in the job execution.
|
||||
|
||||
## Image update policy for macOS
|
||||
|
||||
macOS and Xcode follow a yearly release cadence, during which GitLab increments its versions synchronously. GitLab typically supports multiple versions of preinstalled tools. For more information, see
|
||||
|
|
@ -59,7 +56,7 @@ GitLab provides `stable` and `latest` macOS images that follow different update
|
|||
By definition, the `latest` images are always Beta.
|
||||
A `latest` image is not available.
|
||||
|
||||
### Release process
|
||||
### Image release process**
|
||||
|
||||
When Apple releases a new macOS version, GitLab releases both `stable` and `latest` images based on the OS in the next release. Both images are Beta.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Frontend Development Process
|
||||
|
||||
You can find more about the organization of the frontend team in the [handbook](https://about.gitlab.com/handbook/engineering/frontend/).
|
||||
|
||||
## Development Checklist
|
||||
|
||||
The idea is to remind us about specific topics during the time we build a new feature or start something. This is a common practice in other industries (like pilots) that also use standardized checklists to reduce problems early on.
|
||||
|
||||
Copy the content over to your issue or merge request and if something doesn't apply, remove it from your current list.
|
||||
|
||||
This checklist is intended to help us during development of bigger features/refactorings. It is not a "use it always and every point always matches" list.
|
||||
|
||||
Use your best judgment when to use it and contribute new points through merge requests if something comes to your mind.
|
||||
|
||||
```markdown
|
||||
### Frontend development
|
||||
|
||||
#### Planning development
|
||||
|
||||
- [ ] Check the current set weight of the issue, does it fit your estimate?
|
||||
- [ ] Are all [departments](https://about.gitlab.com/handbook/engineering/#engineering-teams) that are needed from your perspective already involved in the issue? (For example is UX missing?)
|
||||
- [ ] Is the specification complete? Are you missing decisions? How about error handling/defaults/edge cases? Take your time to understand the needed implementation and go through its flow.
|
||||
- [ ] Are all necessary UX specifications available that you will need to implement? Are there new UX components/patterns in the designs? Then contact the UI component team early on. How should error messages or validation be handled?
|
||||
- [ ] **Library usage** Use Vuex as soon as you have even a medium state to manage, use Vue router if you need to have different views internally and want to link from the outside. Check what libraries we already have for which occasions.
|
||||
- [ ] **Plan your implementation:**
|
||||
- [ ] **Architecture plan:** Create a plan aligned with GitLab's architecture, how you are going to do the implementation, for example Vue application setup and its components (through [onion skinning](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/35873#note_39994091)), Store structure and data flow, which existing Vue components can you reuse. It's a good idea to go through your plan with another engineer to refine it.
|
||||
- [ ] **Backend:** The best way is to kickoff the implementation in a call and discuss with the assigned Backend engineer what you will need from the backend and also when. Can you reuse existing API's? How is the performance with the planned architecture? Maybe create together a JSON mock object to already start with development.
|
||||
- [ ] **Communication:** It also makes sense to have for bigger features an own slack channel (normally called #f_{feature_name}) and even weekly demo calls with all people involved.
|
||||
- [ ] **Dependency Plan:** Are there big dependencies in the plan between you and others, then maybe create an execution diagram to show what is blocking which part and the order of the different parts.
|
||||
- [ ] **Task list:** Create a simple checklist of the subtasks that are needed for the implementation, also consider creating even sub issues. (for example show a comment, delete a comment, update a comment, etc.). This helps you and also everyone else following the implementation
|
||||
- [ ] **Keep it small** To make it easier for you and also all reviewers try to keep merge requests small and merge into a feature branch if needed. To accomplish that you need to plan that from the start. Different methods are:
|
||||
- [ ] **Skeleton based plan** Start with an MR that has the skeleton of the components with placeholder content. In following MRs you can fill the components with interactivity. This also makes it easier to spread out development on multiple people.
|
||||
- [ ] **Cookie Mode** Think about hiding the feature behind a cookie flag if the implementation is on top of existing features
|
||||
- [ ] **New route** Are you refactoring something big then you might consider adding a new route where you implement the new feature and when finished delete the current route and rename the new one. (for example 'merge_request' and 'new_merge_request')
|
||||
- [ ] **Setup** Is there any specific setup needed for your implementation (for example a kubernetes cluster)? Then let everyone know if it is not already mentioned where they can find documentation (if it doesn't exist - create it)
|
||||
- [ ] **Security** Are there any new security relevant implementations? Then contact the security team for an app security review. If you are not sure ask our [domain expert](https://about.gitlab.com/handbook/engineering/frontend/#frontend-domain-experts)
|
||||
|
||||
#### During development
|
||||
|
||||
- [ ] Check off tasks on your created task list to keep everyone updated on the progress
|
||||
- [ ] [Share your work early with reviewers/maintainers](#share-your-work-early)
|
||||
- [ ] Share your work with UXer and Product Manager with Screenshots and/or [GIF images](https://about.gitlab.com/handbook/product/making-gifs/). They are easy to create for you and keep them up to date.
|
||||
- [ ] If you are blocked on something let everyone on the issue know through a comment.
|
||||
- [ ] Are you unable to work on this issue for a longer period of time, also let everyone know.
|
||||
- [ ] **Documentation** Update/add docs for the new feature, see `docs/`. Ping one of the documentation experts/reviewers
|
||||
|
||||
#### Finishing development + Review
|
||||
|
||||
- [ ] **Keep it in the scope** Try to focus on the actual scope and avoid a scope creep during review and keep new things to new issues.
|
||||
- [ ] **Performance** Have you checked performance? For example do the same thing with 500 comments instead of 1. Document the tests and possible findings in the MR so a reviewer can directly see it.
|
||||
- [ ] Have you tested with a variety of our [supported browsers](../../install/requirements.md#supported-web-browsers)? You can use [browserstack](https://www.browserstack.com/) to be able to access a wide variety of browsers and operating systems.
|
||||
- [ ] Did you check the mobile view?
|
||||
- [ ] Check the built webpack bundle (For the report run `WEBPACK_REPORT=true gdk start`, then open `webpack-report/index.html`) if we have unnecessary bloat due to wrong references, including libraries multiple times, etc.. If you need help contact the webpack [domain expert](https://about.gitlab.com/handbook/engineering/frontend/#frontend-domain-experts)
|
||||
- [ ] **Tests** Not only greenfield tests - Test also all bad cases that come to your mind.
|
||||
- [ ] If you have multiple MRs then also smoke test against the final merge.
|
||||
- [ ] Are there any big changes on how and especially how frequently we use the API then let production know about it
|
||||
- [ ] Smoke test of the RC on dev., staging., canary deployments and .com
|
||||
- [ ] Follow up on issues that came out of the review. Create issues for discovered edge cases that should be covered in future iterations.
|
||||
```
|
||||
|
||||
### Code deletion checklist
|
||||
|
||||
When your merge request deletes code, it's important to also delete all
|
||||
related code that is no longer used.
|
||||
When deleting Haml and Vue code, check whether it contains the following types of
|
||||
code that is unused:
|
||||
|
||||
- CSS.
|
||||
|
||||
For example, we've deleted a Vue component that contained the `.mr-card` class, which is now unused.
|
||||
The `.mr-card` CSS rule set should then be deleted from `merge_requests.scss`.
|
||||
|
||||
- Ruby variables.
|
||||
|
||||
Deleting unused Ruby variables is important so we don't continue instantiating them with
|
||||
potentially expensive code.
|
||||
|
||||
For example, we've deleted a Haml template that used the `@total_count` Ruby variable.
|
||||
The `@total_count` variable was no longer used in the remaining templates for the page.
|
||||
The instantiation of `@total_count` in `issues_controller.rb` should then be deleted so that we
|
||||
don't make unnecessary database calls to calculate the count of issues.
|
||||
|
||||
- Ruby methods.
|
||||
|
||||
### Merge Request Review
|
||||
|
||||
With the purpose of being [respectful of others' time](https://about.gitlab.com/handbook/values/#be-respectful-of-others-time), follow these guidelines when asking for a review:
|
||||
|
||||
- Make sure your Merge Request:
|
||||
- milestone is set
|
||||
- at least the labels suggested by danger-bot are set
|
||||
- has a clear description
|
||||
- includes before/after screenshots if there is a UI change
|
||||
- pipeline is green
|
||||
- includes tests
|
||||
- includes a changelog entry (when necessary)
|
||||
- Before assigning to a maintainer, assign to a reviewer.
|
||||
- If you assigned a merge request or pinged someone directly, be patient because we work in different timezones and asynchronously. Unless the merge request is urgent (like fixing a broken default branch), don't DM or reassign the merge request before waiting for a 24-hour window.
|
||||
- If you have a question regarding your merge request/issue, make it on the merge request/issue. When we DM each other, we no longer have a SSOT and [no one else is able to contribute](https://about.gitlab.com/handbook/values/#public-by-default).
|
||||
- When you have a big **Draft** merge request with many changes, you're advised to get the review started before adding/removing significant code. Make sure it is assigned well before the release cut-off, as the reviewers/maintainers would always prioritize reviewing finished MRs before the **Draft** ones.
|
||||
- Make sure to remove the `Draft:` title before the last round of review.
|
||||
|
||||
### Share your work early
|
||||
|
||||
1. Before writing code, ensure your vision of the architecture is aligned with
|
||||
GitLab architecture.
|
||||
1. Add a diagram to the issue and ask a frontend maintainer in the Slack channel `#frontend_maintainers` about it.
|
||||
|
||||

|
||||
|
||||
1. Don't take more than one week between starting work on a feature and
|
||||
sharing a Merge Request with a reviewer or a maintainer.
|
||||
|
||||
### Vue features
|
||||
|
||||
1. Follow the steps in [Vue.js Best Practices](vue.md)
|
||||
1. Follow the style guide.
|
||||
1. Only a handful of people are allowed to merge Vue related features.
|
||||
Reach out to one of Vue experts early in this process.
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
This page will guide you through the Frontend development process and show you what a normal Merge Request cycle looks like. You can find more about the organization of the frontend team in the [handbook](https://about.gitlab.com/handbook/engineering/frontend/).
|
||||
|
||||
There are a lot of things to consider for a first merge request and it can feel overwhelming. The [Frontend onboarding course](onboarding_course/index.md) provides a 6-week structured curriculum to learn how to contribute to the GitLab frontend.
|
||||
|
||||
## Development life cycle
|
||||
|
||||
### Step 1: Preparing the issue
|
||||
|
||||
Before tackling any work, read through the issue that has been assigned to you and make sure that all [required departments](https://about.gitlab.com/handbook/engineering/#engineering-teams) have been involved as they should. Read through the comments as needed and if unclear, post a comment in the issue summarizing **what you think the work is** and ping your Engineering or Product Manager to confirm. Then once everything is clarified, apply the correct worfklow labels to the issue and create a merge request branch. If created directly from the issue, the issue and the merge request will be linked by default.
|
||||
|
||||
### Step 2: Plan your implementation
|
||||
|
||||
Before writing code, make sure to ask yourself the following questions and have clear answers before you start developing:
|
||||
|
||||
- What API data is required? Is it already available in our API or should I ask a Backend counterpart?
|
||||
- If this is GraphQL, write a query proposal and ask your BE counterpart to confirm they are in agreement.
|
||||
- Can I use [GitLab UI components](https://gitlab-org.gitlab.io/gitlab-ui/?path=/docs/base-accordion--docs)? Which components are appropriate and do they have all of the functionality that I need?
|
||||
- Are there existing components or utils in the GitLab project that I could use?
|
||||
- [Should this change live behind a Feature Flag](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#when-to-use-feature-flags)?
|
||||
- In which directory should this code live?
|
||||
- Should I build part of this feature as reusable? If so, where should it live in the codebase and how do I make it discoverable?
|
||||
- Note: For now this is still being considered, but the `vue_shared` folder is still the preferred directory for GitLab-wide components.
|
||||
- What kinds of tests will it require? Consider unit tests **and** [Feature Tests](../testing_guide/frontend_testing.md#get-started-with-feature-tests)? Should I reach out to a [SET](https://handbook.gitlab.com/job-families/engineering/software-engineer-in-test/) for guidance or am I comfortable implementing the tests?
|
||||
- How big will this change be? Try to keep diffs to **500+- at the most**.
|
||||
|
||||
If all of these questions have an answer, then you can safely move on to writing code.
|
||||
|
||||
### Step 3: Writing code
|
||||
|
||||
Make sure to communicate with your team as you progress or if you are unable to work on a planned issue for a long period of time.
|
||||
|
||||
If you require assistance, make sure to push your branch and share your Merge Request either directly to a teammate or in the Slack channel `#frontend` to get advice on how to move forward. You can [mark your Merge Request as a draft](../../user/project/merge_requests/drafts.md), which will clearly communicate that it is not ready for a full on review. Always remember to have a [low level of shame](https://handbook.gitlab.com/handbook/values/#low-level-of-shame) and **ask for help when you need it**.
|
||||
|
||||
As you write code, make sure to test your change thoroughly. It is the author's responsibility to test their code, ensure that it works as expected, and ensure that it did not break existing behaviours. Reviewers may help in that regard, but **do not expect it**. Make sure to check different browsers, mobile viewports and unexpected user flows.
|
||||
|
||||
### Step 4: Review
|
||||
|
||||
When it's time to send your code to review, it can be quite stressful. It is recommended to read through [the code review guidelines](../code_review.md) to get a better sense of what to expect. One of the most valuable pieces of advice that is **essential** is simply:
|
||||
|
||||
> [...] to avoid unnecessary back-and-forth with reviewers, [...] perform a self-review of your own merge request, and follow the Code Review guidelines.
|
||||
|
||||
This is key to having a great merge request experience because you will catch small mistakes and leave comments in areas where your reviewer might be uncertain and have questions. This speeds up the process tremendously.
|
||||
|
||||
## Step 5: Verifying
|
||||
|
||||
After your code has merged (congratulations!), make sure to verify that it works on the production environment and does not cause any errors.
|
||||
|
|
@ -86,135 +86,44 @@ Now that our values have been defined, we can base our goals on these values and
|
|||
|
||||
We have detailed description on how we see GitLab frontend in the future in [Frontend Goals](frontend_goals.md) section
|
||||
|
||||
### Frontend onboarding course
|
||||
### First time contributors
|
||||
|
||||
The [Frontend onboarding course](onboarding_course/index.md) provides a 6-week structured curriculum to learn how to contribute to the GitLab frontend.
|
||||
Please make sure to read through the [getting started](getting_started.md) page to know more about what to consider when creating a merge request for the first time or to refresh your memory on the GitLab workflow for frontend changes.
|
||||
|
||||
### Browser Support
|
||||
### Helpful links
|
||||
|
||||
#### Initiatives
|
||||
|
||||
You can find current frontend initiatives with a cross-functional impact on epics
|
||||
with the label [frontend-initiative](https://gitlab.com/groups/gitlab-org/-/epics?state=opened&page=1&sort=UPDATED_AT_DESC&label_name[]=frontend-initiative).
|
||||
|
||||
#### Testing
|
||||
|
||||
How we write [frontend tests](../testing_guide/frontend_testing.md), run the GitLab test suite, and debug test related
|
||||
issues.
|
||||
|
||||
#### Pajamas Design System
|
||||
|
||||
Reusable components with technical and usage guidelines can be found in our
|
||||
[Pajamas Design System](https://design.gitlab.com/).
|
||||
|
||||
#### Frontend FAQ
|
||||
|
||||
Read the [frontend's FAQ](frontend_faq.md) for common small pieces of helpful information.
|
||||
|
||||
#### [Internationalization (i18n) and Translations](../i18n/externalization.md)
|
||||
|
||||
Frontend internationalization support is described in [this document](../i18n/index.md).
|
||||
The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available.
|
||||
|
||||
#### [Troubleshooting](troubleshooting.md)
|
||||
|
||||
Running into a Frontend development problem? Check out [this guide](troubleshooting.md) to help resolve your issue.
|
||||
|
||||
#### Browser support
|
||||
|
||||
For supported browsers, see our [requirements](../../install/requirements.md#supported-web-browsers).
|
||||
|
||||
Use [BrowserStack](https://www.browserstack.com/) to test with our supported browsers.
|
||||
Sign in to BrowserStack with the credentials saved in the **Engineering** vault of the GitLab
|
||||
[shared 1Password account](https://about.gitlab.com/handbook/security/#1password-guide).
|
||||
|
||||
## Initiatives
|
||||
|
||||
You can find current frontend initiatives with a cross-functional impact on epics
|
||||
with the label [frontend-initiative](https://gitlab.com/groups/gitlab-org/-/epics?state=opened&page=1&sort=UPDATED_AT_DESC&label_name[]=frontend-initiative).
|
||||
|
||||
## Principles
|
||||
|
||||
[High-level guidelines](principles.md) for contributing to GitLab.
|
||||
|
||||
## Development Process
|
||||
|
||||
How we [plan and execute](development_process.md) the work on the frontend.
|
||||
|
||||
## Architecture
|
||||
|
||||
How we go about [making fundamental design decisions](architecture.md) in the GitLab frontend team
|
||||
or make changes to our frontend development guidelines.
|
||||
|
||||
## Testing
|
||||
|
||||
How we write [frontend tests](../testing_guide/frontend_testing.md), run the GitLab test suite, and debug test related
|
||||
issues.
|
||||
|
||||
## Pajamas Design System
|
||||
|
||||
Reusable components with technical and usage guidelines can be found in our
|
||||
[Pajamas Design System](https://design.gitlab.com/).
|
||||
|
||||
## Design Patterns
|
||||
|
||||
JavaScript [design patterns](design_patterns.md) in the GitLab codebase.
|
||||
|
||||
## Design Anti-patterns
|
||||
|
||||
JavaScript [design anti-patterns](design_anti_patterns.md) we try to avoid.
|
||||
|
||||
## Vue.js Best Practices
|
||||
|
||||
Vue specific [design patterns and practices](vue.md).
|
||||
|
||||
## Vuex
|
||||
|
||||
[Vuex](vuex.md) specific design patterns and practices.
|
||||
|
||||
## Axios
|
||||
|
||||
[Axios](axios.md) specific practices and gotchas.
|
||||
|
||||
## GraphQL
|
||||
|
||||
How to use [GraphQL](graphql.md).
|
||||
|
||||
## HAML
|
||||
|
||||
How to use [HAML](haml.md).
|
||||
|
||||
## ViewComponent
|
||||
|
||||
How we use [ViewComponent](view_component.md).
|
||||
|
||||
## Icons and Illustrations
|
||||
|
||||
How we use SVG for our [Icons and Illustrations](icons.md).
|
||||
|
||||
## Dependencies
|
||||
|
||||
General information about frontend [dependencies](dependencies.md) and how we manage them.
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
How we implement [keyboard shortcuts](keyboard_shortcuts.md) that can be customized and disabled.
|
||||
|
||||
## Editors
|
||||
|
||||
GitLab text editing experiences are provided by the [source editor](source_editor.md) and
|
||||
the [rich text editor](content_editor.md).
|
||||
|
||||
## Frontend FAQ
|
||||
|
||||
Read the [frontend's FAQ](frontend_faq.md) for common small pieces of helpful information.
|
||||
|
||||
## Style Guides
|
||||
|
||||
See the relevant style guides for our guidelines and for information on linting:
|
||||
|
||||
- [JavaScript](style/javascript.md). Our guide is based on
|
||||
the excellent [Airbnb](https://github.com/airbnb/javascript) style guide with a few small
|
||||
changes.
|
||||
- [SCSS](style/scss.md): [our SCSS conventions](https://gitlab.com/gitlab-org/frontend/gitlab-stylelint-config) which are enforced through [`stylelint`](https://stylelint.io).
|
||||
- [HTML](style/html.md). Guidelines for writing HTML code consistent with the rest of the codebase.
|
||||
- [Vue](style/vue.md). Guidelines and conventions for Vue code may be found here.
|
||||
|
||||
## [Tooling](tooling.md)
|
||||
|
||||
Our code is automatically formatted with [Prettier](https://prettier.io) to follow our guidelines. Read our [Tooling guide](tooling.md) for more detail.
|
||||
|
||||
## [Performance](performance.md)
|
||||
|
||||
Best practices for monitoring and maximizing frontend performance.
|
||||
|
||||
## [Security](security.md)
|
||||
|
||||
Frontend security practices.
|
||||
|
||||
## Accessibility
|
||||
|
||||
Our [accessibility standards and resources](accessibility.md).
|
||||
|
||||
## Logging
|
||||
|
||||
Best practices for [client-side logging](logging.md) for GitLab frontend development.
|
||||
|
||||
## [Internationalization (i18n) and Translations](../i18n/externalization.md)
|
||||
|
||||
Frontend internationalization support is described in [this document](../i18n/index.md).
|
||||
The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available.
|
||||
|
||||
## [Troubleshooting](troubleshooting.md)
|
||||
|
||||
Running into a Frontend development problem? Check out [this guide](troubleshooting.md) to help resolve your issue.
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Principles
|
||||
|
||||
These principles ensure that your frontend contribution starts off in the right direction.
|
||||
|
||||
## Discuss architecture before implementation
|
||||
|
||||
Discuss your architecture design in an issue before writing code. This helps decrease the review time and also provides good practice for writing and thinking about system design.
|
||||
|
||||
## Be consistent
|
||||
|
||||
There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This makes it easier for us to maintain our code across GitLab.
|
||||
|
||||
## Improve code [iteratively](https://about.gitlab.com/handbook/values/#iteration)
|
||||
|
||||
Whenever you see existing code that does not follow our current style guide, update it proactively. You don't need to fix everything, but each merge request should iteratively improve our codebase, and reduce technical debt where possible.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Tips and tricks
|
||||
|
||||
## Code deletion checklist
|
||||
|
||||
When your merge request deletes code, it's important to also delete all
|
||||
related code that is no longer used.
|
||||
When deleting Haml and Vue code, check whether it contains the following types of
|
||||
code that is unused:
|
||||
|
||||
- CSS.
|
||||
|
||||
For example, we've deleted a Vue component that contained the `.mr-card` class, which is now unused.
|
||||
The `.mr-card` CSS rule set should then be deleted from `merge_requests.scss`.
|
||||
|
||||
- Ruby variables.
|
||||
|
||||
Deleting unused Ruby variables is important so we don't continue instantiating them with
|
||||
potentially expensive code.
|
||||
|
||||
For example, we've deleted a Haml template that used the `@total_count` Ruby variable.
|
||||
The `@total_count` variable was no longer used in the remaining templates for the page.
|
||||
The instantiation of `@total_count` in `issues_controller.rb` should then be deleted so that we
|
||||
don't make unnecessary database calls to calculate the count of issues.
|
||||
|
||||
- Ruby methods.
|
||||
|
|
@ -32987,6 +32987,12 @@ msgstr ""
|
|||
msgid "Organization|Copy organization ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|Frequently visited groups"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|Frequently visited projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|Org ID"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33002,6 +33008,9 @@ msgstr ""
|
|||
msgid "Organization|Search or filter list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|View all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Orphaned member"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -865,33 +865,6 @@ RSpec.describe ApplicationController, feature_category: :shared do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#required_signup_info' do
|
||||
controller(described_class) do
|
||||
def index; end
|
||||
end
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'user with required role' do
|
||||
before do
|
||||
user.set_role_required!
|
||||
sign_in(user)
|
||||
get :index
|
||||
end
|
||||
|
||||
it { is_expected.to redirect_to users_sign_up_welcome_path }
|
||||
end
|
||||
|
||||
context 'user without a required role' do
|
||||
before do
|
||||
sign_in(user)
|
||||
get :index
|
||||
end
|
||||
|
||||
it { is_expected.not_to redirect_to users_sign_up_welcome_path }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'rescue_from Gitlab::Auth::IpBlocked' do
|
||||
controller(described_class) do
|
||||
skip_before_action :authenticate_user!
|
||||
|
|
|
|||
|
|
@ -19,17 +19,6 @@ RSpec.describe ConfirmationsController, feature_category: :system_access do
|
|||
get :show, params: { confirmation_token: confirmation_token }
|
||||
end
|
||||
|
||||
context 'when signup info is required' do
|
||||
before do
|
||||
allow(controller).to receive(:current_user) { user }
|
||||
user.set_role_required!
|
||||
end
|
||||
|
||||
it 'does not redirect' do
|
||||
expect(perform_request).not_to redirect_to(users_sign_up_welcome_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'user is already confirmed' do
|
||||
before do
|
||||
user.confirm
|
||||
|
|
@ -137,17 +126,6 @@ RSpec.describe ConfirmationsController, feature_category: :system_access do
|
|||
stub_feature_flags(identity_verification: false)
|
||||
end
|
||||
|
||||
context 'when signup info is required' do
|
||||
before do
|
||||
allow(controller).to receive(:current_user) { user }
|
||||
user.set_role_required!
|
||||
end
|
||||
|
||||
it 'does not redirect' do
|
||||
expect(perform_request).not_to redirect_to(users_sign_up_welcome_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when `email_confirmation_setting` is set to `soft`" do
|
||||
before do
|
||||
stub_application_setting_enum('email_confirmation_setting', 'soft')
|
||||
|
|
|
|||
|
|
@ -12,21 +12,12 @@ RSpec.describe Registrations::WelcomeController, feature_category: :system_acces
|
|||
it { is_expected.to redirect_to new_user_registration_path }
|
||||
end
|
||||
|
||||
context 'when role or setup_for_company is not set' do
|
||||
context 'when setup_for_company is not set' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it { is_expected.to render_template(:show) }
|
||||
end
|
||||
|
||||
context 'when role is required and setup_for_company is not set' do
|
||||
before do
|
||||
user.set_role_required!
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it { is_expected.to render_template(:show) }
|
||||
|
||||
render_views
|
||||
|
||||
|
|
@ -37,7 +28,7 @@ RSpec.describe Registrations::WelcomeController, feature_category: :system_acces
|
|||
end
|
||||
end
|
||||
|
||||
context 'when role and setup_for_company is set' do
|
||||
context 'when setup_for_company is set' do
|
||||
before do
|
||||
user.update!(setup_for_company: false)
|
||||
sign_in(user)
|
||||
|
|
@ -46,15 +37,6 @@ RSpec.describe Registrations::WelcomeController, feature_category: :system_acces
|
|||
it { is_expected.to redirect_to(dashboard_projects_path) }
|
||||
end
|
||||
|
||||
context 'when role is set and setup_for_company is not set' do
|
||||
before do
|
||||
user.update!(role: :software_developer)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it { is_expected.to render_template(:show) }
|
||||
end
|
||||
|
||||
context 'when 2FA is required from group' do
|
||||
before do
|
||||
user = create(:user, require_two_factor_authentication_from_group: true)
|
||||
|
|
|
|||
|
|
@ -237,4 +237,15 @@ RSpec.describe 'Dashboard Groups page', :js, feature_category: :groups_and_proje
|
|||
|
||||
expect(page).to have_link("Explore groups", href: explore_groups_path)
|
||||
end
|
||||
|
||||
context 'when there are no groups to display' do
|
||||
before do
|
||||
sign_in(user)
|
||||
visit dashboard_groups_path
|
||||
end
|
||||
|
||||
it 'shows empty state' do
|
||||
expect(page).to have_content(s_('GroupsEmptyState|A group is a collection of several projects'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,88 +4,104 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe 'Explore Groups page', :js, feature_category: :groups_and_projects do
|
||||
let!(:user) { create :user }
|
||||
let!(:group) { create(:group) }
|
||||
let!(:public_group) { create(:group, :public) }
|
||||
let!(:private_group) { create(:group, :private) }
|
||||
let!(:empty_project) { create(:project, group: public_group) }
|
||||
|
||||
before do
|
||||
group.add_owner(user)
|
||||
context 'when there are groups to show' do
|
||||
let!(:group) { create(:group) }
|
||||
let!(:public_group) { create(:group, :public) }
|
||||
let!(:private_group) { create(:group, :private) }
|
||||
let!(:empty_project) { create(:project, group: public_group) }
|
||||
|
||||
sign_in(user)
|
||||
before do
|
||||
group.add_owner(user)
|
||||
|
||||
visit explore_groups_path
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'shows groups user is member of' do
|
||||
expect(page).to have_content(group.full_name)
|
||||
expect(page).to have_content(public_group.full_name)
|
||||
expect(page).not_to have_content(private_group.full_name)
|
||||
end
|
||||
|
||||
it 'filters groups' do
|
||||
fill_in 'filter', with: group.name
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content(group.full_name)
|
||||
expect(page).not_to have_content(public_group.full_name)
|
||||
expect(page).not_to have_content(private_group.full_name)
|
||||
end
|
||||
|
||||
it 'resets search when user cleans the input' do
|
||||
fill_in 'filter', with: group.name
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content(group.full_name)
|
||||
expect(page).not_to have_content(public_group.full_name)
|
||||
|
||||
fill_in 'filter', with: ""
|
||||
page.find('[name="filter"]').send_keys(:enter)
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content(group.full_name)
|
||||
expect(page).to have_content(public_group.full_name)
|
||||
expect(page).not_to have_content(private_group.full_name)
|
||||
expect(page.all('.js-groups-list-holder .groups-list li').length).to eq 2
|
||||
end
|
||||
|
||||
it 'shows non-archived projects count' do
|
||||
# Initially project is not archived
|
||||
expect(find('.js-groups-list-holder .groups-list li:first-child .stats .number-projects')).to have_text("1")
|
||||
|
||||
# Archive project
|
||||
::Projects::UpdateService.new(empty_project, user, archived: true).execute
|
||||
visit explore_groups_path
|
||||
|
||||
# Check project count
|
||||
expect(find('.js-groups-list-holder .groups-list li:first-child .stats .number-projects')).to have_text("0")
|
||||
|
||||
# Unarchive project
|
||||
::Projects::UpdateService.new(empty_project, user, archived: false).execute
|
||||
visit explore_groups_path
|
||||
|
||||
# Check project count
|
||||
expect(find('.js-groups-list-holder .groups-list li:first-child .stats .number-projects')).to have_text("1")
|
||||
end
|
||||
|
||||
describe 'landing component' do
|
||||
it 'shows a landing component' do
|
||||
expect(page).to have_content('Below you will find all the groups that are public.')
|
||||
end
|
||||
|
||||
it 'is dismissable' do
|
||||
find('.dismiss-button').click
|
||||
|
||||
expect(page).not_to have_content('Below you will find all the groups that are public.')
|
||||
end
|
||||
|
||||
it 'does not show persistently once dismissed' do
|
||||
find('.dismiss-button').click
|
||||
sign_in(user)
|
||||
|
||||
visit explore_groups_path
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
expect(page).not_to have_content('Below you will find all the groups that are public.')
|
||||
it 'shows groups user is member of' do
|
||||
expect(page).to have_content(group.full_name)
|
||||
expect(page).to have_content(public_group.full_name)
|
||||
expect(page).not_to have_content(private_group.full_name)
|
||||
end
|
||||
|
||||
it 'filters groups' do
|
||||
fill_in 'filter', with: group.name
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content(group.full_name)
|
||||
expect(page).not_to have_content(public_group.full_name)
|
||||
expect(page).not_to have_content(private_group.full_name)
|
||||
end
|
||||
|
||||
it 'resets search when user cleans the input' do
|
||||
fill_in 'filter', with: group.name
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content(group.full_name)
|
||||
expect(page).not_to have_content(public_group.full_name)
|
||||
|
||||
fill_in 'filter', with: ""
|
||||
page.find('[name="filter"]').send_keys(:enter)
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content(group.full_name)
|
||||
expect(page).to have_content(public_group.full_name)
|
||||
expect(page).not_to have_content(private_group.full_name)
|
||||
expect(page.all('.js-groups-list-holder .groups-list li').length).to eq 2
|
||||
end
|
||||
|
||||
it 'shows non-archived projects count' do
|
||||
# Initially project is not archived
|
||||
expect(find('.js-groups-list-holder .groups-list li:first-child .stats .number-projects')).to have_text("1")
|
||||
|
||||
# Archive project
|
||||
::Projects::UpdateService.new(empty_project, user, archived: true).execute
|
||||
visit explore_groups_path
|
||||
|
||||
# Check project count
|
||||
expect(find('.js-groups-list-holder .groups-list li:first-child .stats .number-projects')).to have_text("0")
|
||||
|
||||
# Unarchive project
|
||||
::Projects::UpdateService.new(empty_project, user, archived: false).execute
|
||||
visit explore_groups_path
|
||||
|
||||
# Check project count
|
||||
expect(find('.js-groups-list-holder .groups-list li:first-child .stats .number-projects')).to have_text("1")
|
||||
end
|
||||
|
||||
describe 'landing component' do
|
||||
it 'shows a landing component' do
|
||||
expect(page).to have_content('Below you will find all the groups that are public.')
|
||||
end
|
||||
|
||||
it 'is dismissable' do
|
||||
find('.dismiss-button').click
|
||||
|
||||
expect(page).not_to have_content('Below you will find all the groups that are public.')
|
||||
end
|
||||
|
||||
it 'does not show persistently once dismissed' do
|
||||
find('.dismiss-button').click
|
||||
|
||||
visit explore_groups_path
|
||||
|
||||
expect(page).not_to have_content('Below you will find all the groups that are public.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no groups to show' do
|
||||
before do
|
||||
sign_in(user)
|
||||
|
||||
visit explore_groups_path
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'shows empty state' do
|
||||
expect(page).to have_content(_('No public groups'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -332,7 +332,6 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
click_button 'Register'
|
||||
|
||||
expect(page).to have_current_path(users_sign_up_welcome_path), ignore_query: true
|
||||
visit new_project_path
|
||||
|
||||
select 'Software Developer', from: 'user_role'
|
||||
click_button 'Get started!'
|
||||
|
|
@ -341,7 +340,7 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
|
||||
expect(created_user.software_developer_role?).to be_truthy
|
||||
expect(created_user.setup_for_company).to be_nil
|
||||
expect(page).to have_current_path(new_project_path)
|
||||
expect(page).to have_current_path(dashboard_projects_path)
|
||||
end
|
||||
|
||||
it_behaves_like 'Signup name validation', 'new_user_first_name', 127, 'First name'
|
||||
|
|
@ -388,7 +387,7 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
end
|
||||
end
|
||||
|
||||
it 'redirects to step 2 of the signup process, sets the role and redirects back' do
|
||||
it 'allows visiting of a page after initial registration' do
|
||||
visit new_user_registration_path
|
||||
|
||||
fill_in_signup_form
|
||||
|
|
@ -397,15 +396,6 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
|
||||
visit new_project_path
|
||||
|
||||
expect(page).to have_current_path(users_sign_up_welcome_path)
|
||||
|
||||
select 'Software Developer', from: 'user_role'
|
||||
click_button 'Get started!'
|
||||
|
||||
created_user = User.find_by_username(new_user.username)
|
||||
|
||||
expect(created_user.software_developer_role?).to be_truthy
|
||||
expect(created_user.setup_for_company).to be_nil
|
||||
expect(page).to have_current_path(new_project_path)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ import { GlCollapsibleListbox, GlSorting, GlSortingItem } from '@gitlab/ui';
|
|||
import App from '~/organizations/groups_and_projects/components/app.vue';
|
||||
import GroupsPage from '~/organizations/groups_and_projects/components/groups_page.vue';
|
||||
import ProjectsPage from '~/organizations/groups_and_projects/components/projects_page.vue';
|
||||
import { RESOURCE_TYPE_GROUPS, RESOURCE_TYPE_PROJECTS } from '~/organizations/constants';
|
||||
import {
|
||||
DISPLAY_QUERY_GROUPS,
|
||||
DISPLAY_QUERY_PROJECTS,
|
||||
SORT_ITEM_CREATED,
|
||||
SORT_DIRECTION_DESC,
|
||||
} from '~/organizations/groups_and_projects/constants';
|
||||
|
|
@ -36,10 +35,10 @@ describe('GroupsAndProjectsApp', () => {
|
|||
|
||||
describe.each`
|
||||
display | expectedComponent | expectedDisplayListboxSelectedProp
|
||||
${null} | ${GroupsPage} | ${DISPLAY_QUERY_GROUPS}
|
||||
${'unsupported_value'} | ${GroupsPage} | ${DISPLAY_QUERY_GROUPS}
|
||||
${DISPLAY_QUERY_GROUPS} | ${GroupsPage} | ${DISPLAY_QUERY_GROUPS}
|
||||
${DISPLAY_QUERY_PROJECTS} | ${ProjectsPage} | ${DISPLAY_QUERY_PROJECTS}
|
||||
${null} | ${GroupsPage} | ${RESOURCE_TYPE_GROUPS}
|
||||
${'unsupported_value'} | ${GroupsPage} | ${RESOURCE_TYPE_GROUPS}
|
||||
${RESOURCE_TYPE_GROUPS} | ${GroupsPage} | ${RESOURCE_TYPE_GROUPS}
|
||||
${RESOURCE_TYPE_PROJECTS} | ${ProjectsPage} | ${RESOURCE_TYPE_PROJECTS}
|
||||
`(
|
||||
'when `display` query string is $display',
|
||||
({ display, expectedComponent, expectedDisplayListboxSelectedProp }) => {
|
||||
|
|
@ -122,11 +121,11 @@ describe('GroupsAndProjectsApp', () => {
|
|||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
||||
findListbox().vm.$emit('select', DISPLAY_QUERY_PROJECTS);
|
||||
findListbox().vm.$emit('select', RESOURCE_TYPE_PROJECTS);
|
||||
});
|
||||
|
||||
it('updates `display` query string', () => {
|
||||
expect(routerMock.push).toHaveBeenCalledWith({ query: { display: DISPLAY_QUERY_PROJECTS } });
|
||||
expect(routerMock.push).toHaveBeenCalledWith({ query: { display: RESOURCE_TYPE_PROJECTS } });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import App from '~/organizations/show/components/app.vue';
|
||||
import OrganizationAvatar from '~/organizations/show/components/organization_avatar.vue';
|
||||
import GroupsAndProjects from '~/organizations/show/components/groups_and_projects.vue';
|
||||
|
||||
describe('OrganizationShowApp', () => {
|
||||
let wrapper;
|
||||
|
|
@ -10,6 +11,7 @@ describe('OrganizationShowApp', () => {
|
|||
id: 1,
|
||||
name: 'GitLab',
|
||||
},
|
||||
groupsAndProjectsOrganizationPath: '/-/organizations/default/groups_and_projects',
|
||||
};
|
||||
|
||||
const createComponent = () => {
|
||||
|
|
@ -25,4 +27,10 @@ describe('OrganizationShowApp', () => {
|
|||
defaultPropsData.organization,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders groups and projects component and passes `groupsAndProjectsOrganizationPath` prop', () => {
|
||||
expect(
|
||||
wrapper.findComponent(GroupsAndProjects).props('groupsAndProjectsOrganizationPath'),
|
||||
).toEqual(defaultPropsData.groupsAndProjectsOrganizationPath);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
import { GlCollapsibleListbox, GlLink } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import GroupsAndProjects from '~/organizations/show/components/groups_and_projects.vue';
|
||||
import { createRouter } from '~/organizations/show';
|
||||
|
||||
describe('OrganizationShowGroupsAndProjects', () => {
|
||||
const router = createRouter();
|
||||
const routerMock = {
|
||||
push: jest.fn(),
|
||||
};
|
||||
const defaultPropsData = {
|
||||
groupsAndProjectsOrganizationPath: '/-/organizations/default/groups_and_projects',
|
||||
};
|
||||
|
||||
let wrapper;
|
||||
|
||||
const createComponent = ({ routeQuery = {} } = {}) => {
|
||||
wrapper = shallowMountExtended(GroupsAndProjects, {
|
||||
router,
|
||||
mocks: { $route: { path: '/', query: routeQuery }, $router: routerMock },
|
||||
propsData: defaultPropsData,
|
||||
});
|
||||
};
|
||||
|
||||
const findCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
|
||||
it('renders listbox with expected props', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findCollapsibleListbox().props()).toMatchObject({
|
||||
items: [
|
||||
{
|
||||
value: 'frequently_visited_projects',
|
||||
text: 'Frequently visited projects',
|
||||
},
|
||||
{
|
||||
value: 'frequently_visited_groups',
|
||||
text: 'Frequently visited groups',
|
||||
},
|
||||
],
|
||||
selected: 'frequently_visited_projects',
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
displayQueryParam | expectedHref
|
||||
${'frequently_visited_projects'} | ${`${defaultPropsData.groupsAndProjectsOrganizationPath}?display=projects`}
|
||||
${'frequently_visited_groups'} | ${`${defaultPropsData.groupsAndProjectsOrganizationPath}?display=groups`}
|
||||
`('when display query param is $displayQueryParam', ({ displayQueryParam, expectedHref }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({ routeQuery: { display: displayQueryParam } });
|
||||
});
|
||||
|
||||
it('sets listbox `selected` prop correctly', () => {
|
||||
expect(findCollapsibleListbox().props('selected')).toBe(displayQueryParam);
|
||||
});
|
||||
|
||||
it('renders "View all" link with correct href', () => {
|
||||
expect(wrapper.findComponent(GlLink).attributes('href')).toBe(expectedHref);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders label and associates listbox with it', () => {
|
||||
createComponent();
|
||||
|
||||
const expectedId = 'display-listbox-label';
|
||||
|
||||
expect(wrapper.findByTestId('label').attributes('id')).toBe(expectedId);
|
||||
expect(findCollapsibleListbox().props('toggleAriaLabelledBy')).toBe(expectedId);
|
||||
});
|
||||
|
||||
describe('when listbox item is selected', () => {
|
||||
const selectValue = 'frequently_visited_groups';
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
||||
findCollapsibleListbox().vm.$emit('select', selectValue);
|
||||
});
|
||||
|
||||
it('updates `display` query param', () => {
|
||||
expect(routerMock.push).toHaveBeenCalledWith({
|
||||
query: { display: selectValue },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { buildDisplayListboxItem } from '~/organizations/show/utils';
|
||||
import { RESOURCE_TYPE_PROJECTS } from '~/organizations/constants';
|
||||
import { FILTER_FREQUENTLY_VISITED } from '~/organizations/show/constants';
|
||||
|
||||
describe('buildDisplayListboxItem', () => {
|
||||
it('returns list item in correct format', () => {
|
||||
const text = 'Frequently visited projects';
|
||||
|
||||
expect(
|
||||
buildDisplayListboxItem({
|
||||
filter: FILTER_FREQUENTLY_VISITED,
|
||||
resourceType: RESOURCE_TYPE_PROJECTS,
|
||||
text,
|
||||
}),
|
||||
).toEqual({
|
||||
text,
|
||||
value: 'frequently_visited_projects',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -6,12 +6,23 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
|||
let_it_be(:organization) { build_stubbed(:organization) }
|
||||
|
||||
describe '#organization_show_app_data' do
|
||||
before do
|
||||
allow(helper).to receive(:groups_and_projects_organization_path)
|
||||
.with(organization)
|
||||
.and_return('/-/organizations/default/groups_and_projects')
|
||||
end
|
||||
|
||||
it 'returns expected json' do
|
||||
expect(
|
||||
Gitlab::Json.parse(
|
||||
helper.organization_show_app_data(organization)
|
||||
)
|
||||
).to eq({ 'organization' => { 'id' => organization.id, 'name' => organization.name } })
|
||||
).to eq(
|
||||
{
|
||||
'organization' => { 'id' => organization.id, 'name' => organization.name },
|
||||
'groups_and_projects_organization_path' => '/-/organizations/default/groups_and_projects'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue