diff options
Diffstat (limited to 'app')
8 files changed, 87 insertions, 15 deletions
diff --git a/app/assets/javascripts/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_projects/components/import_projects_table.vue index 00eb0afb3bf..e5ac3cbafe5 100644 --- a/app/assets/javascripts/import_projects/components/import_projects_table.vue +++ b/app/assets/javascripts/import_projects/components/import_projects_table.vue @@ -1,4 +1,5 @@ <script> +import _ from 'underscore'; import { mapActions, mapState, mapGetters } from 'vuex'; import { GlLoadingIcon } from '@gitlab/ui'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; @@ -7,6 +8,8 @@ import ImportedProjectTableRow from './imported_project_table_row.vue'; import ProviderRepoTableRow from './provider_repo_table_row.vue'; import eventHub from '../event_hub'; +const reposFetchThrottleDelay = 1000; + export default { name: 'ImportProjectsTable', components: { @@ -23,11 +26,11 @@ export default { }, computed: { - ...mapState(['importedProjects', 'providerRepos', 'isLoadingRepos']), + ...mapState(['importedProjects', 'providerRepos', 'isLoadingRepos', 'filter']), ...mapGetters(['isImportingAnyRepo', 'hasProviderRepos', 'hasImportedProjects']), emptyStateText() { - return sprintf(__('No %{providerTitle} repositories available to import'), { + return sprintf(__('No %{providerTitle} repositories found'), { providerTitle: this.providerTitle, }); }, @@ -47,21 +50,38 @@ export default { }, methods: { - ...mapActions(['fetchRepos', 'fetchJobs', 'stopJobsPolling', 'clearJobsEtagPoll']), + ...mapActions([ + 'fetchRepos', + 'fetchReposFiltered', + 'fetchJobs', + 'stopJobsPolling', + 'clearJobsEtagPoll', + 'setFilter', + ]), importAll() { eventHub.$emit('importAll'); }, + + handleFilterInput({ target }) { + this.setFilter(target.value); + }, + + throttledFetchRepos: _.throttle(function fetch() { + eventHub.$off('importAll'); + this.fetchRepos(); + }, reposFetchThrottleDelay), }, }; </script> <template> <div> + <p class="light text-nowrap mt-2"> + {{ s__('ImportProjects|Select the projects you want to import') }} + </p> + <div class="d-flex justify-content-between align-items-end flex-wrap mb-3"> - <p class="light text-nowrap mt-2 my-sm-0"> - {{ s__('ImportProjects|Select the projects you want to import') }} - </p> <loading-button container-class="btn btn-success js-import-all" :loading="isImportingAnyRepo" @@ -70,6 +90,19 @@ export default { type="button" @click="importAll" /> + <form novalidate @submit.prevent> + <input + :value="filter" + data-qa-selector="githubish_import_filter_field" + class="form-control" + name="filter" + :placeholder="__('Filter your projects by name')" + autofocus + size="40" + @input="handleFilterInput($event)" + @keyup.enter="throttledFetchRepos" + /> + </form> </div> <gl-loading-icon v-if="isLoadingRepos" diff --git a/app/assets/javascripts/import_projects/index.js b/app/assets/javascripts/import_projects/index.js index 2d99d716609..b069dcb7766 100644 --- a/app/assets/javascripts/import_projects/index.js +++ b/app/assets/javascripts/import_projects/index.js @@ -38,7 +38,7 @@ export default function mountImportProjectsTable(mountElement) { }, methods: { - ...mapActions(['setInitialData']), + ...mapActions(['setInitialData', 'setFilter']), }, render(createElement) { diff --git a/app/assets/javascripts/import_projects/store/actions.js b/app/assets/javascripts/import_projects/store/actions.js index c44500937cc..0fb9a4cdfd4 100644 --- a/app/assets/javascripts/import_projects/store/actions.js +++ b/app/assets/javascripts/import_projects/store/actions.js @@ -5,6 +5,7 @@ import Poll from '~/lib/utils/poll'; import createFlash from '~/flash'; import { s__, sprintf } from '~/locale'; import axios from '~/lib/utils/axios_utils'; +import { jobsPathWithFilter, reposPathWithFilter } from './getters'; let eTagPoll; @@ -19,16 +20,20 @@ export const restartJobsPolling = () => { }; export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data); +export const setFilter = ({ commit }, filter) => commit(types.SET_FILTER, filter); export const requestRepos = ({ commit }, repos) => commit(types.REQUEST_REPOS, repos); export const receiveReposSuccess = ({ commit }, repos) => commit(types.RECEIVE_REPOS_SUCCESS, repos); export const receiveReposError = ({ commit }) => commit(types.RECEIVE_REPOS_ERROR); export const fetchRepos = ({ state, dispatch }) => { + dispatch('stopJobsPolling'); dispatch('requestRepos'); + const { provider } = state; + return axios - .get(state.reposPath) + .get(reposPathWithFilter(state)) .then(({ data }) => dispatch('receiveReposSuccess', convertObjectPropsToCamelCase(data, { deep: true })), ) @@ -36,7 +41,7 @@ export const fetchRepos = ({ state, dispatch }) => { .catch(() => { createFlash( sprintf(s__('ImportProjects|Requesting your %{provider} repositories failed'), { - provider: state.provider, + provider, }), ); @@ -77,16 +82,23 @@ export const fetchImport = ({ state, dispatch }, { newName, targetNamespace, rep export const receiveJobsSuccess = ({ commit }, updatedProjects) => commit(types.RECEIVE_JOBS_SUCCESS, updatedProjects); export const fetchJobs = ({ state, dispatch }) => { - if (eTagPoll) return; + const { filter } = state; + + if (eTagPoll) { + stopJobsPolling(); + clearJobsEtagPoll(); + } eTagPoll = new Poll({ resource: { - fetchJobs: () => axios.get(state.jobsPath), + fetchJobs: () => axios.get(jobsPathWithFilter(state)), }, method: 'fetchJobs', successCallback: ({ data }) => dispatch('receiveJobsSuccess', convertObjectPropsToCamelCase(data, { deep: true })), - errorCallback: () => createFlash(s__('ImportProjects|Updating the imported projects failed')), + errorCallback: () => + createFlash(s__('ImportProjects|Update of imported projects with realtime changes failed')), + data: { filter }, }); if (!Visibility.hidden()) { diff --git a/app/assets/javascripts/import_projects/store/getters.js b/app/assets/javascripts/import_projects/store/getters.js index 727b80765bd..b107c293181 100644 --- a/app/assets/javascripts/import_projects/store/getters.js +++ b/app/assets/javascripts/import_projects/store/getters.js @@ -20,3 +20,8 @@ export const isImportingAnyRepo = state => state.reposBeingImported.length > 0; export const hasProviderRepos = state => state.providerRepos.length > 0; export const hasImportedProjects = state => state.importedProjects.length > 0; + +export const reposPathWithFilter = ({ reposPath, filter = '' }) => + filter ? `${reposPath}?filter=${filter}` : reposPath; +export const jobsPathWithFilter = ({ jobsPath, filter = '' }) => + filter ? `${jobsPath}?filter=${filter}` : jobsPath; diff --git a/app/assets/javascripts/import_projects/store/mutation_types.js b/app/assets/javascripts/import_projects/store/mutation_types.js index 6ba3fd6f29e..16574f4450f 100644 --- a/app/assets/javascripts/import_projects/store/mutation_types.js +++ b/app/assets/javascripts/import_projects/store/mutation_types.js @@ -9,3 +9,5 @@ export const RECEIVE_IMPORT_SUCCESS = 'RECEIVE_IMPORT_SUCCESS'; export const RECEIVE_IMPORT_ERROR = 'RECEIVE_IMPORT_ERROR'; export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS'; + +export const SET_FILTER = 'SET_FILTER'; diff --git a/app/assets/javascripts/import_projects/store/mutations.js b/app/assets/javascripts/import_projects/store/mutations.js index b88de0268e7..6c56cfa8298 100644 --- a/app/assets/javascripts/import_projects/store/mutations.js +++ b/app/assets/javascripts/import_projects/store/mutations.js @@ -6,6 +6,10 @@ export default { Object.assign(state, data); }, + [types.SET_FILTER](state, filter) { + state.filter = filter; + }, + [types.REQUEST_REPOS](state) { state.isLoadingRepos = true; }, diff --git a/app/assets/javascripts/import_projects/store/state.js b/app/assets/javascripts/import_projects/store/state.js index 637fef6e53c..829f3aa4fbb 100644 --- a/app/assets/javascripts/import_projects/store/state.js +++ b/app/assets/javascripts/import_projects/store/state.js @@ -12,4 +12,5 @@ export default () => ({ isLoadingRepos: false, canSelectNamespace: false, ciCdOnly: false, + filter: '', }); diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 72f830fc9a1..c418b11ab13 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -2,6 +2,7 @@ class Import::GithubController < Import::BaseController include ImportHelper + include ActionView::Helpers::SanitizeHelper before_action :verify_import_enabled before_action :provider_auth, only: [:status, :realtime_changes, :create] @@ -55,7 +56,7 @@ class Import::GithubController < Import::BaseController def realtime_changes Gitlab::PollingInterval.set_header(response, interval: 3_000) - render json: find_jobs(provider) + render json: already_added_projects.to_json(only: [:id], methods: [:import_status]) end private @@ -82,7 +83,7 @@ class Import::GithubController < Import::BaseController end def already_added_projects - @already_added_projects ||= find_already_added_projects(provider) + @already_added_projects ||= filtered(find_already_added_projects(provider)) end def already_added_project_names @@ -104,7 +105,7 @@ class Import::GithubController < Import::BaseController end def client_repos - @client_repos ||= client.repos + @client_repos ||= filtered(client.repos) end def verify_import_enabled @@ -185,6 +186,20 @@ class Import::GithubController < Import::BaseController def extra_import_params {} end + + def sanitized_filter_param + @filter ||= sanitize(params[:filter]) + end + + def filter_attribute + :name + end + + def filtered(collection) + return collection unless sanitized_filter_param + + collection.select { |item| item[filter_attribute].include?(sanitized_filter_param) } + end end Import::GithubController.prepend_if_ee('EE::Import::GithubController') |