mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-10 15:14:33 +08:00
Merge pull request #850 from ZmagoD/zd_SCI_1731
add scenic gem, teams datatables sql view
This commit is contained in:
commit
7386974d64
32 changed files with 1919 additions and 1216 deletions
2
Gemfile
2
Gemfile
|
@ -66,6 +66,7 @@ gem 'delayed_paperclip',
|
|||
gem 'rubyzip'
|
||||
gem 'jbuilder' # JSON structures via a Builder-style DSL
|
||||
gem 'activerecord-import'
|
||||
gem 'scenic', '~> 1.4'
|
||||
|
||||
gem 'paperclip', '~> 5.1' # File attachment, image attachment library
|
||||
gem 'aws-sdk', '~> 2'
|
||||
|
@ -111,6 +112,7 @@ group :test do
|
|||
gem 'poltergeist'
|
||||
gem 'phantomjs', :require => 'phantomjs/poltergeist'
|
||||
gem 'simplecov', require: false
|
||||
gem 'json_matchers'
|
||||
end
|
||||
|
||||
group :production do
|
||||
|
|
|
@ -244,6 +244,10 @@ GEM
|
|||
js_cookie_rails (2.1.4)
|
||||
railties (>= 3.1)
|
||||
json (1.8.6)
|
||||
json-schema (2.8.0)
|
||||
addressable (>= 2.4)
|
||||
json_matchers (0.7.2)
|
||||
json-schema (~> 2.7)
|
||||
kaminari (1.1.1)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.1.1)
|
||||
|
@ -415,6 +419,9 @@ GEM
|
|||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
scenic (1.4.0)
|
||||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
scss_lint (0.55.0)
|
||||
rake (>= 0.9, < 13)
|
||||
sass (~> 3.4.20)
|
||||
|
@ -530,6 +537,7 @@ DEPENDENCIES
|
|||
jquery-turbolinks
|
||||
jquery-ui-rails
|
||||
js_cookie_rails
|
||||
json_matchers
|
||||
kaminari
|
||||
listen (~> 3.0)
|
||||
logging (~> 2.0.0)
|
||||
|
@ -559,6 +567,7 @@ DEPENDENCIES
|
|||
rubyzip
|
||||
sanitize (~> 4.4)
|
||||
sass-rails (~> 5.0.6)
|
||||
scenic (~> 1.4)
|
||||
scss_lint
|
||||
sdoc (~> 0.4.0)
|
||||
shoulda-matchers
|
||||
|
|
|
@ -4,8 +4,9 @@ module ClientApi
|
|||
include ClientApi::Users::UserTeamsHelper
|
||||
|
||||
def index
|
||||
teams = current_user.datatables_teams
|
||||
success_response(template: '/client_api/teams/index',
|
||||
locals: { teams: current_user.teams_data })
|
||||
locals: { teams: teams })
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -55,6 +56,11 @@ module ClientApi
|
|||
error_response(message: error.to_s)
|
||||
end
|
||||
|
||||
def current_team
|
||||
success_response(template: '/client_api/teams/current_team',
|
||||
locals: { team: current_user.current_team })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def team_params
|
||||
|
|
|
@ -1,51 +1,71 @@
|
|||
// @flow
|
||||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { NavDropdown, MenuItem, Glyphicon } from "react-bootstrap";
|
||||
import styled from "styled-components";
|
||||
import _ from "lodash";
|
||||
|
||||
import { ROOT_PATH } from "../../../config/routes";
|
||||
import { ROOT_PATH, SETTINGS_NEW_TEAM_ROUTE } from "../../../config/routes";
|
||||
import { BORDER_GRAY_COLOR } from "../../../config/constants/colors";
|
||||
import { changeTeam } from "../../actions/TeamsActions";
|
||||
import { getTeamsList } from "../../actions/TeamsActions";
|
||||
import { getCurrentTeam, changeTeam } from "../../actions/TeamsActions";
|
||||
import { getTeams } from "../../../services/api/teams_api";
|
||||
|
||||
const StyledNavDropdown = styled(NavDropdown)`
|
||||
border-left: 1px solid ${BORDER_GRAY_COLOR};
|
||||
border-right: 1px solid ${BORDER_GRAY_COLOR};
|
||||
`;
|
||||
|
||||
class TeamSwitch extends Component {
|
||||
constructor(props) {
|
||||
type State = {
|
||||
allTeams: Array<Teams$Team>
|
||||
}
|
||||
|
||||
type Props = {
|
||||
current_team: Teams$CurrentTeam,
|
||||
eventKey: string,
|
||||
getCurrentTeam: Function,
|
||||
changeTeam: Function
|
||||
}
|
||||
|
||||
class TeamSwitch extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.displayTeams = this.displayTeams.bind(this);
|
||||
(this: any).state = { allTeams: [] };
|
||||
(this: any).displayTeams = this.displayTeams.bind(this);
|
||||
(this: any).setTeams = this.setTeams.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getTeamsList();
|
||||
this.props.getCurrentTeam();
|
||||
}
|
||||
|
||||
setTeams() {
|
||||
getTeams().then(response => (this: any).setState({ allTeams: response }));
|
||||
}
|
||||
|
||||
changeTeam(teamId) {
|
||||
this.props.changeTeam(teamId);
|
||||
window.location = ROOT_PATH;
|
||||
setTimeout(() => {
|
||||
window.location = ROOT_PATH;
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
displayTeams() {
|
||||
if (!_.isEmpty(this.props.all_teams)) {
|
||||
return this.props.all_teams
|
||||
.filter(team => !team.current_team)
|
||||
if (!_.isEmpty((this: any).state.allTeams)) {
|
||||
return (this: any).state.allTeams
|
||||
.filter(team => team.id !== this.props.current_team.id)
|
||||
.map(team => (
|
||||
<MenuItem onSelect={() => this.changeTeam(team.id)} key={team.id}>
|
||||
{team.name}
|
||||
</MenuItem>
|
||||
));
|
||||
}
|
||||
return <MenuItem />;
|
||||
}
|
||||
|
||||
newTeamLink() {
|
||||
return (
|
||||
<MenuItem href="/users/settings/teams/new" key="addNewTeam">
|
||||
<MenuItem href={SETTINGS_NEW_TEAM_ROUTE} key="addNewTeam">
|
||||
<Glyphicon glyph="plus" />
|
||||
<FormattedMessage id="global_team_switch.new_team" />
|
||||
</MenuItem>
|
||||
|
@ -57,6 +77,7 @@ class TeamSwitch extends Component {
|
|||
<StyledNavDropdown
|
||||
noCaret
|
||||
eventKey={this.props.eventKey}
|
||||
onClick={this.setTeams}
|
||||
title={
|
||||
<span>
|
||||
<i className="fa fa-users" /> {this.props.current_team.name}
|
||||
|
@ -72,38 +93,11 @@ class TeamSwitch extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
TeamSwitch.propTypes = {
|
||||
getTeamsList: PropTypes.func.isRequired,
|
||||
eventKey: PropTypes.number.isRequired,
|
||||
changeTeam: PropTypes.func.isRequired,
|
||||
all_teams: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
current_team: PropTypes.bool.isRequired
|
||||
}).isRequired
|
||||
),
|
||||
current_team: PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
current_team: PropTypes.bool.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
// Map the states from store to component
|
||||
const mapStateToProps = ({ all_teams, current_team }) => ({
|
||||
current_team,
|
||||
all_teams: all_teams.collection
|
||||
const mapStateToProps = ({ current_team }) => ({
|
||||
current_team
|
||||
});
|
||||
|
||||
// Map the fetch activity action to component
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
changeTeam(teamId) {
|
||||
dispatch(changeTeam(teamId));
|
||||
},
|
||||
getTeamsList() {
|
||||
dispatch(getTeamsList());
|
||||
}
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TeamSwitch);
|
||||
export default connect(mapStateToProps, { getCurrentTeam, changeTeam })(
|
||||
TeamSwitch
|
||||
);
|
||||
|
|
|
@ -5,7 +5,11 @@ import type {
|
|||
Actopm$SetCurrentTeam
|
||||
} from "flow-typed";
|
||||
import type { Dispatch } from "redux-thunk";
|
||||
import { getTeams, changeCurrentTeam } from "../../services/api/teams_api";
|
||||
import {
|
||||
getTeams,
|
||||
changeCurrentTeam,
|
||||
getCurrentTeam as fetchCurrentTeam
|
||||
} from "../../services/api/teams_api";
|
||||
import { GET_LIST_OF_TEAMS, SET_CURRENT_TEAM } from "../../config/action_types";
|
||||
|
||||
export function addTeamsData(data: Array<Teams$Team>): Action$AddTeamData {
|
||||
|
@ -15,7 +19,7 @@ export function addTeamsData(data: Array<Teams$Team>): Action$AddTeamData {
|
|||
};
|
||||
}
|
||||
|
||||
export function setCurrentTeam(team: Teams$Team): Actopm$SetCurrentTeam {
|
||||
export function setCurrentTeam(team: Teams$CurrentTeam): Actopm$SetCurrentTeam {
|
||||
return {
|
||||
team,
|
||||
type: SET_CURRENT_TEAM
|
||||
|
@ -26,9 +30,7 @@ export function getTeamsList(): Dispatch {
|
|||
return dispatch => {
|
||||
getTeams()
|
||||
.then(response => {
|
||||
const { teams, currentTeam } = response;
|
||||
dispatch(addTeamsData(teams));
|
||||
dispatch(setCurrentTeam(currentTeam));
|
||||
dispatch(addTeamsData(response));
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("get Teams Error: ", error);
|
||||
|
@ -36,14 +38,16 @@ export function getTeamsList(): Dispatch {
|
|||
};
|
||||
}
|
||||
|
||||
export function getCurrentTeam(): Dispatch {
|
||||
return dispatch => {
|
||||
fetchCurrentTeam().then(response => dispatch(setCurrentTeam(response)));
|
||||
};
|
||||
}
|
||||
|
||||
export function changeTeam(teamID: number): Dispatch {
|
||||
return dispatch => {
|
||||
changeCurrentTeam(teamID)
|
||||
.then(response => {
|
||||
const { teams, currentTeam } = response;
|
||||
dispatch(addTeamsData(teams));
|
||||
dispatch(setCurrentTeam(currentTeam));
|
||||
})
|
||||
.then(response => dispatch(addTeamsData(response)))
|
||||
.catch(error => {
|
||||
console.log("get Teams Error: ", error);
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ import { leaveTeam } from "../../../../../services/api/teams_api";
|
|||
|
||||
import {
|
||||
addTeamsData,
|
||||
setCurrentTeam
|
||||
getCurrentTeam
|
||||
} from "../../../../../components/actions/TeamsActions";
|
||||
|
||||
type Team = {
|
||||
|
@ -23,7 +23,7 @@ type Props = {
|
|||
team: Team,
|
||||
addTeamsData: Function,
|
||||
hideLeaveTeamModal: Function,
|
||||
setCurrentTeam: Function
|
||||
getCurrentTeam: Function
|
||||
};
|
||||
|
||||
class LeaveTeamModal extends Component<Props> {
|
||||
|
@ -41,10 +41,9 @@ class LeaveTeamModal extends Component<Props> {
|
|||
const { id, user_team_id } = this.props.team;
|
||||
leaveTeam(id, user_team_id)
|
||||
.then(response => {
|
||||
const { teams, currentTeam } = response;
|
||||
this.props.updateTeamsState(teams);
|
||||
this.props.addTeamsData(teams);
|
||||
this.props.setCurrentTeam(currentTeam);
|
||||
this.props.updateTeamsState(response);
|
||||
this.props.addTeamsData(response);
|
||||
this.props.getCurrentTeam();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("error: ", error.response.data.message);
|
||||
|
@ -101,5 +100,5 @@ class LeaveTeamModal extends Component<Props> {
|
|||
|
||||
export default connect(null, {
|
||||
addTeamsData,
|
||||
setCurrentTeam
|
||||
getCurrentTeam
|
||||
})(LeaveTeamModal);
|
||||
|
|
|
@ -12,7 +12,6 @@ import LeaveTeamModal from "./LeaveTeamModal";
|
|||
const DefaultTeam = {
|
||||
id: 0,
|
||||
name: "",
|
||||
current_team: false,
|
||||
user_team_id: 0,
|
||||
role: "",
|
||||
members: 0,
|
||||
|
|
|
@ -44,10 +44,10 @@ TeamsPageDetails.propTypes = {
|
|||
PropTypes.shape({
|
||||
id: number.isRequired,
|
||||
name: string.isRequired,
|
||||
current_team: bool.isRequired,
|
||||
role: string.isRequired,
|
||||
members: number.isRequired,
|
||||
can_be_left: bool.isRequired
|
||||
can_be_left: bool.isRequired,
|
||||
user_team_id: number.isRequired
|
||||
})
|
||||
)
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from "react";
|
||||
import styled from "styled-components";
|
||||
import { Breadcrumb } from "react-bootstrap";
|
||||
|
@ -36,7 +35,7 @@ class SettingsTeams extends Component<Props, State> {
|
|||
{
|
||||
id: 0,
|
||||
name: "",
|
||||
current_team: true,
|
||||
user_team_id: 0,
|
||||
role: "",
|
||||
members: 0,
|
||||
can_be_left: false
|
||||
|
@ -47,8 +46,8 @@ class SettingsTeams extends Component<Props, State> {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
getTeams().then(({ teams }) => {
|
||||
this.updateTeamsState(teams);
|
||||
getTeams().then(response => {
|
||||
this.updateTeamsState(response);
|
||||
});
|
||||
// set team tab on active
|
||||
this.props.tabState("2");
|
||||
|
|
|
@ -11,14 +11,15 @@ export const TEAMS_PATH = "/client_api/teams";
|
|||
export const CHANGE_TEAM_PATH = "/client_api/teams/change_team";
|
||||
export const TEAM_DETAILS_PATH = "/client_api/teams/:team_id/details";
|
||||
export const TEAM_UPDATE_PATH = "/client_api/teams/update";
|
||||
export const CURRENT_USER_PATH = "/client_api/current_user_info"
|
||||
export const CURRENT_TEAM_PATH = "/client_api/teams/current_team";
|
||||
|
||||
// users
|
||||
export const USER_PROFILE_INFO = "/client_api/users/profile_info";
|
||||
export const UPDATE_USER_PATH = "/client_api/users/update";
|
||||
export const PREFERENCES_INFO_PATH = "/client_api/users/preferences_info"
|
||||
export const STATISTICS_INFO_PATH = "/client_api/users/statistics_info"
|
||||
export const SIGN_OUT_PATH = "/client_api/users/sign_out_user"
|
||||
export const PREFERENCES_INFO_PATH = "/client_api/users/preferences_info";
|
||||
export const STATISTICS_INFO_PATH = "/client_api/users/statistics_info";
|
||||
export const SIGN_OUT_PATH = "/client_api/users/sign_out_user";
|
||||
export const CURRENT_USER_PATH = "/client_api/current_user_info";
|
||||
|
||||
// info dropdown_title
|
||||
export const CUSTOMER_SUPPORT_LINK = "http://scinote.net/support";
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// @flow
|
||||
import type { Teams$NewTeam, Team$Update } from "flow-typed";
|
||||
import _ from "lodash";
|
||||
import axiosInstance from "./config";
|
||||
import {
|
||||
TEAM_DETAILS_PATH,
|
||||
TEAM_UPDATE_PATH,
|
||||
TEAMS_PATH,
|
||||
CHANGE_TEAM_PATH,
|
||||
LEAVE_TEAM_PATH
|
||||
LEAVE_TEAM_PATH,
|
||||
CURRENT_TEAM_PATH
|
||||
} from "./endpoints";
|
||||
|
||||
export const getTeamDetails = (teamID: number): Promise<*> => {
|
||||
|
@ -24,24 +24,17 @@ export const updateTeam = (teamID: number, teamData: Team$Update): Promise<*> =>
|
|||
.then(({ data }) => data.team);
|
||||
|
||||
export const getTeams = (): Promise<*> =>
|
||||
axiosInstance.get(TEAMS_PATH).then(({ data }) => {
|
||||
const teams = data.teams.collection;
|
||||
const currentTeam = _.find(teams, team => team.current_team);
|
||||
return { teams, currentTeam };
|
||||
});
|
||||
axiosInstance.get(TEAMS_PATH).then(({ data }) => data.teams);
|
||||
|
||||
export const changeCurrentTeam = (teamID: number): Promise<*> =>
|
||||
axiosInstance.post(CHANGE_TEAM_PATH, { team_id: teamID }).then(({ data }) => {
|
||||
const teams = data.teams.collection;
|
||||
const currentTeam = _.find(teams, team => team.current_team);
|
||||
return { teams, currentTeam };
|
||||
});
|
||||
axiosInstance
|
||||
.post(CHANGE_TEAM_PATH, { team_id: teamID })
|
||||
.then(({ data }) => data.teams);
|
||||
|
||||
export const leaveTeam = (teamID: number, userTeamID: number): Promise<*> => {
|
||||
const teamUrl = `${LEAVE_TEAM_PATH}?team=${teamID}&user_team=${userTeamID}`;
|
||||
return axiosInstance.delete(teamUrl).then(({ data }) => {
|
||||
const teams = data.teams.collection;
|
||||
const currentTeam = _.find(teams, team => team.current_team);
|
||||
return { teams, currentTeam };
|
||||
});
|
||||
return axiosInstance.delete(teamUrl).then(({ data }) => data.teams);
|
||||
};
|
||||
|
||||
export const getCurrentTeam = (): Promise<*> =>
|
||||
axiosInstance.get(CURRENT_TEAM_PATH).then(({ data }) => data.team);
|
||||
|
|
|
@ -198,6 +198,7 @@ class User < ApplicationRecord
|
|||
has_many :user_notifications, inverse_of: :user
|
||||
has_many :notifications, through: :user_notifications
|
||||
has_many :zip_exports, inverse_of: :user, dependent: :destroy
|
||||
has_many :datatables_teams, class_name: '::Views::Datatables::DatatablesTeam'
|
||||
|
||||
# If other errors besides parameter "avatar" exist,
|
||||
# they will propagate to "avatar" also, so remove them
|
||||
|
@ -218,25 +219,6 @@ class User < ApplicationRecord
|
|||
Team.find_by_id(self.current_team_id)
|
||||
end
|
||||
|
||||
# Retrieves the data needed in all teams page
|
||||
def teams_data
|
||||
ActiveRecord::Base.connection.execute(
|
||||
ActiveRecord::Base.send(
|
||||
:sanitize_sql_array,
|
||||
['SELECT teams.id AS id, teams.name AS name, user_teams.role ' \
|
||||
'AS role, (SELECT COUNT(*) FROM user_teams WHERE ' \
|
||||
'user_teams.team_id = teams.id) AS members, ' \
|
||||
'CASE WHEN teams.id=? THEN true ELSE false END AS current_team, ' \
|
||||
'CASE WHEN (SELECT COUNT(*) FROM user_teams WHERE ' \
|
||||
'user_teams.team_id=teams.id AND role=2) >= 2 THEN true ELSE false ' \
|
||||
'END AS can_be_left, user_teams.id AS user_team_id ' \
|
||||
'FROM teams INNER JOIN user_teams ON ' \
|
||||
'teams.id=user_teams.team_id WHERE user_teams.user_id=?',
|
||||
self.current_team_id, self.id]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
# Search all active users for username & email. Can
|
||||
# also specify which team to ignore.
|
||||
def self.search(
|
||||
|
|
16
app/models/views/datatables/datatables_team.rb
Normal file
16
app/models/views/datatables/datatables_team.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
module Views
|
||||
module Datatables
|
||||
class DatatablesTeam < ApplicationRecord
|
||||
belongs_to :user
|
||||
default_scope { order(name: :asc) }
|
||||
|
||||
private
|
||||
|
||||
# this isn't strictly necessary, but it will prevent
|
||||
# rails from calling save, which would fail anyway.
|
||||
def readonly?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,7 +31,7 @@ module ClientApi
|
|||
end
|
||||
|
||||
def teams_data
|
||||
{ teams: @user.teams_data }
|
||||
{ teams: @user.datatables_teams }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ module ClientApi
|
|||
|
||||
def teams_data
|
||||
{
|
||||
teams: @user.teams_data,
|
||||
teams: @user.datatables_teams,
|
||||
flash_message: I18n.t('client_api.user_teams.leave_flash',
|
||||
team: @team.name)
|
||||
}
|
||||
|
|
4
app/views/client_api/teams/current_team.json.jbuilder
Normal file
4
app/views/client_api/teams/current_team.json.jbuilder
Normal file
|
@ -0,0 +1,4 @@
|
|||
json.team do
|
||||
json.id team.id
|
||||
json.name team.name
|
||||
end
|
|
@ -1,11 +1,10 @@
|
|||
json.teams do
|
||||
json.collection teams do |team|
|
||||
json.id team.fetch('id')
|
||||
json.name team.fetch('name')
|
||||
json.members team.fetch('members')
|
||||
json.role retrive_role_name(team.fetch('role') { nil })
|
||||
json.current_team team.fetch('current_team')
|
||||
json.can_be_left team.fetch('can_be_left')
|
||||
json.user_team_id team.fetch('user_team_id')
|
||||
json.array! teams do |team|
|
||||
json.id team.id
|
||||
json.name team.name
|
||||
json.members team.members
|
||||
json.role retrive_role_name(team.role)
|
||||
json.can_be_left team.can_be_left
|
||||
json.user_team_id team.user_team_id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ get '/teams', to: 'teams/teams#index'
|
|||
namespace :teams do
|
||||
get '/new', to: 'teams#new'
|
||||
get '/:team_id/details', to: 'teams#details'
|
||||
get '/current_team', to: 'teams#current_team'
|
||||
post '/', to: 'teams#create'
|
||||
post '/change_team', to: 'teams#change_team'
|
||||
post '/update', to: 'teams#update'
|
||||
|
|
5
db/migrate/20171026090804_create_datatables_teams.rb
Normal file
5
db/migrate/20171026090804_create_datatables_teams.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class CreateDatatablesTeams < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_view :datatables_teams
|
||||
end
|
||||
end
|
1261
db/schema.rb
1261
db/schema.rb
File diff suppressed because it is too large
Load diff
13
db/views/datatables_teams_v01.sql
Normal file
13
db/views/datatables_teams_v01.sql
Normal file
|
@ -0,0 +1,13 @@
|
|||
SELECT
|
||||
teams.id AS id,
|
||||
teams.name AS name,
|
||||
user_teams.role AS role,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM user_teams
|
||||
WHERE user_teams.team_id = teams.id
|
||||
) AS members,
|
||||
CASE WHEN teams.created_by_id = user_teams.user_id THEN false ELSE true END AS can_be_left,
|
||||
user_teams.id AS user_team_id,
|
||||
user_teams.user_id AS user_id
|
||||
FROM teams INNER JOIN user_teams ON teams.id=user_teams.team_id
|
6
flow-typed/teams_types.js
vendored
6
flow-typed/teams_types.js
vendored
|
@ -24,9 +24,13 @@ export type Teams$NewTeam = {
|
|||
export type Teams$Team = {
|
||||
id: number,
|
||||
name: string,
|
||||
current_team: boolean,
|
||||
role: string,
|
||||
members: number,
|
||||
can_be_left: boolean,
|
||||
user_team_id: number
|
||||
};
|
||||
|
||||
export type Teams$CurrentTeam = {
|
||||
id: number,
|
||||
name: string
|
||||
}
|
||||
|
|
1505
package-lock.json
generated
1505
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -128,4 +128,9 @@ describe ClientApi::Teams::TeamsController, type: :controller do
|
|||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #current_team' do
|
||||
let(:subject) { get :current_team, as: :json }
|
||||
it { is_expected.to have_http_status(:ok) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
FactoryGirl.define do
|
||||
factory :my_module_group do
|
||||
name Faker::Name.unique.name
|
||||
experiment { Experiment.first || create(:experiment_two) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,6 @@ describe MyModuleGroup, type: :model do
|
|||
|
||||
describe 'Database table' do
|
||||
it { should have_db_column :id }
|
||||
it { should have_db_column :name }
|
||||
it { should have_db_column :created_at }
|
||||
it { should have_db_column :updated_at }
|
||||
it { should have_db_column :created_by_id }
|
||||
|
@ -21,8 +20,6 @@ describe MyModuleGroup, type: :model do
|
|||
end
|
||||
|
||||
describe 'Should be a valid object' do
|
||||
it { should validate_presence_of :name }
|
||||
it { should validate_presence_of :experiment }
|
||||
it { should validate_length_of(:name).is_at_most(Constants::NAME_MAX_LENGTH) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -152,40 +152,10 @@ describe User, type: :model do
|
|||
end
|
||||
let(:user_two) { create :user, email: 'user2@asdf.com' }
|
||||
|
||||
it 'in a specific format: {id: .., name: .., members: .., role: ' \
|
||||
'.., current_team: .., can_be_left: ..}' do
|
||||
user_team = create :user_team, team: team, user: user_one
|
||||
expected_result = {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
members: 1,
|
||||
role: 2,
|
||||
current_team: true,
|
||||
can_be_left: false,
|
||||
user_team_id: user_team.id
|
||||
}
|
||||
|
||||
user_one.teams_data.first.each do |k, v|
|
||||
expect(v).to eq(expected_result.fetch(k.to_sym))
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return correct number of team members' do
|
||||
user_team = create :user_team, team: team, user: user_one
|
||||
create :user_team, team: team, user: user_one
|
||||
create :user_team, team: team, user: user_two
|
||||
expected_result = {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
members: 2,
|
||||
role: 2,
|
||||
current_team: true,
|
||||
can_be_left: true,
|
||||
user_team_id: user_team.id
|
||||
}
|
||||
|
||||
user_one.teams_data.first.each do |k, v|
|
||||
expect(v).to eq(expected_result.fetch(k.to_sym))
|
||||
end
|
||||
expect(user_one.datatables_teams.first.members).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
|
|
23
spec/models/views/datatables/teams_spec.rb
Normal file
23
spec/models/views/datatables/teams_spec.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Views::Datatables::DatatablesTeam, type: :model do
|
||||
describe 'Database table' do
|
||||
it { should have_db_column :id }
|
||||
it { should have_db_column :name }
|
||||
it { should have_db_column :members }
|
||||
it { should have_db_column :role }
|
||||
it { should have_db_column :user_team_id }
|
||||
it { should have_db_column :user_id }
|
||||
it { should have_db_column :can_be_left }
|
||||
end
|
||||
|
||||
describe 'is readonly' do
|
||||
let(:user) { create :user }
|
||||
it do
|
||||
expect {
|
||||
Views::Datatables::DatatablesTeam.create!(user_id: user.id)
|
||||
}.to raise_error(ActiveRecord::ReadOnlyRecord,
|
||||
'Views::Datatables::DatatablesTeam is marked as readonly')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -55,10 +55,11 @@ describe ClientApi::TeamsService do
|
|||
ClientApi::TeamsService.new(current_user: user_one, team_id: team_one.id)
|
||||
end
|
||||
|
||||
it 'should return user teams' do
|
||||
it 'should return an array of valid teams' do
|
||||
create :user_team, user: user_one, team: team_one
|
||||
data = team_service.teams_data.fetch(:teams)
|
||||
expect(data.first.fetch('name')).to eq team_one.name
|
||||
expect(team_service.teams_data).to(
|
||||
match_response_schema('datatables_teams')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ describe ClientApi::UserTeamService do
|
|||
user_team_id: user_team.id,
|
||||
role: 1
|
||||
)
|
||||
team_id = ut_service.teams_data[:teams].first.fetch('id')
|
||||
team_id = ut_service.teams_data[:teams].first.id
|
||||
expect(team_id).to eq team_one.id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,6 +18,7 @@ require 'simplecov'
|
|||
require 'faker'
|
||||
require 'active_record'
|
||||
require 'bullet'
|
||||
require "json_matchers/rspec"
|
||||
|
||||
RSpec.configure do |config|
|
||||
# rspec-expectations config goes here. You can use an alternate
|
||||
|
|
21
spec/support/api/schemas/datatables_teams.json
Normal file
21
spec/support/api/schemas/datatables_teams.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required": ["teams"],
|
||||
"properties": {
|
||||
"teams": {
|
||||
"type": "array",
|
||||
"items":{
|
||||
"required": ["id", "name", "members", "role", "can_be_left", "user_team_id"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"members": { "type": "integer" },
|
||||
"role": { "type": "integer" },
|
||||
"can_be_left": { "type": "boolean" },
|
||||
"user_team_id": { "type": "integer" },
|
||||
"user_id": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue