Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-08-28 09:08:38 +00:00
parent 6ab715991f
commit 603425e1f4
36 changed files with 517 additions and 484 deletions

View File

@ -0,0 +1,4 @@
export const RESOURCE_TYPE_GROUPS = 'groups';
export const RESOURCE_TYPE_PROJECTS = 'projects';
export const ORGANIZATION_ROOT_ROUTE_NAME = 'root';

View File

@ -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: {

View File

@ -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 = [

View File

@ -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 }];

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1 @@
export const FILTER_FREQUENTLY_VISITED = 'frequently_visited';

View File

@ -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 } });
},
});
};

View File

@ -0,0 +1,4 @@
export const buildDisplayListboxItem = ({ filter, resourceType, text }) => ({
text,
value: `${filter}_${resourceType}`,
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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

View File

@ -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]

View File

@ -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)**

View File

@ -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.

View File

@ -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.
![Diagram of issue boards architecture](img/boards_diagram.png)
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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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 ""

View File

@ -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!

View File

@ -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')

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 } });
});
});

View File

@ -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);
});
});

View File

@ -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 },
});
});
});
});

View File

@ -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',
});
});
});

View File

@ -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