Merge pull request #319 from guillegiu/master

feature #174
This commit is contained in:
Ivan Diaz 2018-10-01 17:23:26 -03:00 committed by GitHub
commit 515e615418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 162 additions and 37 deletions

View File

@ -125,4 +125,4 @@ export default {
})
}
}
};
};

View File

@ -126,4 +126,4 @@ class ActivityRow extends React.Component {
}
}
export default ActivityRow;
export default ActivityRow;

View File

@ -20,7 +20,8 @@ class TicketEvent extends React.Component {
]),
author: React.PropTypes.object,
content: React.PropTypes.string,
date: React.PropTypes.string
date: React.PropTypes.string,
private: React.PropTypes.string,
};
render() {
@ -83,7 +84,8 @@ class TicketEvent extends React.Component {
<span className="ticket-event__comment-pointer" />
<div className="ticket-event__comment-author">
<span className="ticket-event__comment-author-name">{this.props.author.name}</span>
<span className="ticket-event__comment-author-type">({i18n((this.props.author.staff) ? 'STAFF' : 'CUSTOMER')})</span>
<span className="ticket-event__comment-author-type">{i18n((this.props.author.staff) ? 'STAFF' : 'CUSTOMER')}</span>
{(this.props.private*1) ? <span className="ticket-event__comment-author-type">{i18n('PRIVATE')}</span> : null}
</div>
<div className="ticket-event__comment-date">{DateTransformer.transformToString(this.props.date)}</div>
<div className="ticket-event__comment-content" dangerouslySetInnerHTML={{__html: this.props.content}}></div>
@ -205,7 +207,8 @@ class TicketEvent extends React.Component {
'ticket-event_close': this.props.type === 'CLOSE',
'ticket-event_reopen': this.props.type === 'RE_OPEN',
'ticket-event_department': this.props.type === 'DEPARTMENT_CHANGED',
'ticket-event_priority': this.props.type === 'PRIORITY_CHANGED'
'ticket-event_priority': this.props.type === 'PRIORITY_CHANGED',
'ticket-event_private': this.props.private*1,
};
return classNames(classes);

View File

@ -66,10 +66,14 @@
color: $primary-black;
&-type {
font-size: 10px;
font-size: 10.6px;
padding-left: 10px;
color: $secondary-blue;
font-variant: small-caps;
background-color: very-light-grey;
border: 2px solid;
border-radius: 4px;
padding: 4px;
margin-left: 12px;
}
}
@ -78,6 +82,8 @@
border: 2px solid $light-grey;
border-bottom: none;
padding: 12px;
font-size: 10.6px;
font-family: helvetica;
background-color: $light-grey;
}
@ -176,4 +182,24 @@
padding-top: 5px;
}
}
}
&_private {
.ticket-event__comment-pointer {
border-right-color: $light-yellow;
}
.ticket-event__comment-date {
background-color: $light-yellow;
border-color: $light-yellow;
}
.ticket-event__comment-content {
background-color: $very-light-yellow;
border-color: $very-light-yellow;
}
.ticket-event__staff-pic {
background-color: $light-yellow;
border-color: $light-yellow;
}
.ticket-event__file {
background-color: $light-yellow;
}
}
}

View File

@ -47,7 +47,8 @@ class TicketViewer extends React.Component {
state = {
loading: false,
commentValue: TextEditor.createEmpty(),
commentEdited: false
commentEdited: false,
commentPrivate: false
};
componentDidMount() {
@ -211,19 +212,22 @@ class TicketViewer extends React.Component {
renderResponseField() {
return (
<div className="ticket-viewer__response">
<div className="ticket-viewer__response-title row">{i18n('RESPOND')}</div>
{this.renderCustomResponses()}
<div className="ticket-viewer__response-field row">
<Form {...this.getCommentFormProps()}>
<Form {...this.getCommentFormProps()}>
<div className="ticket-viewer__response-title row">{i18n('RESPOND')}</div>
<div className="ticket-viewer__actions row">
{this.renderCustomResponses()}
{this.renderPrivate()}
</div>
<div className="ticket-viewer__response-field row">
<FormField name="content" validation="TEXT_AREA" required field="textarea" fieldProps={{allowImages: this.props.allowAttachments}}/>
{(this.props.allowAttachments) ? <FormField name="file" field="file"/> : null}
<div className="ticket-viewer__response-buttons">
<SubmitButton type="secondary">{i18n('RESPOND_TICKET')}</SubmitButton>
<Button size="medium" onClick={this.onCloseTicketClick.bind(this)}>{i18n('CLOSE_TICKET')}</Button>
</div>
</Form>
</div>
{(this.state.commentError) ? this.renderCommentError() : null}
</div>
{(this.state.commentError) ? this.renderCommentError() : null}
</Form>
</div>
);
}
@ -243,7 +247,7 @@ class TicketViewer extends React.Component {
});
customResponsesNode = (
<div className="ticket-viewer__response-custom row">
<div className="ticket-viewer__response-custom">
<DropDown items={customResponses} size="medium" onChange={this.onCustomResponsesChanged.bind(this)}/>
</div>
);
@ -258,6 +262,18 @@ class TicketViewer extends React.Component {
);
}
renderPrivate() {
if (this.props.userStaff) {
return (
<div className="ticket-viewer__private">
<FormField label={i18n('PRIVATE')} name="private" field="checkbox"/>
</div>
);
} else {
return null;
}
}
getCommentFormProps() {
return {
onSubmit: this.onSubmit.bind(this),
@ -265,11 +281,13 @@ class TicketViewer extends React.Component {
onChange: (formState) => {this.setState({
commentValue: formState.content,
commentFile: formState.file,
commentEdited: true
commentEdited: true,
commentPrivate: formState.private
})},
values: {
'content': this.state.commentValue,
'file': this.state.commentFile
'file': this.state.commentFile,
'private': this.state.commentPrivate
}
};
}
@ -387,7 +405,7 @@ class TicketViewer extends React.Component {
dataAsForm: true,
data: _.extend({
ticketNumber: this.props.ticket.ticketNumber
}, formState, TextEditor.getContentFormData(formState.content))
}, formState, {private: formState.private ? 1 : 0}, TextEditor.getContentFormData(formState.content))
}).then(this.onCommentSuccess.bind(this), this.onCommentFail.bind(this));
}

View File

@ -10,6 +10,9 @@
font-size: 16px;
padding: 6px 0;
}
&__private {
}
&__number {
color: white;
@ -76,11 +79,19 @@
padding: 20px 0 0 20px;
text-align: left;
}
&-buttons {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: space-between;
align-items: center;
}
}
&__actions {
background-color: $very-light-grey;
display:flex;
align-items:center;justify-content: space-between;
}
}

View File

@ -26,7 +26,7 @@ class AdminLoginPage extends React.Component {
loadingLogin: false,
loadingRecover: false
};
componentDidUpdate(prevProps) {
if (!prevProps.session.failed && this.props.session.failed) {
this.refs.loginForm.refs.password.focus();
@ -34,7 +34,7 @@ class AdminLoginPage extends React.Component {
}
render() {
return (
return (
<div className="admin-login-page">
<WidgetTransition sideToShow={this.state.sideToShow} className={classNames('admin-login-page__container', this.props.className)}>
{this.renderLogin()}
@ -43,7 +43,7 @@ class AdminLoginPage extends React.Component {
</div>
);
}
renderLogin() {
return (
<div>
@ -65,7 +65,7 @@ class AdminLoginPage extends React.Component {
</div>
);
}
renderPasswordRecovery() {
return (
<div>
@ -161,7 +161,7 @@ class AdminLoginPage extends React.Component {
loginFormErrors: errors
});
}
onRecoverFormErrorsValidation(errors) {
this.setState({
recoverFormErrors: errors

View File

@ -224,4 +224,4 @@ export default connect((store) => {
return {
level: store.session.userLevel
};
})(AdminPanelMenu);
})(AdminPanelMenu);

View File

@ -14,7 +14,7 @@ class AdminPanelStats extends React.Component {
</div>
);
}
}
export default AdminPanelStats;
export default AdminPanelStats;

View File

@ -183,6 +183,7 @@ export default {
'HIMSELF': 'ele mesmo',
'ADD_USER': 'Adicionar usuário',
'UPLOAD_FILE': 'Subir arquivo',
'PRIVATE': 'privado',
'ENABLE_USER': 'Ativar usuário',
'DISABLE_USER': 'Desativar usuário',
@ -244,7 +245,7 @@ export default {
'INSTALLATION_COMPLETED': 'Instalação completa.',
'INSTALLATION_COMPLETED_DESCRIPTION': 'A instalação do OpenSupports está concluída. Redirecionando para o painel de administração ...',
'STEP_TITLE': 'Passo {current} de {total} - {title}',
'STEP_TITLE': 'Passo {current} de {total} - {title}',
'STEP_1_DESCRIPTION': 'Selecione o idioma preferido para o assistente de instalação.',
'STEP_2_DESCRIPTION': 'Aqui estão listados os requisitos para executar o OpenSupports. Certifique-se de que todos os requisitos estão satisfeitos.',
'STEP_3_DESCRIPTION': 'Preencha a configuração do banco de dados MySQL.',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': '他自己',
'ADD_USER': '添加用户',
'UPLOAD_FILE': '上传文件',
'PRIVATE': '私人的',
'ENABLE_USER': '启用用户',
'DISABLE_USER': '禁用用户',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'selbst',
'ADD_USER': 'Benutzer hinzufügen',
'UPLOAD_FILE': 'Datei hochladen',
'PRIVATE': 'Privatgelände',
'ENABLE_USER': 'Benutzer aktivieren',
'DISABLE_USER': 'Benutzer deaktivieren',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'himself',
'ADD_USER': 'Add user',
'UPLOAD_FILE': 'Upload file',
'PRIVATE': 'private',
'ENABLE_USER': 'Enable User',
'DISABLE_USER': 'Disable User',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'si mismo',
'ADD_USER': 'Añadir un usuario',
'UPLOAD_FILE': 'Subir archivo',
'PRIVATE': 'privado',
'ENABLE_USER': 'Habilitar usuario',
'DISABLE_USER': 'Deshabilitar usuario',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'lui-même',
'ADD_USER': 'Ajouter un utilisateur',
'UPLOAD_FILE': 'Téléverser un fichier',
'PRIVATE': 'privé',
'ENABLE_USER': 'Activer l\'utilisateur',
'DISABLE_USER': 'Désactiver l\'utilisateur',

View File

@ -184,6 +184,7 @@
'HIMSELF': 'ο ίδιος',
'ADD_USER': 'Πρόσθεσε χρήστη',
'UPLOAD_FILE': 'Ανέβασμα αρχείου',
'PRIVATE': 'ιδιωτικός',
'ENABLE_USER': 'Ενεργοποίηση χρήστη',
'DISABLE_USER': 'Απενεργοποίηση χρήστη',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'स्वयं',
'ADD_USER': 'उपयोगकर्ता जोड़ें',
'UPLOAD_FILE': 'दस्तावेज अपलोड करें',
'PRIVATE': 'निजी',
'ENABLE_USER': 'उपयोगकर्ता सक्षम करें',
'DISABLE_USER': 'उपयोगकर्ता को अक्षम करें',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'lui stesso',
'ADD_USER': 'Aggiungi utente',
'UPLOAD_FILE': 'Caricare un file',
'PRIVATE': 'privato',
'ENABLE_USER': 'Abilita utente',
'DISABLE_USER': 'Disabilita utente',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': '彼自身',
'ADD_USER': 'ユーザーを追加する',
'UPLOAD_FILE': 'ファイルをアップロードする',
'PRIVATE': 'プライベート',
'ENABLE_USER': 'ユーザーを有効にする',
'DISABLE_USER': 'ユーザーを無効にする',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'zichzelf',
'ADD_USER': 'Voeg gebruiker toe',
'UPLOAD_FILE': 'Upload bestand',
'PRIVATE': 'privaat',
'ENABLE_USER': 'Schakel gebruiker in',
'DISABLE_USER': 'Gebruiker uitschakelen',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'ele mesmo',
'ADD_USER': 'Adicionar usuário',
'UPLOAD_FILE': 'Subir arquivo',
'PRIVATE': 'privado',
'ENABLE_USER': 'Ativar usuário',
'DISABLE_USER': 'Desativar usuário',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'сам',
'ADD_USER': 'Добавить пользователя',
'UPLOAD_FILE': 'Загрузить файл',
'PRIVATE': 'частный',
'ENABLE_USER': 'Включить пользователя',
'DISABLE_USER': 'Отключить пользователя',

View File

@ -184,6 +184,7 @@ export default {
'HIMSELF': 'kendisi',
'ADD_USER': 'Kullanıcı Ekle',
'UPLOAD_FILE': 'Dosya yükleme',
'PRIVATE': 'gizli',
'ENABLE_USER': 'Kullanıcıyı Etkinleştir',
'DISABLE_USER': 'Kullanıcıyı Devre Dışı Bırak',

View File

@ -6,6 +6,8 @@ $primary-blue: #414A59;
$secondary-blue: #20B8c5;
$primary-yellow: #E5D151;
$light-yellow: rgba(229,209,81,0.6);
$very-light-yellow: #ffffe0;
$primary-green: #82CA9C;

View File

@ -16,6 +16,7 @@ DataValidator::with('CustomValidations', true);
*
* @apiParam {String} content Content of the comment.
* @apiParam {Number} ticketNumber The number of the ticket to comment.
* @apiParam {Boolean} private Indicates if the comment is not shown to users.
* @apiParam {Number} images The number of images in the content
* @apiParam image_i The image file of index `i` (mutiple params accepted)
* @apiParam file The file you with to upload.
@ -118,7 +119,8 @@ class CommentController extends Controller {
$comment->setProperties(array(
'content' => $this->replaceWithImagePaths($imagePaths, $this->content),
'file' => ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null,
'date' => Date::getCurrentDate()
'date' => Date::getCurrentDate(),
'private' => (Controller::isStaffLogged() && Controller::request('private')) ? 1 : 0
));
if(Controller::isStaffLogged()) {

View File

@ -170,7 +170,8 @@ class Ticket extends DataStore {
'content'=> $ticketEvent->content,
'author' => [],
'date'=> $ticketEvent->date,
'file'=> $ticketEvent->file
'file'=> $ticketEvent->file,
'private'=> $ticketEvent->private,
];
$author = $ticketEvent->getAuthor();
@ -184,6 +185,10 @@ class Ticket extends DataStore {
];
}
if(!Controller::isStaffLogged() && $ticketEvent->private) {
continue;
}
$events[] = $event;
}

View File

@ -13,6 +13,7 @@
* @apiParam {Boolean} author.staff Indicates if the author is a staff.
* @apiParam {String} date The date of the ticket event.
* @apiParam {String} file The file of the ticket event.
* @apiParam {Boolean} private Indicates if this event is not shown to users.
*/
class Ticketevent extends DataStore {
@ -58,7 +59,8 @@ class Ticketevent extends DataStore {
'file',
'authorUser',
'authorStaff',
'date'
'date',
'private'
];
}

View File

@ -10,7 +10,7 @@ describe '/staff/get-new-tickets' do
})
(result['status']).should.equal('success')
(result['data'].size).should.equal(9)
(result['data'].size).should.equal(8)
end
end

View File

@ -142,4 +142,46 @@ describe '/ticket/comment/' do
(result['status']).should.equal('fail')
(result['message']).should.equal('NO_PERMISSION')
end
it 'should keep private on 0 if an user creates a private comment' do
Scripts.login('commenter@os4.com', 'commenter')
result = request('/ticket/comment', {
content: 'this is not a private comment',
ticketNumber: @ticketNumber,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
private: 1
})
(result['status']).should.equal('success')
comment = $database.getRow('ticketevent', 'this is not a private comment', 'content')
(comment['private']).should.equal("0")
request('/user/logout')
end
it 'should change private to 1 if a staff creates a private comment' do
request('/user/logout')
Scripts.login('jorah@opensupports.com', 'testpassword', true)
request('/staff/assign-ticket', {
ticketNumber: @ticketNumber,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
})
result = request('/ticket/comment', {
content: 'this is a private comment',
ticketNumber: @ticketNumber,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
private: 1
})
puts result['message']
(result['status']).should.equal('success')
comment = $database.getRow('ticketevent', 'this is a private comment', 'content')
(comment['private']).should.equal("1")
end
end

View File

@ -18,7 +18,7 @@ describe '/ticket/seen' do
end
end
describe 'when a user is logged' do
describe 'when an user is logged' do
request('/user/logout')
Scripts.login()
@ -36,4 +36,4 @@ describe '/ticket/seen' do
end
end
end
end