diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index c8283326533..0e16e1407ac 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -3,7 +3,7 @@ *.rake @gitlab-org/maintainers/rails-backend # Technical writing team are the default reviewers for everything in `doc/` -/doc/ @gl-docsteam +doc/ @gl-docsteam # Frontend maintainers should see everything in `app/assets/` app/assets/ @gitlab-org/maintainers/frontend diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 071ae8ca8cf..5e3932db235 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -142,6 +142,12 @@ const Api = { return axios.get(url); }, + // Update a single project + updateProject(projectPath, data) { + const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath)); + return axios.put(url, data); + }, + /** * Get all projects for a forked relationship to a specified project * @param {string} projectPath - Path or ID of a project diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index 5d7be0c705a..eeb0fbec1ed 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -9,7 +9,6 @@ import { GlDropdownItem, } from '@gitlab/ui'; -import Icon from '~/vue_shared/components/icon.vue'; import httpStatusCodes from '~/lib/utils/http_status'; import boardsStore from '../stores/boards_store'; import BoardForm from './board_form.vue'; @@ -19,7 +18,6 @@ const MIN_BOARDS_TO_VIEW_RECENT = 10; export default { name: 'BoardsSelector', components: { - Icon, BoardForm, GlLoadingIcon, GlSearchBoxByType, diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index c6c8dc6352c..ab1e5f92698 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -2,7 +2,6 @@ /* eslint-disable vue/require-default-prop */ /* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { GlLink, GlModalDirective } from '@gitlab/ui'; -import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; import { s__, __, sprintf } from '~/locale'; import eventHub from '../event_hub'; import identicon from '../../vue_shared/components/identicon.vue'; @@ -16,7 +15,6 @@ export default { components: { loadingButton, identicon, - TimeagoTooltip, GlLink, UninstallApplicationButton, UninstallApplicationConfirmationModal, diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index a0ab20a97aa..99844a356c8 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -19,7 +19,6 @@ import applicationRow from './application_row.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import KnativeDomainEditor from './knative_domain_editor.vue'; import { CLUSTER_TYPE, PROVIDER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants'; -import LoadingButton from '~/vue_shared/components/loading_button.vue'; import eventHub from '~/clusters/event_hub'; import CrossplaneProviderStack from './crossplane_provider_stack.vue'; @@ -27,7 +26,6 @@ export default { components: { applicationRow, clipboardButton, - LoadingButton, GlLoadingIcon, KnativeDomainEditor, CrossplaneProviderStack, diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 8ea443814e9..c07850b1a4f 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -2,7 +2,6 @@ import { mapState, mapGetters, mapActions } from 'vuex'; import { GlLoadingIcon } from '@gitlab/ui'; import Mousetrap from 'mousetrap'; -import Icon from '~/vue_shared/components/icon.vue'; import { __ } from '~/locale'; import createFlash from '~/flash'; import PanelResizer from '~/vue_shared/components/panel_resizer.vue'; @@ -27,7 +26,6 @@ import { export default { name: 'DiffsApp', components: { - Icon, CompareVersions, DiffFile, NoChanges, @@ -95,7 +93,6 @@ export default { parseInt(localStorage.getItem(TREE_LIST_WIDTH_STORAGE_KEY), 10) || INITIAL_TREE_WIDTH; return { - assignedDiscussions: false, treeWidth, }; }, @@ -114,6 +111,7 @@ export default { numVisibleFiles: state => state.diffs.size, plainDiffPath: state => state.diffs.plainDiffPath, emailPatchPath: state => state.diffs.emailPatchPath, + retrievingBatches: state => state.diffs.retrievingBatches, }), ...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion']), ...mapGetters('diffs', ['isParallelView', 'currentDiffIndex']), @@ -144,9 +142,6 @@ export default { isLimitedContainer() { return !this.showTreeList && !this.isParallelView && !this.isFluidLayout; }, - shouldSetDiscussions() { - return this.isNotesFetched && !this.assignedDiscussions && !this.isLoading; - }, }, watch: { diffViewType() { @@ -163,10 +158,8 @@ export default { }, isLoading: 'adjustView', showTreeList: 'adjustView', - shouldSetDiscussions(newVal) { - if (newVal) { - this.setDiscussions(); - } + retrievingBatches(newVal) { + if (!newVal) this.unwatchDiscussions(); }, }, mounted() { @@ -192,10 +185,14 @@ export default { }, created() { this.adjustView(); - eventHub.$once('fetchedNotesData', this.setDiscussions); eventHub.$once('fetchDiffData', this.fetchData); eventHub.$on('refetchDiffData', this.refetchDiffData); this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES; + + this.unwatchDiscussions = this.$watch( + () => `${this.diffFiles.length}:${this.$store.state.notes.discussions.length}`, + () => this.setDiscussions(), + ); }, beforeDestroy() { eventHub.$off('fetchDiffData', this.fetchData); @@ -217,7 +214,6 @@ export default { 'toggleShowTreeList', ]), refetchDiffData() { - this.assignedDiscussions = false; this.fetchData(false); }, startDiffRendering() { @@ -269,17 +265,13 @@ export default { } }, setDiscussions() { - if (this.shouldSetDiscussions) { - this.assignedDiscussions = true; - - requestIdleCallback( - () => - this.assignDiscussionsToDiff() - .then(this.$nextTick) - .then(this.startTaskList), - { timeout: 1000 }, - ); - } + requestIdleCallback( + () => + this.assignDiscussionsToDiff() + .then(this.$nextTick) + .then(this.startTaskList), + { timeout: 1000 }, + ); }, adjustView() { if (this.shouldShow) { diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index 43a7703f611..cfffccd54eb 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -2,7 +2,6 @@ import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import Icon from '~/vue_shared/components/icon.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; -import CIIcon from '~/vue_shared/components/ci_icon.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import initUserPopovers from '../../user_popovers'; @@ -25,7 +24,6 @@ export default { UserAvatarLink, Icon, ClipboardButton, - CIIcon, TimeAgoTooltip, CommitPipelineStatus, }, diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index 91d374eafc0..5d27c6eb865 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -1,7 +1,7 @@ @@ -28,16 +25,19 @@ export default { diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue new file mode 100644 index 00000000000..402763e2e21 --- /dev/null +++ b/app/assets/javascripts/registry/settings/components/settings_form.vue @@ -0,0 +1,158 @@ + + + diff --git a/app/assets/javascripts/registry/settings/constants.js b/app/assets/javascripts/registry/settings/constants.js new file mode 100644 index 00000000000..c0dac466b29 --- /dev/null +++ b/app/assets/javascripts/registry/settings/constants.js @@ -0,0 +1,15 @@ +import { s__ } from '~/locale'; + +export const FETCH_SETTINGS_ERROR_MESSAGE = s__( + 'ContainerRegistry|Something went wrong while fetching the expiration policy.', +); + +export const UPDATE_SETTINGS_ERROR_MESSAGE = s__( + 'ContainerRegistry|Something went wrong while updating the expiration policy.', +); + +export const UPDATE_SETTINGS_SUCCESS_MESSAGE = s__( + 'ContainerRegistry|Expiration policy successfully saved.', +); + +export const NAME_REGEX_LENGTH = 255; diff --git a/app/assets/javascripts/registry/settings/registry_settings_bundle.js b/app/assets/javascripts/registry/settings/registry_settings_bundle.js index 2938178ea86..927b6059884 100644 --- a/app/assets/javascripts/registry/settings/registry_settings_bundle.js +++ b/app/assets/javascripts/registry/settings/registry_settings_bundle.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; -import store from './stores/'; +import store from './store/'; import RegistrySettingsApp from './components/registry_settings_app.vue'; Vue.use(Translate); diff --git a/app/assets/javascripts/registry/settings/store/actions.js b/app/assets/javascripts/registry/settings/store/actions.js new file mode 100644 index 00000000000..b161373dd0a --- /dev/null +++ b/app/assets/javascripts/registry/settings/store/actions.js @@ -0,0 +1,40 @@ +import Api from '~/api'; +import createFlash from '~/flash'; +import { + FETCH_SETTINGS_ERROR_MESSAGE, + UPDATE_SETTINGS_ERROR_MESSAGE, + UPDATE_SETTINGS_SUCCESS_MESSAGE, +} from '../constants'; +import * as types from './mutation_types'; + +export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data); +export const updateSettings = ({ commit }, data) => commit(types.UPDATE_SETTINGS, data); +export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING); +export const receiveSettingsSuccess = ({ commit }, data = {}) => commit(types.SET_SETTINGS, data); +export const receiveSettingsError = () => createFlash(FETCH_SETTINGS_ERROR_MESSAGE); +export const updateSettingsError = () => createFlash(UPDATE_SETTINGS_ERROR_MESSAGE); +export const resetSettings = ({ commit }) => commit(types.RESET_SETTINGS); + +export const fetchSettings = ({ dispatch, state }) => { + dispatch('toggleLoading'); + return Api.project(state.projectId) + .then(({ tag_expiration_policies }) => + dispatch('receiveSettingsSuccess', tag_expiration_policies), + ) + .catch(() => dispatch('receiveSettingsError')) + .finally(() => dispatch('toggleLoading')); +}; + +export const saveSettings = ({ dispatch, state }) => { + dispatch('toggleLoading'); + return Api.updateProject(state.projectId, { tag_expiration_policies: state.settings }) + .then(({ tag_expiration_policies }) => { + dispatch('receiveSettingsSuccess', tag_expiration_policies); + createFlash(UPDATE_SETTINGS_SUCCESS_MESSAGE); + }) + .catch(() => dispatch('updateSettingsError')) + .finally(() => dispatch('toggleLoading')); +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/registry/settings/stores/index.js b/app/assets/javascripts/registry/settings/store/index.js similarity index 100% rename from app/assets/javascripts/registry/settings/stores/index.js rename to app/assets/javascripts/registry/settings/store/index.js diff --git a/app/assets/javascripts/registry/settings/store/mutation_types.js b/app/assets/javascripts/registry/settings/store/mutation_types.js new file mode 100644 index 00000000000..db499ffa761 --- /dev/null +++ b/app/assets/javascripts/registry/settings/store/mutation_types.js @@ -0,0 +1,5 @@ +export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'; +export const UPDATE_SETTINGS = 'UPDATE_SETTINGS'; +export const TOGGLE_LOADING = 'TOGGLE_LOADING'; +export const SET_SETTINGS = 'SET_SETTINGS'; +export const RESET_SETTINGS = 'RESET_SETTINGS'; diff --git a/app/assets/javascripts/registry/settings/store/mutations.js b/app/assets/javascripts/registry/settings/store/mutations.js new file mode 100644 index 00000000000..b8384fd4a45 --- /dev/null +++ b/app/assets/javascripts/registry/settings/store/mutations.js @@ -0,0 +1,20 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_INITIAL_STATE](state, initialState) { + state.projectId = initialState.projectId; + }, + [types.UPDATE_SETTINGS](state, settings) { + state.settings = { ...state.settings, ...settings }; + }, + [types.SET_SETTINGS](state, settings) { + state.settings = settings; + state.original = Object.freeze(settings); + }, + [types.RESET_SETTINGS](state) { + state.settings = { ...state.original }; + }, + [types.TOGGLE_LOADING](state) { + state.isLoading = !state.isLoading; + }, +}; diff --git a/app/assets/javascripts/registry/settings/store/state.js b/app/assets/javascripts/registry/settings/store/state.js new file mode 100644 index 00000000000..c3a26083c9f --- /dev/null +++ b/app/assets/javascripts/registry/settings/store/state.js @@ -0,0 +1,26 @@ +export default () => ({ + /* + * Project Id used to build the API call + */ + projectId: '', + /* + * Boolean to determine if the UI is loading data from the API + */ + isLoading: false, + /* + * This contains the data shown and manipulated in the UI + * Has the following structure: + * { + * enabled: Boolean + * cadence: String, + * older_than: String, + * keep_n: String, + * name_regex: String + * } + */ + settings: {}, + /* + * Same structure as settings, above but Frozen object and used only in case the user clicks 'cancel' + */ + original: {}, +}); diff --git a/app/assets/javascripts/registry/settings/stores/actions.js b/app/assets/javascripts/registry/settings/stores/actions.js deleted file mode 100644 index f2c469d4edb..00000000000 --- a/app/assets/javascripts/registry/settings/stores/actions.js +++ /dev/null @@ -1,6 +0,0 @@ -import * as types from './mutation_types'; - -export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data); - -// to avoid eslint error until more actions are added to the store -export default () => {}; diff --git a/app/assets/javascripts/registry/settings/stores/mutation_types.js b/app/assets/javascripts/registry/settings/stores/mutation_types.js deleted file mode 100644 index 8a0f519eabd..00000000000 --- a/app/assets/javascripts/registry/settings/stores/mutation_types.js +++ /dev/null @@ -1,4 +0,0 @@ -export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'; - -// to avoid eslint error until more actions are added to the store -export default () => {}; diff --git a/app/assets/javascripts/registry/settings/stores/mutations.js b/app/assets/javascripts/registry/settings/stores/mutations.js deleted file mode 100644 index 4f32e11ed52..00000000000 --- a/app/assets/javascripts/registry/settings/stores/mutations.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as types from './mutation_types'; - -export default { - [types.SET_INITIAL_STATE](state, initialState) { - state.helpPagePath = initialState.helpPagePath; - state.registrySettingsEndpoint = initialState.registrySettingsEndpoint; - }, -}; diff --git a/app/assets/javascripts/registry/settings/stores/state.js b/app/assets/javascripts/registry/settings/stores/state.js deleted file mode 100644 index 4c0439458b6..00000000000 --- a/app/assets/javascripts/registry/settings/stores/state.js +++ /dev/null @@ -1,10 +0,0 @@ -export default () => ({ - /* - * Help page path to generate the link - */ - helpPagePath: '', - /* - * Settings endpoint to call to fetch and update the settings - */ - registrySettingsEndpoint: '', -}); diff --git a/app/assets/javascripts/releases/list/components/release_block.vue b/app/assets/javascripts/releases/list/components/release_block.vue index 4d8d8682401..d5621808ed7 100644 --- a/app/assets/javascripts/releases/list/components/release_block.vue +++ b/app/assets/javascripts/releases/list/components/release_block.vue @@ -1,7 +1,7 @@