Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
js: Namespaces#index refactoring w/ Vue
Browse files Browse the repository at this point in the history
- Created custom elements for namespaces (namespace-panel, new-namespace-form,
  namespace-table, namespace-table-row, namespace-visibility)
- Added sorting columns (similar to #590)
- Fixed pagination (similar to #585)
- Improved UX of changing namespace visibility (see gif below)
- Updated slim gem
- Updated active_model_serializers gem
- Used serializers (check namespace_serializer.rb) for json rendering
- Added moment for dates (kind of overkill but a good thing in the long term)
- Added lodash and lodash babel plugin (makes partial imports possible)
- And other minor changes
  • Loading branch information
vitoravelino committed Aug 15, 2017
1 parent ee387ff commit 3e145dc
Show file tree
Hide file tree
Showing 58 changed files with 1,335 additions and 433 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ source "https://rubygems.org"
gem "rails", "~> 4.2.8"
gem "sass-rails", ">= 3.2"
gem "bootstrap-sass", "~> 3.3.4"
gem "slim"
gem "slim", "~> 3.0.8"
gem "pundit"
gem "sprockets", "~> 2.12.3"
gem "jwt"
Expand Down
18 changes: 12 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ GEM
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_model_serializers (0.9.0)
activemodel (>= 3.2)
active_model_serializers (0.10.6)
actionpack (>= 4.1, < 6)
activemodel (>= 4.1, < 6)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.2)
active_record_union (1.1.0)
activerecord (>= 4.0)
activejob (4.2.8)
Expand Down Expand Up @@ -82,6 +85,8 @@ GEM
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
case_transform (0.2)
activesupport
cconfig (1.1.1)
safe_yaml (~> 1.0.0, >= 1.0.0)
choice (0.2.0)
Expand Down Expand Up @@ -192,6 +197,7 @@ GEM
json (1.8.6)
json-schema (2.5.1)
addressable (~> 2.3.7)
jsonapi-renderer (0.1.3)
jwt (1.5.0)
kaminari (0.16.3)
actionpack (>= 3.0.0)
Expand Down Expand Up @@ -365,8 +371,8 @@ GEM
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slim (2.0.2)
temple (~> 0.6.6)
slim (3.0.8)
temple (>= 0.7.6, < 0.9)
tilt (>= 1.3.3, < 2.1)
slop (3.6.0)
sprockets (2.12.4)
Expand All @@ -378,7 +384,7 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
temple (0.6.7)
temple (0.8.0)
terminal-table (1.5.2)
thor (0.19.1)
thread_safe (0.3.6)
Expand Down Expand Up @@ -485,7 +491,7 @@ DEPENDENCIES
search_cop
shoulda
simplecov (= 0.10.0)
slim
slim (~> 3.0.8)
sprockets (~> 2.12.3)
thor
timecop
Expand Down
60 changes: 60 additions & 0 deletions app/assets/javascripts/modules/namespaces/components/new-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Vue from 'vue';

import EventBus from '~/utils/eventbus';

import { setTypeahead } from '~/utils/typeahead';

import Alert from '~/shared/components/alert';
import FormMixin from '~/shared/mixins/form';

import NamespacesService from '../services/namespaces';

const TYPEAHEAD_INPUT = '.remote .typeahead';

const { set } = Vue;

export default {
template: '#js-new-namespace-form-tmpl',

mixins: [FormMixin],

data() {
return {
namespace: {
namespace: {},
},
};
},

methods: {
onSubmit() {
NamespacesService.save(this.namespace).then((response) => {
const namespace = response.data.data;
const name = namespace.attributes.clean_name;

this.toggleForm();
set(this.namespace, 'namespace', {});

Alert.show(`Namespace '${name}' was created successfully`);
EventBus.$emit('namespaceCreated', namespace);
}).catch((response) => {
let errors = response.data;

if (Array.isArray(errors)) {
errors = errors.join('<br />');
}

Alert.show(errors);
});
},
},

mounted() {
const $team = setTypeahead(TYPEAHEAD_INPUT, '/namespaces/typeahead/%QUERY');

// workaround because of typeahead
$team.on('change', () => {
set(this.namespace.namespace, 'team', $team.val());
});
},
};
19 changes: 19 additions & 0 deletions app/assets/javascripts/modules/namespaces/components/panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import LoadingIcon from '~/shared/components/loading-icon';
import ToggleLink from '~/shared/components/toggle-link';
import PanelWithFormMixin from '~/shared/mixins/panel-with-form';

import NamespacesTable from './table';

export default {
template: '#js-namespaces-panel-tmpl',

mixins: [PanelWithFormMixin],

props: ['namespaces', 'tableSortable'],

components: {
LoadingIcon,
NamespacesTable,
ToggleLink,
},
};
23 changes: 23 additions & 0 deletions app/assets/javascripts/modules/namespaces/components/table-row.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import moment from 'moment';

import NamespaceVisibility from './visibility';

export default {
template: '#js-namespace-table-row-tmpl',

props: ['namespace'],

computed: {
scopeClass() {
return `namespace_${this.namespace.id}`;
},

createdAt() {
return moment(this.namespace.attributes.created_at).format('MMMM DD, YYYY HH:mm');
},
},

components: {
NamespaceVisibility,
},
};
74 changes: 74 additions & 0 deletions app/assets/javascripts/modules/namespaces/components/table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Vue from 'vue';
import getProperty from 'lodash/get';

import Comparator from '~/utils/comparator';
import TablePagination from '~/shared/components/table-pagination';

import NamespaceTableRow from './table-row';

const { set } = Vue;

export default {
template: '#js-namespaces-table-tmpl',

props: ['namespaces', 'sortable'],

components: {
NamespaceTableRow,
TablePagination,
},

data() {
return {
sortAsc: true,
sortBy: 'attributes.clean_name',
limit: 3,
currentPage: 1,
};
},

computed: {
offset() {
return (this.currentPage - 1) * this.limit;
},

filteredNamespaces() {
const order = this.sortAsc ? 1 : -1;
const sortedNamespaces = [...this.namespaces];
const sample = sortedNamespaces[0];
const value = getProperty(sample, this.sortBy);
const comparator = Comparator.of(value);

// sorting
sortedNamespaces.sort((a, b) => {
const aValue = getProperty(a, this.sortBy);
const bValue = getProperty(b, this.sortBy);

return order * comparator(aValue, bValue);
});

// pagination
const slicedNamespaces = sortedNamespaces.slice(this.offset, this.limit * this.currentPage);

return slicedNamespaces;
},
},

methods: {
sort(attribute) {
if (!this.sortable) {
return;
}

// if sort column has changed, go always asc
// inverse current order otherwise
if (this.sortBy === attribute) {
set(this, 'sortAsc', !this.sortAsc);
} else {
set(this, 'sortAsc', true);
}

set(this, 'sortBy', attribute);
},
},
};
75 changes: 75 additions & 0 deletions app/assets/javascripts/modules/namespaces/components/visibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Vue from 'vue';

import Alert from '~/shared/components/alert';

import NamespacesService from '../services/namespaces';

const { set } = Vue;

export default {
template: '#js-namespace-visibility-tmpl',

props: ['namespace'],

data() {
return {
onGoingRequest: false,
newVisibility: null,
};
},

computed: {
isPrivate() {
return this.namespace.attributes.visibility === 'visibility_private';
},

isProtected() {
return this.namespace.attributes.visibility === 'visibility_protected';
},

isPublic() {
return this.namespace.attributes.visibility === 'visibility_public';
},

canChangeVibisility() {
return this.namespace.meta.can_change_visibility &&
!this.onGoingRequest;
},

privateTitle() {
if (this.namespace.attributes.global) {
return 'The global namespace cannot be private';
}

return 'Team members can pull images from this namespace';
},
},

methods: {
showLoading(visibility) {
return this.onGoingRequest &&
visibility === this.newVisibility;
},

change(visibility) {
const currentVisibility = this.namespace.attributes.visibility;

if (visibility === currentVisibility) {
return;
}

set(this, 'onGoingRequest', true);
set(this, 'newVisibility', visibility);

NamespacesService.changeVisibility(this.namespace.id, { visibility }).then(() => {
set(this.namespace.attributes, 'visibility', visibility);
Alert.show(`Visibility of '${this.namespace.attributes.clean_name}' namespace updated`);
}).catch(() => {
Alert.show('An error happened while updating namespace visibility');
}).finally(() => {
set(this, 'onGoingRequest', false);
set(this, 'newVisibility', null);
});
},
},
};
9 changes: 2 additions & 7 deletions app/assets/javascripts/modules/namespaces/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import NamespacesIndexPage from './pages/index';
import './pages/index';

import NamespacesShowPage from './pages/show';

const NAMESPACES_INDEX_ROUTE = 'namespaces/index';
const NAMESPACES_SHOW_ROUTE = 'namespaces/show';

$(() => {
const $body = $('body');
const route = $body.data('route');

if (route === NAMESPACES_INDEX_ROUTE) {
// eslint-disable-next-line
new NamespacesIndexPage($body);
}

if (route === NAMESPACES_SHOW_ROUTE) {
// eslint-disable-next-line
new NamespacesShowPage($body);
Expand Down
Loading

0 comments on commit 3e145dc

Please sign in to comment.