Merge branch 'che-multiuser' into spi

6.19.x
Artem Zatsarynnyi 2017-10-05 16:30:22 +03:00
commit 4bc18519db
143 changed files with 4035 additions and 937 deletions

View File

@ -18,6 +18,8 @@ import org.eclipse.che.api.user.server.spi.PreferenceDao;
import org.eclipse.che.api.user.server.spi.UserDao;
import org.eclipse.che.api.workspace.server.hc.ServerCheckerFactoryImpl;
import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.mail.template.ST.STTemplateProcessorImpl;
import org.eclipse.che.mail.template.TemplateProcessor;
import org.eclipse.che.multiuser.api.permission.server.AdminPermissionInitializer;
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
import org.eclipse.che.multiuser.api.permission.server.PermissionCheckerImpl;
@ -41,6 +43,8 @@ public class MultiUserCheWsMasterModule extends AbstractModule {
bind(InstallerConfigProvisioner.class).to(MultiuserInstallerConfigProvisioner.class);
install(new OpenShiftInfraModule());
bind(TemplateProcessor.class).to(STTemplateProcessorImpl.class);
bind(DataSource.class).toProvider(org.eclipse.che.core.db.JndiDataSourceProvider.class);
install(new org.eclipse.che.multiuser.api.permission.server.jpa.SystemPermissionsJpaModule());
install(new org.eclipse.che.multiuser.api.permission.server.PermissionsModule());

View File

@ -74,11 +74,49 @@ che.limits.organization.workspaces.ram=-1
# additional workspaces. This applies to the total number of both running
# and stopped workspaces. Since each workspace is saved as a snapshot, placing a
# cap on this number limits the disk consumption for workspace storage.
che.limits.organization.workspaces.count=-1
# The maximum number of running workspaces that a single organization is allowed.
# If the organization has reached this threshold and they try to start an
# additional workspace, they will be prompted with an error message. The
# organization will need to stop a running workspace to activate another.
che.limits.organization.workspaces.run.count=-1
# Address that will be used as from email for email notifications
che.mail.from_email_address=che@noreply.com
##### ORGANIZATIONS' NOTIFICATIONS SETTINGS #####
che.organization.email.member_added_subject=You've been added to a Che Organization
che.organization.email.member_added_template=st-html-templates/user_added_to_organization
che.organization.email.member_removed_subject=You've been removed from a Che Organization
che.organization.email.member_removed_template=st-html-templates/user_removed_from_organization
che.organization.email.org_removed_subject=Che Organization deleted
che.organization.email.org_removed_template=st-html-templates/organization_deleted
che.organization.email.org_renamed_subject=Che Organization renamed
che.organization.email.org_renamed_template=st-html-templates/organization_renamed
##### KEYCLOACK CONFIGURATION #####
# Url to keycloak identity provider server
che.keycloak.auth_server_url=http://${CHE_HOST}:5050/auth
# Keycloak realm is used to authenticate users
che.keycloak.realm=che
# Keycloak client id in che.keycloak.realm that is used by dashboard, ide and cli to authenticate users
che.keycloak.client_id=che-public
# Redhat che specific configuration
# URL to access OSO oauth tokens
che.keycloak.oso.endpoint=NULL
# URL to access Github oauth tokens
che.keycloak.github.endpoint=NULL
# The number of seconds to tolerate for clock skew when verifying exp or nbf claims.
che.keycloak.allowed_clock_skew_sec=3

View File

@ -55,7 +55,7 @@ public class PagesTest {
}
@Test
public void eagerlyIteratesAllElements() {
public void eagerlyIteratesAllElements() throws Exception {
ArrayList<String> result = Lists.newArrayList(Pages.iterate(testSource::getStrings, 2));
assertEquals(result, testSource.strings);

View File

@ -42,6 +42,10 @@
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId>

View File

@ -21,6 +21,7 @@ import org.eclipse.che.commons.annotation.Nullable;
* @author Alexander Garagatyi
*/
public class EmailBean {
private String from;
private String to;
private String replyTo;
@ -29,6 +30,36 @@ public class EmailBean {
private String subject;
private List<Attachment> attachments;
public EmailBean() {}
public EmailBean(EmailBean email) {
this(
email.getFrom(),
email.getTo(),
email.getReplyTo(),
email.getMimeType(),
email.getBody(),
email.getSubject(),
email.getAttachments());
}
public EmailBean(
String from,
String to,
String replyTo,
String mimeType,
String body,
String subject,
List<Attachment> attachments) {
this.from = from;
this.to = to;
this.replyTo = replyTo;
this.mimeType = mimeType;
this.body = body;
this.subject = subject;
this.attachments = attachments;
}
public String getFrom() {
return from;
}
@ -123,8 +154,12 @@ public class EmailBean {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EmailBean)) return false;
if (this == o) {
return true;
}
if (!(o instanceof EmailBean)) {
return false;
}
EmailBean emailBean = (EmailBean) o;
return Objects.equals(getFrom(), emailBean.getFrom())
&& Objects.equals(getTo(), emailBean.getTo())

View File

@ -21,20 +21,23 @@ import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import org.eclipse.che.inject.ConfigurationProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Provider of {@link Session} */
@Singleton
public class MailSessionProvider implements Provider<Session> {
private static final Logger LOG = LoggerFactory.getLogger(MailSessionProvider.class);
private final Session session;
/**
* Configuration can be injected from container with help of {@lin ConfigurationProperties} class.
* In this case all properties that starts with 'che.mail.' will be used to create {@link
* Configuration can be injected from container with help of {@link ConfigurationProperties}
* class. In this case all properties that starts with 'che.mail.' will be used to create {@link
* Session}. First 4 letters 'che.' from property names will be removed.
*/
@Inject
public MailSessionProvider(ConfigurationProperties configurationProperties) {
this(
configurationProperties
.getProperties("che.mail.*")
@ -70,6 +73,7 @@ public class MailSessionProvider implements Provider<Session> {
this.session = Session.getInstance(props);
}
} else {
LOG.warn("Mail server is not configured. Sending of emails won't work.");
this.session = null;
}
}
@ -77,7 +81,7 @@ public class MailSessionProvider implements Provider<Session> {
@Override
public Session get() {
if (session == null) {
throw new RuntimeException("SMTP is not configured");
throw new RuntimeException("Mail server is not configured");
}
return session;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.mail.template.ST;
import com.google.common.io.CharStreams;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Map;
import javax.inject.Singleton;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.mail.template.Template;
import org.eclipse.che.mail.template.TemplateProcessor;
import org.eclipse.che.mail.template.exception.TemplateException;
import org.eclipse.che.mail.template.exception.TemplateNotFoundException;
import org.stringtemplate.v4.ST;
/**
* {@link TemplateProcessor} implementation based on {@link ST}.
*
* @author Sergii Leshchenko
*/
@Singleton
public class STTemplateProcessorImpl implements TemplateProcessor {
@Override
public String process(String templateName, Map<String, Object> variables)
throws TemplateException {
ST st = new ST(resolve(templateName));
variables.forEach(st::add);
return st.render();
}
@Override
public String process(Template template) throws TemplateException {
return process(template.getName(), template.getAttributes());
}
private String resolve(String template) throws TemplateNotFoundException {
try (Reader reader = new InputStreamReader(IoUtil.getResource(template))) {
return CharStreams.toString(reader);
} catch (IOException e) {
throw new TemplateNotFoundException(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.mail.template;
import java.util.Map;
/**
* Holds information that is required for template processing.
*
* @author Sergii Leshchenko
*/
public class Template {
private final String templateName;
private final Map<String, Object> attributes;
public Template(String templateName, Map<String, Object> attributes) {
this.templateName = templateName;
this.attributes = attributes;
}
/**
* Returns template name.
*
* @see ClassLoader#getResource(String)
*/
public String getName() {
return templateName;
}
/** Returns attributes which will be used while template processing. */
public Map<String, Object> getAttributes() {
return attributes;
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.mail.template;
import java.util.Map;
import org.eclipse.che.mail.template.exception.TemplateException;
import org.eclipse.che.mail.template.exception.TemplateNotFoundException;
/**
* Provides ability to process templates.
*
* <p>Note that variables definition format is implementation specific.
*
* @author Anton Korneta
* @author Sergii Leshchenko
*/
public interface TemplateProcessor {
/**
* Process specified template with given variables.
*
* @param templateName the template name which will be used for processing
* @param variables the variables to used while processing of the given template
* @return processed template as string
* @throws TemplateNotFoundException when given {@code template} not found
* @throws TemplateException when any another problem occurs during the template processing
* @see ClassLoader#getResource(String)
*/
String process(String templateName, Map<String, Object> variables) throws TemplateException;
/**
* Process the specified template.
*
* @param template the template to process
* @return processed template as string
* @throws TemplateNotFoundException when given {@code template} not found
* @throws TemplateException when any another problem occurs during the template processing
*/
String process(Template template) throws TemplateException;
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.mail.template.exception;
/**
* Should be thrown when any exception occurs unable while template processing.
*
* @author Sergii Leshchenko
*/
public class TemplateException extends Exception {
public TemplateException(String message) {
super(message);
}
public TemplateException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.mail.template.exception;
/**
* Should be thrown when unable to resolve template by given path.
*
* @author Anton Korneta
*/
public class TemplateNotFoundException extends TemplateException {
public TemplateNotFoundException(String message) {
super(message);
}
public TemplateNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -28,7 +28,7 @@ export class DashboardConfig {
register.directive('dashboardPanel', DashboardPanel);
// config routes
register.app.config(($routeProvider: ng.route.IRouteProvider) => {
register.app.config(($routeProvider: che.route.IRouteProvider) => {
$routeProvider.accessWhen('/', {
title: 'Dashboard',
templateUrl: 'app/dashboard/dashboard.html',
@ -43,8 +43,6 @@ export class DashboardConfig {
$location.path('/create-workspace');
}
});
return cheService.fetchServices();
}]
}
});

View File

@ -46,7 +46,7 @@ class IdeIFrameSvc {
} else if ("show-navbar" === event.data) {
$rootScope.hideNavbar = false;
$mdSidenav('left').toggle();
$mdSidenav('left').open();
} else if ("hide-navbar" === event.data) {
$rootScope.hideNavbar = true;

View File

@ -110,7 +110,7 @@ promise.then((keycloakSettings: any) => {
}).catch((error: any) => {
console.error('Keycloak initialization failed with error: ', error);
}).then(() => {
angular.bootstrap(document.body, ['userDashboard'], {strictDi: true}); // manually bootstrap Angular
angular.bootstrap(document, ['userDashboard'], {strictDi: true}); // manually bootstrap Angular
});
// add a global resolve flag on all routes (user needs to be resolved first)

View File

@ -11,6 +11,7 @@
'use strict';
import {CheAPI} from '../../components/api/che-api.factory';
import {CheKeycloak} from '../../components/api/che-keycloak.factory';
import {CheService} from '../../components/api/che-service.factory';
export class CheNavBarController {
private menuItemUrl = {
@ -53,6 +54,8 @@ export class CheNavBarController {
private hasPersonalAccount: boolean;
private organizations: Array<che.IOrganization>;
private cheKeycloak: CheKeycloak;
private cheService: CheService;
private isPermissionServiceAvailable: boolean;
/**
* Default constructor
@ -65,7 +68,8 @@ export class CheNavBarController {
cheAPI: CheAPI,
$window: ng.IWindowService,
chePermissions: che.api.IChePermissions,
cheKeycloak: CheKeycloak) {
cheKeycloak: CheKeycloak,
cheService: CheService) {
this.$mdSidenav = $mdSidenav;
this.$scope = $scope;
this.$location = $location;
@ -74,6 +78,7 @@ export class CheNavBarController {
this.$window = $window;
this.chePermissions = chePermissions;
this.cheKeycloak = cheKeycloak;
this.cheService = cheService;
this.profile = cheAPI.getProfile().getProfile();
@ -88,13 +93,31 @@ export class CheNavBarController {
cheAPI.getWorkspace().fetchWorkspaces();
cheAPI.getFactory().fetchFactories();
if (this.chePermissions.getSystemPermissions()) {
this.updateData();
} else {
this.chePermissions.fetchSystemPermissions().finally(() => {
this.updateData();
});
}
this.isPermissionServiceAvailable = false;
this.resolvePermissionServiceAvailability().then((isAvailable: boolean) => {
this.isPermissionServiceAvailable = isAvailable;
if (isAvailable) {
if (this.chePermissions.getSystemPermissions()) {
this.updateData();
} else {
this.chePermissions.fetchSystemPermissions().finally(() => {
this.updateData();
});
}
}
});
}
/**
* Resolves promise with <code>true</code> if Permissions service is available.
*
* @returns {ng.IPromise<boolean>}
*/
resolvePermissionServiceAvailability(): ng.IPromise<boolean> {
return this.cheService.fetchServices().then(() => {
return this.cheService.isServiceAvailable(this.chePermissions.getPermissionsServicePath());
});
}
/**

View File

@ -15,8 +15,14 @@
<md-progress-linear md-mode="indeterminate" ng-hide="navbarController.profile && navbarController.profile.userId"></md-progress-linear>
<div ng-show="navbarController.profile && navbarController.profile.userId"
layout="column" flex>
<section class="navbar-top-logo logo-color-white" layout="column" layout-align="center left" ng-include="branding.logoText">
</section>
<div flex="none"
layout="row" layout-align="start center">
<section class="navbar-top-logo logo-color-white" layout="column" layout-align="center left" ng-include="branding.logoText">
</section>
<div flex></div>
<navbar-notification></navbar-notification>
</div>
<md-divider md-theme="factory-theme"></md-divider>
@ -69,7 +75,7 @@
</div>
</md-button>
</md-list-item>
<md-list-item flex class="navbar-subsection-item" ng-if="!navbarController.userServices.hasInstallationManagerService && !navbarController.hasPersonalAccount">
<md-list-item flex class="navbar-subsection-item" ng-if="navbarController.isPermissionServiceAvailable && !navbarController.userServices.hasInstallationManagerService && !navbarController.hasPersonalAccount">
<md-button nav-bar-selected flex che-reload-href
href="{{navbarController.menuItemUrl.organizations}}" layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
@ -124,7 +130,8 @@
<div class="admin-navbar-menu"
layout="column" layout-align="end stretch" flex>
<section class="left-sidebar-menu navbar-account-section">
<section class="left-sidebar-menu navbar-account-section"
ng-if="navbarController.isPermissionServiceAvailable">
<md-list layout="column" flex>
<md-list-item class="navbar-subsection-item">

View File

@ -297,7 +297,7 @@ export class OrganizationDetailsController {
*/
canChangeResourceLimits(): boolean {
if (this.isRootOrganization()) {
return this.chePermissions.getUserServices().hasAdminUserService;
return this.chePermissions.getUserServices().hasInstallationManagerService;
}
return this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.MANAGE_RESOURCES, this.organization.parent);
}

View File

@ -18,8 +18,8 @@
class="che-list-item-details">
<div flex-gt-xs="50"
class="che-list-item-name">
<span class="che-xs-header noselect" hide-gt-xs>Name</span>
<span class="member-email che-hover ">{{member.fullName}}</span>
<span class="che-xs-header noselect" hide-gt-xs>Username</span>
<span class="member-email che-hover ">{{member.name}}</span>
</div>
<div flex-gt-xs="50"
class="che-list-item-name">

View File

@ -24,7 +24,7 @@
<che-list-header-column flex-gt-xs="50"
che-sort-value='organizationSelectMembersDialogController.memberOrderBy'
che-sort-item='name'
che-column-title='Name'></che-list-header-column>
che-column-title='Username'></che-list-header-column>
<che-list-header-column flex-gt-xs="50"
che-sort-value='organizationSelectMembersDialogController.memberOrderBy'
che-sort-item='email'

View File

@ -7,7 +7,7 @@
<div layout="row"
layout-align="start center"
class="che-checkbox-area" ng-if="memberItemController.editable">
<che-list-item-checked ng-model="memberItemController.callback.cheListHelper.itemsSelectionStatus[memberItemController.member.userId]"
<che-list-item-checked ng-model="memberItemController.callback.cheListHelper.itemsSelectionStatus[memberItemController.member.email]"
ng-click="memberItemController.callback.cheListHelper.updateBulkSelectionStatus()"
ng-show="!memberItemController.isOwner"
che-aria-label-checkbox="member {{memberItemController.member.userId}}"></che-list-item-checked>

View File

@ -10,6 +10,7 @@
*/
'use strict';
import {CheProfile} from '../../../../components/api/che-profile.factory';
import {CheUser} from '../../../../components/api/che-user.factory';
/**
* This class is handling the controller for the add members popup
@ -58,16 +59,25 @@ export class AddMemberController {
private cheTeam: che.api.ICheTeam;
private cheUser: CheUser;
private $log: ng.ILogService;
private cheListHelper: che.widget.ICheListHelper;
/**
* Default constructor.
* @ngInject for Dependency injection
*/
constructor($q: ng.IQService, $mdDialog: angular.material.IDialogService, lodash: any, cheTeam: che.api.ICheTeam,
chePermissions: che.api.IChePermissions, cheProfile: CheProfile) {
chePermissions: che.api.IChePermissions, cheProfile: CheProfile, cheUser: CheUser, $log: ng.ILogService,
$scope: ng.IScope, cheListHelperFactory: che.widget.ICheListHelperFactory) {
this.$q = $q;
this.$mdDialog = $mdDialog;
this.lodash = lodash;
this.cheTeam = cheTeam;
this.cheUser = cheUser;
this.$log = $log;
this.chePermissions = chePermissions;
this.cheProfile = cheProfile;
@ -81,6 +91,12 @@ export class AddMemberController {
if (this.team) {
this.fetchTeamMembers();
}
const helperId = 'add-members';
this.cheListHelper = cheListHelperFactory.getHelper(helperId);
$scope.$on('$destroy', () => {
cheListHelperFactory.removeHelper(helperId);
});
}
fetchTeamMembers(): void {
@ -112,19 +128,21 @@ export class AddMemberController {
continue;
}
let user = this.cheProfile.getProfileById(userId);
if (user) {
this.formUserItem(user, permission);
} else {
let promise = this.cheProfile.fetchProfileById(userId).then(() => {
this.formUserItem(this.cheProfile.getProfileById(userId), permission);
});
promises.push(promise);
if (this.cheUser.getUserFromId(userId)) {
this.formUserItem(this.cheUser.getUserFromId(userId), permission);
continue;
}
const promise = this.cheUser.fetchUserId(userId).then(() => {
this.formUserItem(this.cheUser.getUserFromId(userId), permission);
}, (error: any) => {
this.$log.log(`Failed to fetch user by ID with error ${error}`);
});
promises.push(promise);
}
this.$q.all(promises).finally(() => {
this.cheListHelper.setList(this.members, 'email');
this.isLoading = false;
});
}
@ -138,6 +156,7 @@ export class AddMemberController {
formUserItem(user: any, permissions: any): void {
user.name = this.cheProfile.getFullName(user.attributes);
let userItem = angular.copy(user);
userItem.userId = user.id;
userItem.permissions = permissions;
this.members.push(userItem);
}
@ -156,10 +175,8 @@ export class AddMemberController {
shareWorkspace() {
let checkedUsers = [];
Object.keys(this.membersSelectedStatus).forEach((key: string) => {
if (this.membersSelectedStatus[key] === true) {
checkedUsers.push({userId: key, isTeamAdmin: this.isTeamAdmin(key)});
}
this.cheListHelper.getSelectedItems().forEach((member: any) => {
checkedUsers.push({userId: member.userId, isTeamAdmin: this.isTeamAdmin(member.userId)});
});
let permissionPromises = this.callbackController.shareWorkspace(checkedUsers);
@ -194,74 +211,4 @@ export class AddMemberController {
});
}
/**
* Return <code>true</code> if all members in list are checked.
* @returns {boolean}
*/
isAllMembersSelected(): boolean {
return this.isAllSelected;
}
/**
* Returns <code>true</code> if all members in list are not checked.
* @returns {boolean}
*/
isNoMemberSelected(): boolean {
return this.isNoSelected;
}
/**
* Make all members in list selected.
*/
selectAllMembers(): void {
this.members.forEach((member: any) => {
this.membersSelectedStatus[member.userId] = true;
});
}
/**
* Make all members in list deselected.
*/
deselectAllMembers(): void {
this.members.forEach((member: any) => {
this.membersSelectedStatus[member.userId] = false;
});
}
/**
* Change bulk selection value.
*/
changeBulkSelection(): void {
if (this.isBulkChecked) {
this.deselectAllMembers();
this.isBulkChecked = false;
} else {
this.selectAllMembers();
this.isBulkChecked = true;
}
this.updateSelectedStatus();
}
/**
* Update members selected status.
*/
updateSelectedStatus(): void {
this.isNoSelected = true;
this.isAllSelected = true;
Object.keys(this.membersSelectedStatus).forEach((key: string) => {
if (this.membersSelectedStatus[key]) {
this.isNoSelected = false;
} else {
this.isAllSelected = false;
}
});
if (this.isNoSelected) {
this.isBulkChecked = false;
return;
}
this.isBulkChecked = (this.isAllSelected && Object.keys(this.membersSelectedStatus).length === this.members.length);
}
}

View File

@ -10,10 +10,10 @@
<div layout="column" layout-gt-xs="row" layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
<md-checkbox class="che-list-item-checkbox md-default-theme"
aria-label="Member list"
ng-checked="addMemberController.isBulkChecked"
ng-click="addMemberController.changeBulkSelection()"></md-checkbox>
ng-checked="addMemberController.cheListHelper.areAllItemsSelected"
ng-click="addMemberController.cheListHelper.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
@ -30,13 +30,9 @@
</div>
</div>
</che-list-header>
<che-list ng-show="addMemberController.members.length > 0" class="members-list">
<che-list ng-show="addMemberController.cheListHelper.visibleItemsNumber > 0" class="members-list">
<member-item
ng-repeat="member in addMemberController.members"
ng-model="addMemberController.membersSelectedStatus[member.userId]"
che-selectable="true"
che-display-labels="false"
che-on-checkbox-click="addMemberController.updateSelectedStatus()"
ng-repeat="member in addMemberController.cheListHelper.getVisibleItems()"
callback="addMemberController"
editable="true"
hide-details="true"
@ -53,7 +49,7 @@
<div layout="row" layout-align="end center" class="buttons-panel">
<che-button-primary
ng-disabled="addMemberController.isNoSelected || addMemberController.isLoading"
ng-disabled="addMemberController.cheListHelper.isNoItemSelected || addMemberController.isLoading"
che-button-title="Share" name="shareButton"
ng-click="addMemberController.shareWorkspace()"></che-button-primary>
<che-button-notice che-button-title="Close"

View File

@ -404,7 +404,7 @@ export class ShareWorkspaceController {
users: this.users
},
parent: parentEl,
templateUrl: 'app/workspace/share-workspace/add-members/add-members.html'
templateUrl: 'app/workspaces/share-workspace/add-members/add-members.html'
});
}

View File

@ -13,6 +13,7 @@ import {CheWorkspace, WorkspaceStatus} from '../../../components/api/workspace/c
import {CheNotification} from '../../../components/notification/che-notification.factory';
import {WorkspaceDetailsService} from './workspace-details.service';
import IdeSvc from '../../ide/ide.service';
import {CheService} from '../../../components/api/che-service.factory';
export interface IInitData {
namespaceId: string;
@ -62,7 +63,7 @@ export class WorkspaceDetailsController {
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($location: ng.ILocationService, $log: ng.ILogService, $scope: ng.IScope, cheNotification: CheNotification, cheWorkspace: CheWorkspace, ideSvc: IdeSvc, workspaceDetailsService: WorkspaceDetailsService, initData: IInitData) {
constructor($location: ng.ILocationService, $log: ng.ILogService, $scope: ng.IScope, cheNotification: CheNotification, cheWorkspace: CheWorkspace, ideSvc: IdeSvc, workspaceDetailsService: WorkspaceDetailsService, initData: IInitData, cheService: CheService, chePermissions: che.api.IChePermissions) {
this.$log = $log;
this.$scope = $scope;
this.$location = $location;
@ -102,6 +103,10 @@ export class WorkspaceDetailsController {
this.cheWorkspace.unsubscribeOnWorkspaceChange(this.workspaceId, action);
searchDeRegistrationFn();
});
if (cheService.isServiceAvailable(chePermissions.getPermissionsServicePath())) {
this.workspaceDetailsService.addPage('Share', '<share-workspace></share-workspace>', 'icon-ic_folder_shared_24px');
}
}
/**

View File

@ -129,15 +129,7 @@
</che-label-container>
</md-tab-body>
</md-tab>
<!-- Share Tab -->
<md-tab>
<md-tab-label>
<span class="che-tab-label-title">Share</span>
</md-tab-label>
<md-tab-body>
<share-workspace></share-workspace>
</md-tab-body>
</md-tab>
<!-- SSH Tab -->
<md-tab>
<md-tab-label>

View File

@ -153,9 +153,11 @@ export class WorkspacesConfig {
constructor(register: che.IRegisterService) {
/* tslint:disable */
new StackSelectorScopeFilter(register);
new StackSelectorSearchFilter(register);
new StackSelectorTagsFilter(register);
/* tslint:enable */
register.controller('WorkspaceDetailsSshCtrl', WorkspaceDetailsSshCtrl);
register.directive('workspaceDetailsSsh', WorkspaceDetailsSsh);
@ -297,8 +299,8 @@ export class WorkspacesConfig {
register.controller('ShareWorkspaceController', ShareWorkspaceController);
register.directive('shareWorkspace', ShareWorkspace);
register.controller('addDeveloperController', AddDeveloperController);
register.controller('addMemberController', AddMemberController);
register.controller('AddDeveloperController', AddDeveloperController);
register.controller('AddMemberController', AddMemberController);
register.controller('UserItemController', UserItemController);
register.directive('userItem', UserItem);

View File

@ -201,6 +201,14 @@ export class ChePermissions implements che.api.IChePermissions {
return this.userServices;
}
/**
* Gets the factory service path.
* @returns {string}
*/
getPermissionsServicePath(): string {
return 'permissions';
}
private updateUserServices(systemPermissions: che.api.ISystemPermissions): void {
let isManageUsers: boolean = systemPermissions && systemPermissions.actions.includes('manageUsers');
let isManageSystem: boolean = systemPermissions && systemPermissions.actions.includes('manageSystem');

View File

@ -21,7 +21,7 @@ export class CheService {
*/
private $http: ng.IHttpService;
private servicesPromise: ng.IPromise;
private servicesPromise: ng.IPromise<any>;
/**
* The list of available services.
*/
@ -37,14 +37,16 @@ export class CheService {
*/
constructor ($http: ng.IHttpService) {
this.$http = $http;
this.fetchServices();
}
/**
* Fetches all available services.
*
* @returns {ng.IPromise}
* @returns {ng.IPromise<any>}
*/
fetchServices(): ng.IPromise {
fetchServices(): ng.IPromise<any> {
if (this.servicesPromise) {
return this.servicesPromise;
}
@ -77,9 +79,9 @@ export class CheService {
/**
* Fetches the services info.
*
* @returns {IHttpPromise<T>}
* @returns {IHttpPromise<any>}
*/
fetchServicesInfo(): ng.IPromise {
fetchServicesInfo(): ng.IPromise<any> {
let promise = this.$http({'method': 'OPTIONS', 'url': '/api/'});
let infoPromise = promise.then((response: any) => {
this.servicesInfo = response.data;

View File

@ -28,7 +28,7 @@ export class CheWebsocket {
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor ($websocket, $location, $interval : ng.IIntervalService, proxySettings : string, userDashboardConfig) {
constructor ($websocket, $location, $interval : ng.IIntervalService, proxySettings : string, userDashboardConfig, keycloakAuth: any) {
this.$websocket = $websocket;
this.$interval = $interval;
@ -50,7 +50,8 @@ export class CheWebsocket {
wsUrl = wsProtocol + '://' + $location.host() + ':' + $location.port() + '/api/ws';
}
this.wsBaseUrl = wsUrl;
let keycloakToken = keycloakAuth.isPresent ? '?token=' + keycloakAuth.keycloak.token : '';
this.wsBaseUrl = wsUrl + keycloakToken;
this.bus = null;
this.remoteBus = null;
}

View File

@ -91,6 +91,7 @@ export class CheHttpBackend {
*/
setup(): void {
this.httpBackend.when('OPTIONS', '/api/').respond({});
this.httpBackend.when('GET', '/api/').respond(200, {rootResources: []});
this.httpBackend.when('GET', '/api/keycloak/settings').respond(404);

View File

@ -78,6 +78,7 @@ declare namespace che {
fetchSystemPermissions(): ng.IPromise<any>;
getSystemPermissions(): ISystemPermissions;
getUserServices(): IUserServices;
getPermissionsServicePath(): string;
}
export interface ICheTeam {

View File

@ -0,0 +1 @@
tests

View File

@ -533,11 +533,12 @@ CHE_SINGLE_PORT=false
#CHE_SYSTEM_ADMIN__NAME=admin
#
CHE_KEYCLOAK_OSO_ENDPOINT=NULL
CHE_KEYCLOAK_GITHUB_ENDPOINT=NULL
CHE_KEYCLOAK_AUTH__SERVER__URL=http://172.17.0.1:5050/auth
CHE_KEYCLOAK_REALM=che
CHE_KEYCLOAK_CLIENT__ID=che-public
#CHE_KEYCLOAK_OSO_ENDPOINT=NULL
#CHE_KEYCLOAK_GITHUB_ENDPOINT=NULL
#CHE_KEYCLOAK_AUTH__SERVER__URL=http://172.17.0.1:5050/auth
#CHE_KEYCLOAK_REALM=che
#CHE_KEYCLOAK_CLIENT__ID=che-public
#CHE_KEYCLOAK_ALLOWED__CLOCK__SKEW__SEC=3
########################################################################################

View File

@ -21,6 +21,23 @@
set -e
# ----------------
# helper functions
# ----------------
# append_after_match allows to append content after matching line
# this is needed to append content of yaml files
# first arg is mathing string, second string to insert after match
append_after_match() {
while IFS= read -r line
do
printf '%s\n' "$line"
if [[ "$line" == *"$1"* ]];then
printf '%s\n' "$2"
fi
done < /dev/stdin
}
# --------------
# Print Che logo
# --------------
@ -95,6 +112,10 @@ DEFAULT_CHE_IMAGE_TAG="spi"
CHE_IMAGE_TAG=${CHE_IMAGE_TAG:-${DEFAULT_CHE_IMAGE_TAG}}
DEFAULT_CHE_LOG_LEVEL="INFO"
CHE_LOG_LEVEL=${CHE_LOG_LEVEL:-${DEFAULT_CHE_LOG_LEVEL}}
DEFAULT_ENABLE_SSL="true"
ENABLE_SSL=${ENABLE_SSL:-${DEFAULT_ENABLE_SSL}}
DEFAULT_K8S_VERSION_PRIOR_TO_1_6="true"
K8S_VERSION_PRIOR_TO_1_6=${K8S_VERSION_PRIOR_TO_1_6:-${DEFAULT_K8S_VERSION_PRIOR_TO_1_6}}
# Keycloak production endpoints are used by default
DEFAULT_KEYCLOAK_OSO_ENDPOINT="https://sso.openshift.io/auth/realms/fabric8/broker/openshift-v3/token"
@ -110,16 +131,20 @@ OPENSHIFT_FLAVOR=${OPENSHIFT_FLAVOR:-${DEFAULT_OPENSHIFT_FLAVOR}}
# TODO move this env variable as a config map in the deployment config
# as soon as the 'che-multiuser' branch is merged to master
CHE_WORKSPACE_LOGS="/data/logs/machine/logs" \
CHE_HOST="${OPENSHIFT_NAMESPACE_URL}"
if [ "${OPENSHIFT_FLAVOR}" == "minishift" ]; then
# ---------------------------
# Set minishift configuration
# ---------------------------
echo -n "[CHE] Checking if minishift is running..."
minishift status | grep -q "Running" ||(echo "Minishift is not running. Aborting"; exit 1)
echo "done!"
if [ -z "${MINISHIFT_IP}" ]; then
# ---------------------------
# Set minishift configuration
# ---------------------------
echo -n "[CHE] Checking if minishift is running..."
minishift status | grep -q "Running" ||(echo "Minishift is not running. Aborting"; exit 1)
echo "done!"
MINISHIFT_IP="$(minishift ip)"
fi
DEFAULT_OPENSHIFT_ENDPOINT="https://$(minishift ip):8443/"
DEFAULT_OPENSHIFT_ENDPOINT="https://${MINISHIFT_IP}:8443/"
OPENSHIFT_ENDPOINT=${OPENSHIFT_ENDPOINT:-${DEFAULT_OPENSHIFT_ENDPOINT}}
DEFAULT_OPENSHIFT_USERNAME="developer"
OPENSHIFT_USERNAME=${OPENSHIFT_USERNAME:-${DEFAULT_OPENSHIFT_USERNAME}}
@ -127,13 +152,17 @@ if [ "${OPENSHIFT_FLAVOR}" == "minishift" ]; then
OPENSHIFT_PASSWORD=${OPENSHIFT_PASSWORD:-${DEFAULT_OPENSHIFT_PASSWORD}}
DEFAULT_CHE_OPENSHIFT_PROJECT="eclipse-che"
CHE_OPENSHIFT_PROJECT=${CHE_OPENSHIFT_PROJECT:-${DEFAULT_CHE_OPENSHIFT_PROJECT}}
DEFAULT_OPENSHIFT_NAMESPACE_URL="${CHE_OPENSHIFT_PROJECT}.$(minishift ip).nip.io"
DEFAULT_OPENSHIFT_NAMESPACE_URL="${CHE_OPENSHIFT_PROJECT}.${MINISHIFT_IP}.nip.io"
OPENSHIFT_NAMESPACE_URL=${OPENSHIFT_NAMESPACE_URL:-${DEFAULT_OPENSHIFT_NAMESPACE_URL}}
CHE_KEYCLOAK_DISABLED=${CHE_KEYCLOAK_DISABLED:-${DEFAULT_CHE_KEYCLOAK_DISABLED}}
DEFAULT_CHE_DEBUGGING_ENABLED="true"
CHE_DEBUGGING_ENABLED=${CHE_DEBUGGING_ENABLED:-${DEFAULT_CHE_DEBUGGING_ENABLED}}
DEFAULT_OC_SKIP_TLS="true"
OC_SKIP_TLS=${OC_SKIP_TLS:-${DEFAULT_OC_SKIP_TLS}}
DEFAULT_CHE_APPLY_RESOURCE_QUOTAS="false"
CHE_APPLY_RESOURCE_QUOTAS=${CHE_APPLY_RESOURCE_QUOTAS:-${DEFAULT_CHE_APPLY_RESOURCE_QUOTAS}}
DEFAULT_IMAGE_PULL_POLICY="IfNotPresent"
IMAGE_PULL_POLICY=${IMAGE_PULL_POLICY:-${DEFAULT_IMAGE_PULL_POLICY}}
elif [ "${OPENSHIFT_FLAVOR}" == "osio" ]; then
# ----------------------
@ -151,6 +180,8 @@ elif [ "${OPENSHIFT_FLAVOR}" == "osio" ]; then
CHE_KEYCLOAK_DISABLED=${CHE_KEYCLOAK_DISABLED:-${DEFAULT_CHE_KEYCLOAK_DISABLED}}
DEFAULT_CHE_DEBUGGING_ENABLED="false"
CHE_DEBUGGING_ENABLED=${CHE_DEBUGGING_ENABLED:-${DEFAULT_CHE_DEBUGGING_ENABLED}}
DEFAULT_OC_SKIP_TLS="false"
OC_SKIP_TLS=${OC_SKIP_TLS:-${DEFAULT_OC_SKIP_TLS}}
elif [ "${OPENSHIFT_FLAVOR}" == "ocp" ]; then
# ----------------------
@ -161,6 +192,8 @@ elif [ "${OPENSHIFT_FLAVOR}" == "ocp" ]; then
CHE_KEYCLOAK_DISABLED=${CHE_KEYCLOAK_DISABLED:-${DEFAULT_CHE_KEYCLOAK_DISABLED}}
DEFAULT_CHE_DEBUGGING_ENABLED="false"
CHE_DEBUGGING_ENABLED=${CHE_DEBUGGING_ENABLED:-${DEFAULT_CHE_DEBUGGING_ENABLED}}
DEFAULT_OC_SKIP_TLS="false"
OC_SKIP_TLS=${OC_SKIP_TLS:-${DEFAULT_OC_SKIP_TLS}}
fi
@ -180,10 +213,10 @@ if [ -z "${OPENSHIFT_NAMESPACE_URL+x}" ]; then echo "[CHE] **ERROR**Env var OPEN
# -----------------------------------
echo -n "[CHE] Logging on using OpenShift endpoint \"${OPENSHIFT_ENDPOINT}\"..."
if [ -z "${OPENSHIFT_TOKEN+x}" ]; then
oc login "${OPENSHIFT_ENDPOINT}" --insecure-skip-tls-verify=false -u "${OPENSHIFT_USERNAME}" -p "${OPENSHIFT_PASSWORD}" > /dev/null
oc login "${OPENSHIFT_ENDPOINT}" --insecure-skip-tls-verify="${OC_SKIP_TLS}" -u "${OPENSHIFT_USERNAME}" -p "${OPENSHIFT_PASSWORD}" > /dev/null
OPENSHIFT_TOKEN=$(oc whoami -t)
else
oc login "${OPENSHIFT_ENDPOINT}" --insecure-skip-tls-verify=false --token="${OPENSHIFT_TOKEN}" > /dev/null
oc login "${OPENSHIFT_ENDPOINT}" --insecure-skip-tls-verify="${OC_SKIP_TLS}" --token="${OPENSHIFT_TOKEN}" > /dev/null
fi
echo "done!"
@ -325,15 +358,16 @@ CHE_IMAGE="${CHE_IMAGE_REPO}:${CHE_IMAGE_TAG}"
# e.g. docker.io/rhchestage => docker.io\/rhchestage
CHE_IMAGE_SANITIZED=$(echo "${CHE_IMAGE}" | sed 's/\//\\\//g')
MULTI_USER_REPLACEMENT_STRING="s+- env:+- env:\\n\
- name: \"CHE_WORKSPACE_LOGS\"\\n\
value: \"${CHE_WORKSPACE_LOGS}\"\\n\
- name: \"CHE_KEYCLOAK_AUTH__SERVER__URL\"\\n\
value: \"${CHE_KEYCLOAK_AUTH__SERVER__URL}\"\\n\
- name: \"CHE_KEYCLOAK_REALM\"\\n\
value: \"${CHE_KEYCLOAK_REALM}\"\\n\
- name: \"CHE_KEYCLOAK_CLIENT__ID\"\\n\
value: \"${CHE_KEYCLOAK_CLIENT__ID}\"+"
MULTI_USER_REPLACEMENT_STRING=" - name: \"CHE_WORKSPACE_LOGS\"
value: \"${CHE_WORKSPACE_LOGS}\"
- name: \"CHE_KEYCLOAK_AUTH__SERVER__URL\"
value: \"${CHE_KEYCLOAK_AUTH__SERVER__URL}\"
- name: \"CHE_KEYCLOAK_REALM\"
value: \"${CHE_KEYCLOAK_REALM}\"
- name: \"CHE_KEYCLOAK_CLIENT__ID\"
value: \"${CHE_KEYCLOAK_CLIENT__ID}\"
- name: \"CHE_HOST\"
value: \"${CHE_HOST}\""
# TODO When merging the multi-user work to master, this replacement string should
# be replaced by the corresponding change in the fabric8 deployment descriptor
@ -348,6 +382,7 @@ if [ "${OPENSHIFT_FLAVOR}" == "minishift" ]; then
cat "${CHE_DEPLOYMENT_FILE_PATH}" | \
if [ ! -z "${OPENSHIFT_NAMESPACE_URL+x}" ]; then sed "s/ hostname-http:.*/ hostname-http: ${OPENSHIFT_NAMESPACE_URL}/" ; else cat -; fi | \
sed "s/ image:.*/ image: \"${CHE_IMAGE_SANITIZED}\"/" | \
sed "s/ imagePullPolicy:.*/ imagePullPolicy: \"${IMAGE_PULL_POLICY}\"/" | \
sed "s/ workspaces-memory-limit: 2300Mi/ workspaces-memory-limit: 1300Mi/" | \
sed "s/ workspaces-memory-request: 1500Mi/ workspaces-memory-request: 500Mi/" | \
sed "s/ che-openshift-secure-routes: \"true\"/ che-openshift-secure-routes: \"false\"/" | \
@ -355,6 +390,7 @@ cat "${CHE_DEPLOYMENT_FILE_PATH}" | \
sed "s/ che.docker.server_evaluation_strategy.custom.external.protocol: https/ che.docker.server_evaluation_strategy.custom.external.protocol: http/" | \
sed "s/ che-openshift-precreate-subpaths: \"false\"/ che-openshift-precreate-subpaths: \"true\"/" | \
sed "s/ che.predefined.stacks.reload_on_start: \"true\"/ che.predefined.stacks.reload_on_start: \"false\"/" | \
sed "s/ remote-debugging-enabled: \"false\"/ remote-debugging-enabled: \"${CHE_DEBUGGING_ENABLED}\"/" | \
sed "s| keycloak-oso-endpoint:.*| keycloak-oso-endpoint: ${KEYCLOAK_OSO_ENDPOINT}|" | \
sed "s| keycloak-github-endpoint:.*| keycloak-github-endpoint: ${KEYCLOAK_GITHUB_ENDPOINT}|" | \
sed "s/ CHE_INFRA_OPENSHIFT_TLS__ENABLED: \"true\"/ CHE_INFRA_OPENSHIFT_TLS__ENABLED: \"false\"/" | \
@ -368,6 +404,7 @@ cat "${CHE_DEPLOYMENT_FILE_PATH}" | \
if [ "${CHE_DEBUGGING_ENABLED}" == "true" ]; then sed "s/ remote-debugging-enabled: \"false\"/ remote-debugging-enabled: \"true\"/"; else cat -; fi | \
sed "$MULTI_USER_REPLACEMENT_STRING" | \
sed "$MULTI_USER_HEALTH_CHECK_REPLACEMENT_STRING" | \
append_after_match "env:" "${MULTI_USER_REPLACEMENT_STRING}" | \
oc apply --force=true -f -
elif [ "${OPENSHIFT_FLAVOR}" == "osio" ]; then
echo "[CHE] Deploying Che on OSIO (image ${CHE_IMAGE})"
@ -384,23 +421,32 @@ cat "${CHE_DEPLOYMENT_FILE_PATH}" | \
sed "s| CHE_HOST: \${DEFAULT_OPENSHIFT_NAMESPACE_URL}| CHE_HOST: che-${DEFAULT_OPENSHIFT_NAMESPACE_URL}|" | \
sed "s| CHE_API: http://\${DEFAULT_OPENSHIFT_NAMESPACE_URL}/wsmaster/api| CHE_API: https://che-${DEFAULT_OPENSHIFT_NAMESPACE_URL}/wsmaster/api|" | \
sed "s/ image:.*/ image: \"${CHE_IMAGE_SANITIZED}\"/" | \
sed "s/ imagePullPolicy:.*/ imagePullPolicy: \"${IMAGE_PULL_POLICY}\"/" | \
if [ "${CHE_KEYCLOAK_DISABLED}" == "true" ]; then sed "s/ keycloak-disabled: \"false\"/ keycloak-disabled: \"true\"/" ; else cat -; fi | \
if [ "${CHE_DEBUGGING_ENABLED}" == "true" ]; then sed "s/ remote-debugging-enabled: \"false\"/ remote-debugging-enabled: \"true\"/"; else cat -; fi | \
sed "$MULTI_USER_REPLACEMENT_STRING" | \
sed "$MULTI_USER_HEALTH_CHECK_REPLACEMENT_STRING" | \
append_after_match "env:" "${MULTI_USER_REPLACEMENT_STRING}" | \
oc apply --force=true -f -
else
echo "[CHE] Deploying Che on OpenShift Container Platform (image ${CHE_IMAGE})"
curl -sSL http://central.maven.org/maven2/io/fabric8/tenant/apps/che/"${OSIO_VERSION}"/che-"${OSIO_VERSION}"-openshift.yml | \
if [ ! -z "${OPENSHIFT_NAMESPACE_URL+x}" ]; then sed "s/ hostname-http:.*/ hostname-http: ${OPENSHIFT_NAMESPACE_URL}/" ; else cat -; fi | \
sed "s/ image:.*/ image: \"${CHE_IMAGE_SANITIZED}\"/" | \
sed "s/ imagePullPolicy:.*/ imagePullPolicy: \"${IMAGE_PULL_POLICY}\"/" | \
sed "s| keycloak-oso-endpoint:.*| keycloak-oso-endpoint: ${KEYCLOAK_OSO_ENDPOINT}|" | \
sed "s| keycloak-github-endpoint:.*| keycloak-github-endpoint: ${KEYCLOAK_GITHUB_ENDPOINT}|" | \
sed "s/ keycloak-disabled:.*/ keycloak-disabled: \"${CHE_KEYCLOAK_DISABLED}\"/" | \
if [ "${CHE_LOG_LEVEL}" == "DEBUG" ]; then sed "s/ log-level: \"INFO\"/ log-level: \"DEBUG\"/" ; else cat -; fi | \
if [ "${CHE_DEBUGGING_ENABLED}" == "true" ]; then sed "s/ remote-debugging-enabled: \"false\"/ remote-debugging-enabled: \"true\"/"; else cat -; fi | \
if [ "${ENABLE_SSL}" == "false" ]; then sed "s/ che-openshift-secure-routes: \"true\"/ che-openshift-secure-routes: \"false\"/" ; else cat -; fi | \
if [ "${ENABLE_SSL}" == "false" ]; then sed "s/ che-secure-external-urls: \"true\"/ che-secure-external-urls: \"false\"/" ; else cat -; fi | \
if [ "${ENABLE_SSL}" == "false" ]; then grep -v -e "tls:" -e "insecureEdgeTerminationPolicy: Redirect" -e "termination: edge" ; else cat -; fi | \
if [ "${ENABLE_SSL}" == "false" ]; then sed "s/ che.docker.server_evaluation_strategy.custom.external.protocol: https/ che.docker.server_evaluation_strategy.custom.external.protocol: http/" ; else cat -; fi | \
if [ "${K8S_VERSION_PRIOR_TO_1_6}" == "true" ]; then sed "s/ che-openshift-precreate-subpaths: \"false\"/ che-openshift-precreate-subpaths: \"true\"/" ; else cat -; fi | \
sed "$MULTI_USER_REPLACEMENT_STRING" | \
sed "$MULTI_USER_HEALTH_CHECK_REPLACEMENT_STRING" | \
append_after_match "env:" "${MULTI_USER_REPLACEMENT_STRING}" | \
oc apply --force=true -f -
fi
echo
@ -423,7 +469,7 @@ if [ "${CHE_DEBUGGING_ENABLED}" == "true" ]; then
echo -n "[CHE] Creating an OS route to debug Che wsmaster..."
oc expose dc che --name=che-debug --target-port=http-debug --port=8000 --type=NodePort
NodePort=$(oc get service che-debug -o jsonpath='{.spec.ports[0].nodePort}')
echo "[CHE] Remote wsmaster debugging URL: $(minishift ip):${NodePort}"
echo "[CHE] Remote wsmaster debugging URL: ${MINISHIFT_IP}:${NodePort}"
fi
che_route=$(oc get route che -o jsonpath='{.spec.host}')

View File

@ -6,7 +6,9 @@
# http://www.eclipse.org/legal/epl-v10.html
#
COMMAND_DIR=$(dirname "$0")
set -e
COMMAND_DIR=$(dirname "$0")
if [ "${CHE_SERVER_URL}" == "" ]; then
CHE_SERVER_ROUTE_HOST=$(oc get route che -o jsonpath='{.spec.host}' || echo "")

View File

@ -6,7 +6,9 @@
# http://www.eclipse.org/legal/epl-v10.html
#
COMMAND_DIR=$(dirname "$0")
set -e
COMMAND_DIR=$(dirname "$0")
"$COMMAND_DIR"/deploy_postgres_only.sh
"$COMMAND_DIR"/wait_until_postgres_is_available.sh
@ -29,5 +31,5 @@ spec:
name: latest
importPolicy:
scheduled: true
EOF

View File

@ -6,7 +6,9 @@
# http://www.eclipse.org/legal/epl-v10.html
#
COMMAND_DIR=$(dirname "$0")
set -e
COMMAND_DIR=$(dirname "$0")
oc create -f "$COMMAND_DIR"/che-init-image-stream.yaml
@ -61,7 +63,7 @@ spec:
name: latest
importPolicy:
scheduled: true
EOF
oc start-build che-init-image-stream-build

View File

@ -6,6 +6,8 @@
# http://www.eclipse.org/legal/epl-v10.html
#
set -e
echo "[CHE] This script is going to wait until Postgres is deployed and available"
command -v oc >/dev/null 2>&1 || { echo >&2 "[CHE] [ERROR] Command line tool oc (https://docs.openshift.org/latest/cli_reference/get_started_cli.html) is required but it's not installed. Aborting."; exit 1; }

View File

@ -6,6 +6,8 @@
# http://www.eclipse.org/legal/epl-v10.html
#
set -e
echo "[CHE] This script is going to replace Che stacks for current Che instance"
command -v oc >/dev/null 2>&1 || { echo >&2 "[CHE] [ERROR] Command line tool oc (https://docs.openshift.org/latest/cli_reference/get_started_cli.html) is required but it's not installed. Aborting."; exit 1; }

View File

@ -6,6 +6,8 @@
# http://www.eclipse.org/legal/epl-v10.html
#
set -e
echo "[CHE] This script is going to wait until Che is deployed and available"
command -v oc >/dev/null 2>&1 || { echo >&2 "[CHE] [ERROR] Command line tool oc (https://docs.openshift.org/latest/cli_reference/get_started_cli.html) is required but it's not installed. Aborting."; exit 1; }

View File

@ -16,7 +16,7 @@ import org.eclipse.che.ide.api.constraints.Constraints;
import org.eclipse.che.ide.api.editor.EditorPartPresenter;
/**
* Multi Part Stack is layout element, containing {@code EditorPartStack}s and provides methods to
* Multi Part Stack is layout element, containing {@link EditorPartStack}s and provides methods to
* control them.
*
* @author Roman Nikitenko
@ -41,6 +41,16 @@ public interface EditorMultiPartStack extends PartStack {
@Nullable
EditorPartStack getPartStackByPart(PartPresenter part);
/**
* Get {@link EditorPartStack} by given {@code tabId}
*
* @param tabId ID of editor tab to find part stack which contains corresponding editor part
* @return editor part stack which contains part with given {@code tabId} or null if this one is
* not found in any {@link EditorPartStack}
*/
@Nullable
EditorPartStack getPartStackByTabId(@NotNull String tabId);
/**
* Get editor part which associated with given {@code tabId}
*
@ -85,6 +95,14 @@ public interface EditorMultiPartStack extends PartStack {
*/
EditorPartStack createRootPartStack();
/**
* Remove given part stack. Note: All opened parts will be closed, use {@link
* EditorPartStack#getParts()} to avoid removing not empty part stack
*
* @param partStackToRemove part stack to remove
*/
void removePartStack(EditorPartStack partStackToRemove);
/**
* Split part stack
*

View File

@ -8,7 +8,7 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.project;
package org.eclipse.che.ide.api.project;
/** @author Artem Zatsarynnyi */
public class QueryExpression {

View File

@ -12,11 +12,11 @@ package org.eclipse.che.ide.api.resources;
import com.google.common.annotations.Beta;
import com.google.common.base.Optional;
import java.util.List;
import org.eclipse.che.api.core.model.project.type.ProjectType;
import org.eclipse.che.api.project.shared.dto.SourceEstimation;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.api.resources.Project.ProjectRequest;
import org.eclipse.che.ide.resource.Path;
import org.eclipse.che.ide.util.NameUtils;
@ -449,7 +449,33 @@ public interface Container extends Resource {
* @return the {@link Promise} with array of found results
* @since 4.4.0
*/
Promise<List<SearchResult>> search(String fileMask, String contentMask);
Promise<SearchResult> search(String fileMask, String contentMask);
/**
* Searches the all possible files which configured into {@link QueryExpression}.
*
* <p>Method doesn't guarantees the sorted order of the returned resources.
*
* @param queryExpression the search query expression includes search parameters
* @return the {@link Promise} with array of found results
*/
Promise<SearchResult> search(QueryExpression queryExpression);
/**
* Creates the search expression which matches given file or content mask.
*
* <p>Supplied file mask may supports wildcard:
*
* <ul>
* <li>{@code *} - which matches any character sequence (including the empty one)
* <li>{@code ?} - which matches any single character
* </ul>
*
* @param fileMask the file name mask
* @param query the content entity mask
* @return the instance of {@link QueryExpression}
*/
QueryExpression createSearchQueryExpression(String fileMask, String query);
/**
* Returns the plain list of file tree with given {@code depth}.

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.api.resources;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.che.api.project.shared.Constants;
import org.eclipse.che.api.project.shared.SearchOccurrence;
import org.eclipse.che.api.project.shared.dto.SearchOccurrenceDto;
import org.eclipse.che.api.project.shared.dto.SearchResultDto;
/** @author Vitalii Parfonov */
public class SearchItemReference {
private String name;
private String path;
private String project;
private String contentUrl;
private List<SearchOccurrence> occurrences;
public SearchItemReference(SearchResultDto searchResultDto) {
name = searchResultDto.getItemReference().getName();
path = searchResultDto.getItemReference().getPath();
project = searchResultDto.getItemReference().getProject();
if (searchResultDto.getItemReference().getLink(Constants.LINK_REL_GET_CONTENT) != null) {
contentUrl =
searchResultDto.getItemReference().getLink(Constants.LINK_REL_GET_CONTENT).getHref();
}
final List<SearchOccurrenceDto> dtos = searchResultDto.getSearchOccurrences();
occurrences = new ArrayList<>(dtos.size());
for (SearchOccurrence dto : dtos) {
occurrences.add(new SearchOccurrenceImpl(dto));
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getProject() {
return project;
}
public void setProject(String project) {
this.project = project;
}
public List<SearchOccurrence> getOccurrences() {
return occurrences;
}
public void setOccurrences(List<SearchOccurrence> occurrences) {
this.occurrences = occurrences;
}
public String getContentUrl() {
return contentUrl;
}
public void setContentUrl(String contentUrl) {
this.contentUrl = contentUrl;
}
}

View File

@ -10,74 +10,25 @@
*/
package org.eclipse.che.ide.api.resources;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.che.api.project.shared.Constants;
import org.eclipse.che.api.project.shared.SearchOccurrence;
import org.eclipse.che.api.project.shared.dto.SearchOccurrenceDto;
import org.eclipse.che.api.project.shared.dto.SearchResultDto;
/** @author Vitalii Parfonov */
/** Class contains an information about result of the text search operation. */
public class SearchResult {
private List<SearchItemReference> itemReferences;
private int totalHits;
private String name;
private String path;
private String project;
private String contentUrl;
private List<SearchOccurrence> occurrences;
public SearchResult(SearchResultDto searchResultDto) {
name = searchResultDto.getItemReference().getName();
path = searchResultDto.getItemReference().getPath();
project = searchResultDto.getItemReference().getProject();
if (searchResultDto.getItemReference().getLink(Constants.LINK_REL_GET_CONTENT) != null) {
contentUrl =
searchResultDto.getItemReference().getLink(Constants.LINK_REL_GET_CONTENT).getHref();
}
final List<SearchOccurrenceDto> dtos = searchResultDto.getSearchOccurrences();
occurrences = new ArrayList<>(dtos.size());
for (SearchOccurrence dto : dtos) {
occurrences.add(new SearchOccurrenceImpl(dto));
}
public SearchResult(List<SearchItemReference> itemReferences, int totalHits) {
this.itemReferences = itemReferences;
this.totalHits = totalHits;
}
public String getName() {
return name;
/** returns list of found items {@link SearchItemReference} */
public List<SearchItemReference> getItemReferences() {
return itemReferences;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getProject() {
return project;
}
public void setProject(String project) {
this.project = project;
}
public List<SearchOccurrence> getOccurrences() {
return occurrences;
}
public void setOccurrences(List<SearchOccurrence> occurrences) {
this.occurrences = occurrences;
}
public String getContentUrl() {
return contentUrl;
}
public void setContentUrl(String contentUrl) {
this.contentUrl = contentUrl;
/** returns total file count where requested text was found */
public int getTotalHits() {
return totalHits;
}
}

View File

@ -463,6 +463,9 @@ public interface CoreLocalizationConstant extends Messages {
@Key("messages.unableOpenResource")
String unableOpenResource(String path);
@Key("messages.canNotOpenFileInSplitMode")
String canNotOpenFileInSplitMode(String path);
@Key("messages.canNotOpenFileWithoutParams")
String canNotOpenFileWithoutParams();

View File

@ -17,7 +17,7 @@ import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
@ -266,41 +266,47 @@ public class FindActionViewImpl extends PopupPanel implements FindActionView {
event.stopPropagation();
event.preventDefault();
list.getSelectionModel().selectPrevious();
return;
break;
case KeyCodes.KEY_DOWN:
event.stopPropagation();
event.preventDefault();
list.getSelectionModel().selectNext();
return;
break;
case KeyCodes.KEY_PAGEUP:
event.stopPropagation();
event.preventDefault();
list.getSelectionModel().selectPreviousPage();
return;
break;
case KeyCodes.KEY_PAGEDOWN:
event.stopPropagation();
event.preventDefault();
list.getSelectionModel().selectNextPage();
return;
break;
case KeyCodes.KEY_ENTER:
event.stopPropagation();
event.preventDefault();
delegate.onActionSelected(list.getSelectionModel().getSelectedItem());
return;
break;
case KeyCodes.KEY_ESCAPE:
event.stopPropagation();
event.preventDefault();
hide();
return;
break;
default:
//here we need some delay to be sure that input box initiated with given value
//in manually testing hard to reproduce this problem but it reproduced with selenium tests
new Timer() {
@Override
public void run() {
delegate.nameChanged(nameField.getText(), includeNonMenu.getValue());
}
}.schedule(300);
break;
}
Scheduler.get()
.scheduleDeferred(
(Command) () -> delegate.nameChanged(nameField.getText(), includeNonMenu.getValue()));
}
}

View File

@ -10,6 +10,8 @@
*/
package org.eclipse.che.ide.command.editor.page.goal;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.inject.Inject;
import java.util.Optional;
@ -60,7 +62,8 @@ public class GoalPage extends AbstractCommandEditorPage implements GoalPageView.
@Override
protected void initialize() {
initialGoal = editedCommand.getGoal();
String goal = editedCommand.getGoal();
initialGoal = isNullOrEmpty(goal) ? goalRegistry.getDefaultGoal().getId() : goal;
view.setAvailableGoals(goalRegistry.getAllGoals());
view.setGoal(initialGoal);

View File

@ -13,6 +13,8 @@ package org.eclipse.che.ide.editor;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
import static java.lang.Boolean.parseBoolean;
import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.EMERGE_MODE;
import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
import static org.eclipse.che.ide.api.parts.PartStackType.EDITING;
import com.google.gwt.user.client.rpc.AsyncCallback;
@ -25,8 +27,10 @@ import elemental.json.JsonObject;
import elemental.util.ArrayOf;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
@ -54,6 +58,7 @@ import org.eclipse.che.ide.api.editor.texteditor.HasReadOnlyProperty;
import org.eclipse.che.ide.api.editor.texteditor.TextEditor;
import org.eclipse.che.ide.api.filetypes.FileType;
import org.eclipse.che.ide.api.filetypes.FileTypeRegistry;
import org.eclipse.che.ide.api.notification.NotificationManager;
import org.eclipse.che.ide.api.parts.ActivePartChangedEvent;
import org.eclipse.che.ide.api.parts.ActivePartChangedHandler;
import org.eclipse.che.ide.api.parts.EditorMultiPartStack;
@ -76,6 +81,7 @@ import org.eclipse.che.ide.part.explorer.project.ProjectExplorerPresenter;
import org.eclipse.che.ide.resource.Path;
import org.eclipse.che.ide.resources.reveal.RevealResourceEvent;
import org.eclipse.che.ide.ui.smartTree.data.HasDataObject;
import org.eclipse.che.ide.util.loging.Log;
/**
* Default implementation of {@link EditorAgent}.
@ -101,10 +107,12 @@ public class EditorAgentImpl
private final EditorMultiPartStack editorMultiPartStack;
private final List<EditorPartPresenter> openedEditors;
private final Map<EditorPartStack, Set<Path>> openingEditorsPathsToStacks;
private final Map<EditorPartPresenter, String> openedEditorsToProviders;
private final EditorContentSynchronizer editorContentSynchronizer;
private final PromiseProvider promiseProvider;
private final ResourceProvider resourceProvider;
private final NotificationManager notificationManager;
private List<EditorPartPresenter> dirtyEditors;
private EditorPartPresenter activeEditor;
private PartPresenter activePart;
@ -120,7 +128,8 @@ public class EditorAgentImpl
EditorMultiPartStackPresenter editorMultiPartStack,
EditorContentSynchronizer editorContentSynchronizer,
PromiseProvider promiseProvider,
ResourceProvider resourceProvider) {
ResourceProvider resourceProvider,
NotificationManager notificationManager) {
this.eventBus = eventBus;
this.fileTypeRegistry = fileTypeRegistry;
this.preferencesManager = preferencesManager;
@ -131,7 +140,9 @@ public class EditorAgentImpl
this.editorContentSynchronizer = editorContentSynchronizer;
this.promiseProvider = promiseProvider;
this.resourceProvider = resourceProvider;
this.notificationManager = notificationManager;
this.openedEditors = newArrayList();
this.openingEditorsPathsToStacks = new HashMap<>();
this.openedEditorsToProviders = new HashMap<>();
eventBus.addHandler(ActivePartChangedEvent.TYPE, this);
@ -190,12 +201,37 @@ public class EditorAgentImpl
@Override
public void openEditor(@NotNull final VirtualFile file) {
doOpen(file, new OpenEditorCallbackImpl(), null);
openEditor(file, new OpenEditorCallbackImpl());
}
@Override
public void openEditor(@NotNull VirtualFile file, Constraints constraints) {
doOpen(file, new OpenEditorCallbackImpl(), constraints);
if (constraints == null) {
openEditor(file);
return;
}
EditorPartStack relativeEditorPartStack =
editorMultiPartStack.getPartStackByTabId(constraints.relativeId);
if (relativeEditorPartStack == null) {
String errorMessage =
coreLocalizationConstant.canNotOpenFileInSplitMode(file.getLocation().toString());
notificationManager.notify(errorMessage, FAIL, EMERGE_MODE);
Log.error(getClass(), errorMessage + ": relative part stack is not found");
return;
}
EditorPartStack editorPartStackConsumer =
editorMultiPartStack.split(relativeEditorPartStack, constraints, -1);
if (editorPartStackConsumer == null) {
String errorMessage =
coreLocalizationConstant.canNotOpenFileInSplitMode(file.getLocation().toString());
notificationManager.notify(errorMessage, FAIL, EMERGE_MODE);
Log.error(getClass(), errorMessage + ": split part stack is not found");
return;
}
doOpen(file, editorPartStackConsumer, new OpenEditorCallbackImpl());
}
@Override
@ -248,20 +284,30 @@ public class EditorAgentImpl
@Override
public void openEditor(@NotNull VirtualFile file, @NotNull OpenEditorCallback callback) {
doOpen(file, callback, null);
Path path = file.getLocation();
EditorPartStack activeEditorPartStack = editorMultiPartStack.getActivePartStack();
if (activeEditorPartStack != null) {
PartPresenter openedPart = activeEditorPartStack.getPartByPath(path);
if (openedPart != null) {
editorMultiPartStack.setActivePart(openedPart);
callback.onEditorActivated((EditorPartPresenter) openedPart);
return;
}
if (isFileOpening(path, activeEditorPartStack)) {
return;
}
} else {
activeEditorPartStack = editorMultiPartStack.createRootPartStack();
}
doOpen(file, activeEditorPartStack, callback);
}
private void doOpen(
final VirtualFile file, final OpenEditorCallback callback, final Constraints constraints) {
EditorPartStack activePartStack = editorMultiPartStack.getActivePartStack();
if (constraints == null && activePartStack != null) {
PartPresenter partPresenter = activePartStack.getPartByPath(file.getLocation());
if (partPresenter != null) {
workspaceAgent.setActivePart(partPresenter, EDITING);
callback.onEditorActivated((EditorPartPresenter) partPresenter);
return;
}
}
VirtualFile file, EditorPartStack editorPartStackConsumer, OpenEditorCallback callback) {
addToOpeningFilesList(file.getLocation(), editorPartStackConsumer);
final FileType fileType = fileTypeRegistry.getFileTypeByFile(file);
final EditorProvider editorProvider = editorRegistry.getEditor(fileType);
@ -270,33 +316,33 @@ public class EditorAgentImpl
Promise<EditorPartPresenter> promise = provider.createEditor(file);
if (promise != null) {
promise.then(
new Operation<EditorPartPresenter>() {
@Override
public void apply(EditorPartPresenter arg) throws OperationException {
initEditor(file, callback, fileType, arg, constraints, editorProvider);
}
editor -> {
initEditor(file, callback, fileType, editor, editorPartStackConsumer, editorProvider);
});
return;
}
}
final EditorPartPresenter editor = editorProvider.getEditor();
initEditor(file, callback, fileType, editor, constraints, editorProvider);
initEditor(file, callback, fileType, editor, editorPartStackConsumer, editorProvider);
}
private void initEditor(
final VirtualFile file,
final OpenEditorCallback openEditorCallback,
VirtualFile file,
OpenEditorCallback openEditorCallback,
FileType fileType,
final EditorPartPresenter editor,
final Constraints constraints,
EditorPartPresenter editor,
EditorPartStack editorPartStack,
EditorProvider editorProvider) {
OpenEditorCallback initializeCallback =
new OpenEditorCallbackImpl() {
@Override
public void onEditorOpened(EditorPartPresenter editor) {
workspaceAgent.openPart(editor, EDITING, constraints);
workspaceAgent.setActivePart(editor);
editorPartStack.addPart(editor);
editorMultiPartStack.setActivePart(editor);
openedEditors.add(editor);
removeFromOpeningFilesList(file.getLocation(), editorPartStack);
openEditorCallback.onEditorOpened(editor);
}
@ -304,6 +350,13 @@ public class EditorAgentImpl
@Override
public void onInitializationFailed() {
openEditorCallback.onInitializationFailed();
removeFromOpeningFilesList(file.getLocation(), editorPartStack);
if (!openingEditorsPathsToStacks.containsKey(editorPartStack)
&& editorPartStack.getParts().isEmpty()) {
editorMultiPartStack.removePartStack(editorPartStack);
}
}
};
@ -313,7 +366,6 @@ public class EditorAgentImpl
private void finalizeInit(
VirtualFile file, EditorPartPresenter editor, EditorProvider editorProvider) {
openedEditors.add(editor);
openedEditorsToProviders.put(editor, editorProvider.getId());
editor.addCloseHandler(this);
@ -334,6 +386,30 @@ public class EditorAgentImpl
});
}
private boolean isFileOpening(Path path, EditorPartStack editorPartStack) {
Set<Path> openingFiles = openingEditorsPathsToStacks.get(editorPartStack);
return openingFiles != null && openingFiles.contains(path);
}
private void addToOpeningFilesList(Path path, EditorPartStack editorPartStack) {
if (editorPartStack == null) {
return;
}
Set<Path> openingFiles =
openingEditorsPathsToStacks.computeIfAbsent(editorPartStack, k -> new HashSet<>());
openingFiles.add(path);
}
private void removeFromOpeningFilesList(Path path, EditorPartStack editorPartStack) {
if (editorPartStack == null || !openingEditorsPathsToStacks.containsKey(editorPartStack)) {
return;
}
Set<Path> openingFiles = openingEditorsPathsToStacks.get(editorPartStack);
openingFiles.remove(path);
}
@Override
public void activateEditor(@NotNull EditorPartPresenter editor) {
workspaceAgent.setActivePart(editor);
@ -480,9 +556,13 @@ public class EditorAgentImpl
public Promise<Void> loadState(@NotNull final JsonObject state) {
if (state.hasKey("FILES")) {
JsonObject files = state.getObject("FILES");
EditorPartStack partStack = editorMultiPartStack.createRootPartStack();
EditorPartStack editorPartStackConsumer = editorMultiPartStack.getActivePartStack();
if (editorPartStackConsumer == null) {
editorPartStackConsumer = editorMultiPartStack.createRootPartStack();
}
final Map<EditorPartPresenter, EditorPartStack> activeEditors = new HashMap<>();
List<Promise<Void>> restore = restore(files, partStack, activeEditors);
List<Promise<Void>> restore = restore(files, editorPartStackConsumer, activeEditors);
Promise<ArrayOf<?>> promise =
promiseProvider.all2(restore.toArray(new Promise[restore.size()]));
promise.then(
@ -558,9 +638,16 @@ public class EditorAgentImpl
new AsyncPromiseHelper.RequestCall<Void>() {
@Override
public void makeCall(final AsyncCallback<Void> callback) {
String path = file.getString("PATH");
String location = file.getString("PATH");
Path path = Path.valueOf(location);
if (isFileOpening(path, editorPartStack)) {
callback.onSuccess(null);
return;
}
addToOpeningFilesList(path, editorPartStack);
resourceProvider
.getResource(path)
.getResource(location)
.then(
new Operation<java.util.Optional<VirtualFile>>() {
@Override
@ -650,6 +737,9 @@ public class EditorAgentImpl
public void onEditorOpened(EditorPartPresenter editor) {
editorPartStack.addPart(editor);
openedEditors.add(editor);
removeFromOpeningFilesList(file.getLocation(), editorPartStack);
promiseCallback.onSuccess(null);
openEditorCallback.onEditorOpened(editor);
}
@ -659,6 +749,12 @@ public class EditorAgentImpl
promiseCallback.onFailure(
new Exception("Can not initialize editor for " + file.getLocation()));
openEditorCallback.onInitializationFailed();
removeFromOpeningFilesList(file.getLocation(), editorPartStack);
if (!openingEditorsPathsToStacks.containsKey(editorPartStack)
&& editorPartStack.getParts().isEmpty()) {
editorMultiPartStack.removePartStack(editorPartStack);
}
}
};

View File

@ -188,11 +188,14 @@ public class EditorMultiPartStackPresenter
}
}
private void removePartStack(EditorPartStack editorPartStack) {
@Override
public void removePartStack(EditorPartStack editorPartStack) {
if (activeEditorPartStack == editorPartStack) {
activeEditorPartStack = null;
}
editorPartStack.getParts().forEach(editorPartStack::removePart);
view.removePartStack(editorPartStack);
partStackPresenters.remove(editorPartStack);
@ -241,7 +244,7 @@ public class EditorMultiPartStackPresenter
}
@Nullable
private EditorPartStack getPartStackByTabId(@NotNull String tabId) {
public EditorPartStack getPartStackByTabId(@NotNull String tabId) {
for (EditorPartStack editorPartStack : partStackPresenters) {
PartPresenter editorPart = editorPartStack.getPartByTabId(tabId);
if (editorPart != null) {

View File

@ -28,6 +28,7 @@ import org.eclipse.che.api.project.shared.dto.CopyOptions;
import org.eclipse.che.api.project.shared.dto.ItemReference;
import org.eclipse.che.api.project.shared.dto.MoveOptions;
import org.eclipse.che.api.project.shared.dto.NewProjectConfigDto;
import org.eclipse.che.api.project.shared.dto.ProjectSearchResponseDto;
import org.eclipse.che.api.project.shared.dto.SearchResultDto;
import org.eclipse.che.api.project.shared.dto.SourceEstimation;
import org.eclipse.che.api.project.shared.dto.TreeElement;
@ -37,6 +38,8 @@ import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto;
import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto;
import org.eclipse.che.ide.MimeType;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.api.resources.SearchItemReference;
import org.eclipse.che.ide.api.resources.SearchResult;
import org.eclipse.che.ide.dto.DtoFactory;
import org.eclipse.che.ide.resource.Path;
@ -169,7 +172,7 @@ public class ProjectServiceClient {
* @see ItemReference
* @since 4.4.0
*/
public Promise<List<SearchResult>> search(QueryExpression expression) {
public Promise<SearchResult> search(QueryExpression expression) {
Path prjPath = isNullOrEmpty(expression.getPath()) ? Path.ROOT : new Path(expression.getPath());
final String url = getBaseUrl() + SEARCH + encodePath(prjPath.addLeadingSeparator());
@ -193,17 +196,21 @@ public class ProjectServiceClient {
.createGetRequest(url + queryParameters.toString().replaceFirst("&", "?"))
.header(ACCEPT, APPLICATION_JSON)
.loader(loaderFactory.newLoader("Searching..."))
.send(unmarshaller.newListUnmarshaller(SearchResultDto.class))
.send(unmarshaller.newUnmarshaller(ProjectSearchResponseDto.class))
.then(
(Function<List<SearchResultDto>, List<SearchResult>>)
searchResultDtos -> {
if (searchResultDtos.isEmpty()) {
return Collections.emptyList();
(Function<ProjectSearchResponseDto, SearchResult>)
searchResultDto -> {
List<SearchResultDto> itemReferences = searchResultDto.getItemReferences();
if (itemReferences == null || itemReferences.isEmpty()) {
return new SearchResult(
Collections.emptyList(), searchResultDto.getTotalHits());
}
return searchResultDtos
.stream()
.map(SearchResult::new)
.collect(Collectors.toList());
return new SearchResult(
itemReferences
.stream()
.map(SearchItemReference::new)
.collect(Collectors.toList()),
searchResultDto.getTotalHits());
});
}

View File

@ -32,7 +32,6 @@ public class ZipImporterPagePresenter extends AbstractWizardPage<MutableProjectC
private static final RegExp URL_REGEX =
RegExp.compile("(https?|ftp)://(-\\.)?([^\\s/?\\.#-]+\\.?)+(/[^\\s]*)?");
private static final RegExp WHITESPACE = RegExp.compile("^\\s");
private static final RegExp END_URL = RegExp.compile(".zip$");
private CoreLocalizationConstant locale;
private ZipImporterPageView view;
@ -159,11 +158,6 @@ public class ZipImporterPagePresenter extends AbstractWizardPage<MutableProjectC
return false;
}
if (!END_URL.test(url)) {
view.showUrlError(locale.importProjectMessageUrlInvalid());
return false;
}
if (WHITESPACE.test(url)) {
view.showUrlError(locale.importProjectMessageStartWithWhiteSpace());
return false;

View File

@ -12,16 +12,17 @@ package org.eclipse.che.ide.resources.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.common.annotations.Beta;
import com.google.common.base.Optional;
import java.util.List;
import org.eclipse.che.api.core.model.workspace.config.ProjectConfig;
import org.eclipse.che.api.project.shared.dto.SourceEstimation;
import org.eclipse.che.api.promises.client.Function;
import org.eclipse.che.api.promises.client.FunctionException;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseProvider;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.api.resources.Container;
import org.eclipse.che.ide.api.resources.File;
import org.eclipse.che.ide.api.resources.Folder;
@ -182,10 +183,15 @@ abstract class ContainerImpl extends ResourceImpl implements Container {
/** {@inheritDoc} */
@Override
public Promise<List<SearchResult>> search(String fileMask, String contentMask) {
public Promise<SearchResult> search(String fileMask, String contentMask) {
return resourceManager.search(this, fileMask, contentMask);
}
@Override
public Promise<SearchResult> search(QueryExpression queryExpression) {
return resourceManager.search(queryExpression);
}
/** {@inheritDoc} */
@Override
public Promise<Resource[]> getTree(int depth) {
@ -196,4 +202,20 @@ abstract class ContainerImpl extends ResourceImpl implements Container {
public Promise<SourceEstimation> estimate(String projectType) {
return resourceManager.estimate(this, projectType);
}
@Override
public QueryExpression createSearchQueryExpression(String fileMask, String contentMask) {
QueryExpression queryExpression = new QueryExpression();
if (!isNullOrEmpty(contentMask)) {
queryExpression.setText(contentMask);
}
if (!isNullOrEmpty(fileMask)) {
queryExpression.setName(fileMask);
}
if (!getLocation().isRoot()) {
queryExpression.setPath(getLocation().toString());
}
return queryExpression;
}
}

View File

@ -66,6 +66,7 @@ import org.eclipse.che.ide.api.editor.EditorAgent;
import org.eclipse.che.ide.api.editor.EditorPartPresenter;
import org.eclipse.che.ide.api.filewatcher.ClientServerEventService;
import org.eclipse.che.ide.api.project.MutableProjectConfig;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.api.project.type.ProjectTypeRegistry;
import org.eclipse.che.ide.api.resources.Container;
import org.eclipse.che.ide.api.resources.File;
@ -83,7 +84,6 @@ import org.eclipse.che.ide.api.vcs.VcsStatus;
import org.eclipse.che.ide.context.AppContextImpl;
import org.eclipse.che.ide.dto.DtoFactory;
import org.eclipse.che.ide.project.ProjectServiceClient;
import org.eclipse.che.ide.project.QueryExpression;
import org.eclipse.che.ide.resource.Path;
import org.eclipse.che.ide.util.Arrays;
@ -1280,7 +1280,7 @@ public final class ResourceManager {
return promises.resolve(null);
}
protected Promise<List<SearchResult>> search(
protected Promise<SearchResult> search(
final Container container, String fileMask, String contentMask) {
QueryExpression queryExpression = new QueryExpression();
if (!isNullOrEmpty(contentMask)) {
@ -1296,6 +1296,10 @@ public final class ResourceManager {
return ps.search(queryExpression);
}
protected Promise<SearchResult> search(QueryExpression queryExpression) {
return ps.search(queryExpression);
}
Promise<SourceEstimation> estimate(Container container, String projectType) {
checkArgument(projectType != null, "Null project type");
checkArgument(!projectType.isEmpty(), "Empty project type");

View File

@ -12,15 +12,11 @@ package org.eclipse.che.ide.search;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.common.base.Optional;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.List;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.api.resources.Container;
import org.eclipse.che.ide.api.resources.SearchResult;
import org.eclipse.che.ide.resource.Path;
import org.eclipse.che.ide.search.presentation.FindResultPresenter;
@ -33,6 +29,8 @@ import org.eclipse.che.ide.search.presentation.FindResultPresenter;
*/
@Singleton
public class FullTextSearchPresenter implements FullTextSearchView.ActionDelegate {
public static final int SEARCH_RESULT_ITEMS = 30;
private static final String URL_ENCODED_BACKSLASH = "%5C";
private static final String AND_OPERATOR = "AND";
@ -71,26 +69,23 @@ public class FullTextSearchPresenter implements FullTextSearchView.ActionDelegat
.getWorkspaceRoot()
.getContainer(startPoint)
.then(
new Operation<Optional<Container>>() {
@Override
public void apply(Optional<Container> optionalContainer) throws OperationException {
if (!optionalContainer.isPresent()) {
view.showErrorMessage("Path '" + startPoint + "' doesn't exists");
return;
}
final Container container = optionalContainer.get();
container
.search(view.getFileMask(), prepareQuery(text))
.then(
new Operation<List<SearchResult>>() {
@Override
public void apply(List<SearchResult> result) throws OperationException {
view.close();
findResultPresenter.handleResponse(result, text);
}
});
optionalContainer -> {
if (!optionalContainer.isPresent()) {
view.showErrorMessage("Path '" + startPoint + "' doesn't exists");
return;
}
final Container container = optionalContainer.get();
QueryExpression queryExpression =
container.createSearchQueryExpression(view.getFileMask(), prepareQuery(text));
queryExpression.setMaxItems(SEARCH_RESULT_ITEMS);
container
.search(queryExpression)
.then(
result -> {
view.close();
findResultPresenter.handleResponse(result, queryExpression, text);
});
});
}

View File

@ -10,10 +10,8 @@
*/
package org.eclipse.che.ide.search.factory;
import java.util.List;
import org.eclipse.che.api.project.shared.SearchOccurrence;
import org.eclipse.che.ide.api.resources.SearchResult;
import org.eclipse.che.ide.search.presentation.FindResultGroupNode;
import org.eclipse.che.ide.api.resources.SearchItemReference;
import org.eclipse.che.ide.search.presentation.FoundItemNode;
import org.eclipse.che.ide.search.presentation.FoundOccurrenceNode;
@ -24,15 +22,20 @@ import org.eclipse.che.ide.search.presentation.FoundOccurrenceNode;
*/
public interface FindResultNodeFactory {
/**
* Create new instance of {@link FindResultGroupNode}.
* Create new instance of {@link FoundItemNode}.
*
* @param result list of files with occurrences
* @param searchItemReference the result of the search operation
* @param request requested text to search
* @return new instance of {@link FindResultGroupNode}
* @return new instance of {@link FoundItemNode}
*/
FindResultGroupNode newResultNode(List<SearchResult> result, String request);
FoundItemNode newFoundItemNode(SearchResult searchResult, String request);
FoundItemNode newFoundItemNode(SearchItemReference searchItemReference, String request);
/**
* Create new instance of {@link FoundOccurrenceNode}.
*
* @param searchOccurrence linforamtion about occurrence
* @param itemPath path to the file resource
* @return new instance of {@link FoundOccurrenceNode}
*/
FoundOccurrenceNode newFoundOccurrenceNode(SearchOccurrence searchOccurrence, String itemPath);
}

View File

@ -1,116 +0,0 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.search.presentation;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseProvider;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.api.resources.SearchResult;
import org.eclipse.che.ide.search.factory.FindResultNodeFactory;
import org.eclipse.che.ide.ui.smartTree.compare.NameComparator;
import org.eclipse.che.ide.ui.smartTree.data.AbstractTreeNode;
import org.eclipse.che.ide.ui.smartTree.data.Node;
import org.eclipse.che.ide.ui.smartTree.presentation.HasPresentation;
import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation;
/**
* Tree node represent search result.
*
* @author Valeriy Svydenko
* @author Vlad Zhukovskyi
*/
public class FindResultGroupNode extends AbstractTreeNode implements HasPresentation {
private final CoreLocalizationConstant locale;
private NodePresentation nodePresentation;
private FindResultNodeFactory nodeFactory;
private PromiseProvider promiseProvider;
private List<SearchResult> findResults;
private String request;
@Inject
public FindResultGroupNode(
CoreLocalizationConstant locale,
FindResultNodeFactory nodeFactory,
PromiseProvider promiseProvider,
@Assisted List<SearchResult> findResult,
@Assisted String request) {
this.locale = locale;
this.nodeFactory = nodeFactory;
this.promiseProvider = promiseProvider;
this.findResults = findResult;
this.request = request;
}
/** {@inheritDoc} */
@Override
protected Promise<List<Node>> getChildrenImpl() {
List<Node> fileNodes = new ArrayList<>();
for (SearchResult searchResult : findResults) {
FoundItemNode foundItemNode = nodeFactory.newFoundItemNode(searchResult, request);
fileNodes.add(foundItemNode);
}
//sort nodes by file name
Collections.sort(fileNodes, new NameComparator());
return promiseProvider.resolve(fileNodes);
}
/** {@inheritDoc} */
@Override
public NodePresentation getPresentation(boolean update) {
if (nodePresentation == null) {
nodePresentation = new NodePresentation();
updatePresentation(nodePresentation);
}
if (update) {
updatePresentation(nodePresentation);
}
return nodePresentation;
}
/** {@inheritDoc} */
@Override
public String getName() {
return locale.actionFullTextSearch();
}
/** {@inheritDoc} */
@Override
public boolean isLeaf() {
return false;
}
/** {@inheritDoc} */
@Override
public void updatePresentation(@NotNull NodePresentation presentation) {
int total = 0;
for (SearchResult searchResult : findResults) {
total += searchResult.getOccurrences().size();
}
StringBuilder resultTitle =
new StringBuilder("Found occurrences of '" + request + "\' (" + total + " occurrence");
if (total > 1) {
resultTitle.append("s)");
} else {
resultTitle.append(")");
}
presentation.setPresentableText(resultTitle.toString());
}
}

View File

@ -11,6 +11,7 @@
package org.eclipse.che.ide.search.presentation;
import static org.eclipse.che.ide.api.resources.ResourceDelta.REMOVED;
import static org.eclipse.che.ide.search.FullTextSearchPresenter.SEARCH_RESULT_ITEMS;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.IsWidget;
@ -23,11 +24,14 @@ import org.eclipse.che.ide.Resources;
import org.eclipse.che.ide.api.parts.PartStackType;
import org.eclipse.che.ide.api.parts.WorkspaceAgent;
import org.eclipse.che.ide.api.parts.base.BasePresenter;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.api.resources.ResourceChangedEvent;
import org.eclipse.che.ide.api.resources.ResourceChangedEvent.ResourceChangedHandler;
import org.eclipse.che.ide.api.resources.ResourceDelta;
import org.eclipse.che.ide.api.resources.SearchItemReference;
import org.eclipse.che.ide.api.resources.SearchResult;
import org.eclipse.che.ide.api.selection.Selection;
import org.eclipse.che.ide.project.ProjectServiceClient;
import org.eclipse.che.ide.resources.tree.ResourceNode;
import org.eclipse.che.ide.ui.smartTree.Tree;
import org.eclipse.che.ide.ui.smartTree.data.Node;
@ -43,18 +47,25 @@ import org.vectomatic.dom.svg.ui.SVGResource;
public class FindResultPresenter extends BasePresenter
implements FindResultView.ActionDelegate, ResourceChangedHandler {
private final WorkspaceAgent workspaceAgent;
private ProjectServiceClient projectServiceClient;
private final CoreLocalizationConstant localizationConstant;
private final Resources resources;
private final FindResultView view;
private int skipCount = 0;
private QueryExpression queryExpression;
private String requestedString;
@Inject
public FindResultPresenter(
WorkspaceAgent workspaceAgent,
ProjectServiceClient projectServiceClient,
CoreLocalizationConstant localizationConstant,
Resources resources,
FindResultView view,
EventBus eventBus) {
this.workspaceAgent = workspaceAgent;
this.projectServiceClient = projectServiceClient;
this.localizationConstant = localizationConstant;
this.resources = resources;
this.view = view;
@ -92,13 +103,18 @@ public class FindResultPresenter extends BasePresenter
/**
* Activate Find results part and showing all occurrences.
*
* @param resources list of files which contains requested text
* @param result search result of requested text
* @param request requested text
*/
public void handleResponse(List<SearchResult> resources, String request) {
public void handleResponse(SearchResult result, QueryExpression queryExpression, String request) {
this.queryExpression = queryExpression;
this.requestedString = request;
workspaceAgent.openPart(this, PartStackType.INFORMATION);
workspaceAgent.setActivePart(this);
view.showResults(resources, request);
view.setPreviousBtnActive(false);
view.setNextBtnActive(result.getItemReferences().size() == SEARCH_RESULT_ITEMS);
view.showResults(result, request);
}
@Override
@ -106,6 +122,47 @@ public class FindResultPresenter extends BasePresenter
setSelection(new Selection<>(selection));
}
@Override
public void onNextButtonClicked() {
queryExpression.setSkipCount(skipCount + SEARCH_RESULT_ITEMS);
projectServiceClient
.search(queryExpression)
.then(
result -> {
List<SearchItemReference> itemReferences = result.getItemReferences();
skipCount += itemReferences.size();
view.setPreviousBtnActive(true);
if (itemReferences.isEmpty()) {
view.setNextBtnActive(false);
return;
}
if (itemReferences.size() % SEARCH_RESULT_ITEMS == 0) {
view.setNextBtnActive(true);
} else {
skipCount += SEARCH_RESULT_ITEMS;
view.setNextBtnActive(false);
}
view.showResults(result, requestedString);
});
}
@Override
public void onPreviousButtonClicked() {
skipCount -= skipCount % SEARCH_RESULT_ITEMS + SEARCH_RESULT_ITEMS;
queryExpression.setSkipCount(skipCount);
projectServiceClient
.search(queryExpression)
.then(
result -> {
List<SearchItemReference> itemReferences = result.getItemReferences();
view.setNextBtnActive(true);
boolean hasPreviousResults =
itemReferences.size() % SEARCH_RESULT_ITEMS == 0 && skipCount != 0;
view.setPreviousBtnActive(hasPreviousResults);
view.showResults(result, requestedString);
});
}
@Override
@SuppressWarnings("unchecked")
public void onResourceChanged(ResourceChangedEvent event) {

View File

@ -32,17 +32,35 @@ public interface FindResultView extends View<FindResultView.ActionDelegate> {
*/
void setVisible(boolean visible);
/**
* Sets whether next result button is enable.
*
* @param enable visible - true to enable the button, false to disable it
*/
void setNextBtnActive(boolean enable);
/**
* Sets whether previous result button is enable.
*
* @param enable visible - true to enable the button, false to disable it
*/
void setPreviousBtnActive(boolean enable);
/**
* Activate Find results part and showing all occurrences.
*
* @param nodes list of files which contains requested text
* @param result search result of requested text
* @param request requested text
*/
void showResults(List<SearchResult> resources, String request);
void showResults(SearchResult result, String request);
Tree getTree();
interface ActionDelegate extends BaseActionDelegate {
void onSelectionChanged(List<Node> selection);
void onNextButtonClicked();
void onPreviousButtonClicked();
}
}

View File

@ -10,19 +10,29 @@
*/
package org.eclipse.che.ide.search.presentation;
import static java.util.Collections.emptySet;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Collections;
import java.util.List;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.api.parts.base.BaseView;
import org.eclipse.che.ide.api.resources.SearchItemReference;
import org.eclipse.che.ide.api.resources.SearchResult;
import org.eclipse.che.ide.search.factory.FindResultNodeFactory;
import org.eclipse.che.ide.ui.smartTree.NodeLoader;
import org.eclipse.che.ide.ui.smartTree.NodeStorage;
import org.eclipse.che.ide.ui.smartTree.Tree;
import org.eclipse.che.ide.ui.smartTree.data.NodeInterceptor;
import org.eclipse.che.ide.ui.smartTree.event.SelectionChangedEvent;
import org.eclipse.che.ide.ui.smartTree.data.Node;
/**
* Implementation for FindResult view. Uses tree for presenting search results.
@ -31,33 +41,42 @@ import org.eclipse.che.ide.ui.smartTree.event.SelectionChangedEvent;
*/
@Singleton
class FindResultViewImpl extends BaseView<FindResultView.ActionDelegate> implements FindResultView {
private final Tree tree;
private final FindResultNodeFactory findResultNodeFactory;
@UiField FlowPanel paginationPanel;
@UiField Button nextBtn;
@UiField Button previousBtn;
@UiField Label resultLabel;
@UiField Label requestedLabel;
@Inject
public FindResultViewImpl(
FindResultNodeFactory findResultNodeFactory, CoreLocalizationConstant localizationConstant) {
FindResultViewImplUiBinder uiBinder,
FindResultNodeFactory findResultNodeFactory,
CoreLocalizationConstant localizationConstant) {
NodeStorage nodeStorage = new NodeStorage();
NodeLoader loader = new NodeLoader(emptySet());
tree = new Tree(nodeStorage, loader);
Widget contentWidget = uiBinder.createAndBindUi(this);
setContentWidget(contentWidget);
setTitle(localizationConstant.actionFullTextSearch());
this.findResultNodeFactory = findResultNodeFactory;
NodeStorage nodeStorage = new NodeStorage();
NodeLoader loader = new NodeLoader(Collections.<NodeInterceptor>emptySet());
tree = new Tree(nodeStorage, loader);
nextBtn.setHTML("<i class=\"fa fa-angle-right\" aria-hidden=\"true\"></i>");
previousBtn.setHTML("<i class=\"fa fa-angle-left\" aria-hidden=\"true\"></i>");
//do not remove debug id; it's needed for selenium tests
// do not remove debug id; it's needed for selenium tests
tree.ensureDebugId("result-search-tree");
ensureDebugId("find-info-panel");
tree.getSelectionModel()
.addSelectionChangedHandler(
new SelectionChangedEvent.SelectionChangedHandler() {
@Override
public void onSelectionChanged(SelectionChangedEvent event) {
delegate.onSelectionChanged(event.getSelection());
}
});
DockLayoutPanel dockLayoutPanel = (DockLayoutPanel) contentWidget;
dockLayoutPanel.add(tree);
setContentWidget(tree);
tree.getSelectionModel()
.addSelectionChangedHandler(event -> delegate.onSelectionChanged(event.getSelection()));
tree.setAutoSelect(true);
}
@ -68,13 +87,57 @@ class FindResultViewImpl extends BaseView<FindResultView.ActionDelegate> impleme
tree.setFocus(true);
}
@Override
public void setPreviousBtnActive(boolean enable) {
previousBtn.setEnabled(enable);
}
@Override
public void setNextBtnActive(boolean enable) {
nextBtn.setEnabled(enable);
}
/** {@inheritDoc} */
@Override
public void showResults(List<SearchResult> resources, String request) {
public void showResults(SearchResult result, String request) {
StringBuilder resultTitle = new StringBuilder();
List<SearchItemReference> resources = result.getItemReferences();
if (resources.isEmpty()) {
resultTitle.append("No results found for ");
resultLabel.setText(resultTitle.toString());
requestedLabel.setText("\'" + request + "\'");
tree.getNodeStorage().clear();
return;
}
requestedLabel.setText("");
int total = 0;
for (SearchItemReference searchItemReference : resources) {
total += searchItemReference.getOccurrences().size();
}
resultTitle.append(total).append(" occurrence");
if (total > 1) {
resultTitle.append('s');
}
resultTitle.append(" found in ").append(resources.size()).append(" file");
if (resources.size() > 1) {
resultTitle.append('s');
}
resultTitle.append(" (per page results) for '");
resultTitle.append(request);
resultTitle.append("'. Total file count - ");
resultTitle.append(result.getTotalHits());
resultLabel.setText(resultTitle.toString());
tree.getNodeStorage().clear();
tree.getNodeStorage().add(findResultNodeFactory.newResultNode(resources, request));
tree.expandAll();
tree.getSelectionModel().select(tree.getRootNodes().get(0), false);
for (SearchItemReference item : resources) {
tree.getNodeStorage().add(findResultNodeFactory.newFoundItemNode(item, request));
}
Node rootNode = tree.getRootNodes().get(0);
tree.getSelectionModel().select(rootNode, false);
focusView();
}
@ -82,4 +145,18 @@ class FindResultViewImpl extends BaseView<FindResultView.ActionDelegate> impleme
public Tree getTree() {
return tree;
}
@SuppressWarnings("unused")
@UiHandler("nextBtn")
public void nextBtnClick(ClickEvent event) {
delegate.onNextButtonClicked();
}
@SuppressWarnings("unused")
@UiHandler("previousBtn")
public void previousBtnClick(ClickEvent event) {
delegate.onPreviousButtonClicked();
}
interface FindResultViewImplUiBinder extends UiBinder<Widget, FindResultViewImpl> {}
}

View File

@ -0,0 +1,65 @@
<!--
Copyright (c) 2012-2017 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
.resultContainer {
margin-top: 2px;
position: absolute;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
padding-top: 5px;
bottom: 0;
}
.paginationPanel {
float: left;
margin-left: 15px;
}
.button {
min-width: 2px;
width: 25px;
height: 20px;
margin-left: 10px;
padding: 0 0;
font-family: 'Console', Times, serif;
font-size: 13pt;
font-weight: bold;
}
.textLabel {
margin-left: 5px;
float: left;
}
</ui:style>
<g:DockLayoutPanel unit="PX" width="100%" height="100%">
<g:north size="35">
<g:FlowPanel addStyleNames="{style.resultContainer}"
debugId="search-result-container" width="100%">
<g:Label ui:field="resultLabel" debugId="search-result-label"
addStyleNames="{style.textLabel}"/>
<g:Label ui:field="requestedLabel" debugId="search-request-label"
addStyleNames="{style.textLabel}"/>
<g:FlowPanel ui:field="paginationPanel" debugId="search-result-pagination-container"
addStyleNames="{style.paginationPanel}">
<g:Button ui:field="previousBtn" debugId="previous-button"
addStyleNames="{style.button}"/>
<g:Button ui:field="nextBtn" debugId="previous-button" addStyleNames="{style.button}"/>
</g:FlowPanel>
</g:FlowPanel>
</g:north>
</g:DockLayoutPanel>
</ui:UiBinder>

View File

@ -22,7 +22,7 @@ import org.eclipse.che.api.project.shared.SearchOccurrence;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseProvider;
import org.eclipse.che.ide.Resources;
import org.eclipse.che.ide.api.resources.SearchResult;
import org.eclipse.che.ide.api.resources.SearchItemReference;
import org.eclipse.che.ide.search.factory.FindResultNodeFactory;
import org.eclipse.che.ide.ui.smartTree.data.AbstractTreeNode;
import org.eclipse.che.ide.ui.smartTree.data.Node;
@ -41,7 +41,7 @@ public class FoundItemNode extends AbstractTreeNode implements HasPresentation {
private FindResultNodeFactory nodeFactory;
private PromiseProvider promiseProvider;
private Resources resources;
private SearchResult searchResult;
private SearchItemReference searchItemReference;
private String request;
@Inject
@ -49,12 +49,12 @@ public class FoundItemNode extends AbstractTreeNode implements HasPresentation {
FindResultNodeFactory nodeFactory,
PromiseProvider promiseProvider,
Resources resources,
@Assisted SearchResult searchResult,
@Assisted SearchItemReference searchItemReference,
@Assisted String request) {
this.nodeFactory = nodeFactory;
this.promiseProvider = promiseProvider;
this.resources = resources;
this.searchResult = searchResult;
this.searchItemReference = searchItemReference;
this.request = request;
}
@ -75,7 +75,7 @@ public class FoundItemNode extends AbstractTreeNode implements HasPresentation {
/** {@inheritDoc} */
@Override
public String getName() {
return searchResult.getName();
return searchItemReference.getName();
}
/** {@inheritDoc} */
@ -89,9 +89,9 @@ public class FoundItemNode extends AbstractTreeNode implements HasPresentation {
public void updatePresentation(@NotNull NodePresentation presentation) {
StringBuilder resultTitle = new StringBuilder();
resultTitle.append(" (");
resultTitle.append(searchResult.getOccurrences().size());
resultTitle.append(searchItemReference.getOccurrences().size());
resultTitle.append(" occurrence");
if (searchResult.getOccurrences().size() > 1) {
if (searchItemReference.getOccurrences().size() > 1) {
resultTitle.append('s');
}
resultTitle.append(" of '");
@ -100,15 +100,15 @@ public class FoundItemNode extends AbstractTreeNode implements HasPresentation {
resultTitle.append(" found)");
presentation.setPresentableText(resultTitle.toString());
SpanElement spanElement = Elements.createSpanElement(resources.coreCss().foundItem());
spanElement.setId(searchResult.getPath());
spanElement.setInnerText(searchResult.getPath());
spanElement.setId(searchItemReference.getPath());
spanElement.setInnerText(searchItemReference.getPath());
presentation.setUserElement((Element) spanElement);
}
@Override
protected Promise<List<Node>> getChildrenImpl() {
List<Node> fileNodes;
List<SearchOccurrence> occurrences = searchResult.getOccurrences();
List<SearchOccurrence> occurrences = searchItemReference.getOccurrences();
occurrences.sort(
Comparator.comparingInt(
(SearchOccurrence searchOccurrence) -> searchOccurrence.getLineNumber()));
@ -117,7 +117,7 @@ public class FoundItemNode extends AbstractTreeNode implements HasPresentation {
.stream()
.map(
occurrence ->
nodeFactory.newFoundOccurrenceNode(occurrence, searchResult.getPath()))
nodeFactory.newFoundOccurrenceNode(occurrence, searchItemReference.getPath()))
.collect(Collectors.toList());
return promiseProvider.resolve(fileNodes);
}

View File

@ -103,15 +103,6 @@ public class AppStateManager {
}
}
@VisibleForTesting
void restoreWorkspaceState() {
final String wsId = appContext.getWorkspace().getId();
if (allWsState.hasKey(wsId)) {
restoreState(allWsState.getObject(wsId));
}
}
private void restoreWorkspaceStateWithDelay() {
new Timer() {
@Override
@ -121,7 +112,17 @@ public class AppStateManager {
}.schedule(1000);
}
private void restoreState(JsonObject settings) {
@VisibleForTesting
Promise<Void> restoreWorkspaceState() {
final String wsId = appContext.getWorkspace().getId();
if (allWsState.hasKey(wsId)) {
return restoreState(allWsState.getObject(wsId));
}
return promises.resolve(null);
}
private Promise<Void> restoreState(JsonObject settings) {
try {
if (settings.hasKey(WORKSPACE)) {
JsonObject workspace = settings.getObject(WORKSPACE);
@ -137,10 +138,12 @@ public class AppStateManager {
ignored -> component.loadState(workspace.getObject(key)));
}
}
return sequentialRestore;
}
} catch (JsonException e) {
Log.error(getClass(), e);
}
return promises.resolve(null);
}
public Promise<Void> persistWorkspaceState() {

View File

@ -242,6 +242,7 @@ messages.someFilesCanNotBeSaved = Failed to save all files
messages.saveChanges = {0} has been modified. Save changes?
messages.promptSaveChanges = Some data has been modified. Save changes?
messages.unableOpenResource = Unable to open {0}.
messages.canNotOpenFileInSplitMode=Can not open file {0} in split mode
messages.canNotOpenFileWithoutParams=Can not open file without parameters
messages.fileToOpenIsNotSpecified=File to open is not specified
messages.fileToOpenLineIsNotANumber=Line specified for the file to open is not a number

View File

@ -17,6 +17,7 @@ import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -93,6 +94,44 @@ public class GoalPageTest {
verify(dirtyStateListener, times(2)).onDirtyStateChanged();
}
@SuppressWarnings("unchecked")
@Test
public void shouldSetDefaultGoalIfInitialIsNull() throws Exception {
reset(goalRegistry);
reset(view);
String defaultGoalId = "Common";
CommandGoal defaultGoal = mock(CommandGoal.class);
when(goalRegistry.getDefaultGoal()).thenReturn(defaultGoal);
when(defaultGoal.getId()).thenReturn(defaultGoalId);
when(editedCommand.getGoal()).thenReturn(null);
page.initialize();
verify(goalRegistry).getAllGoals();
verify(view).setAvailableGoals(anySet());
verify(view).setGoal(defaultGoalId);
}
@SuppressWarnings("unchecked")
@Test
public void shouldSetDefaultGoalIfInitialIsEmpty() throws Exception {
reset(goalRegistry);
reset(view);
String defaultGoalId = "Common";
CommandGoal defaultGoal = mock(CommandGoal.class);
when(goalRegistry.getDefaultGoal()).thenReturn(defaultGoal);
when(defaultGoal.getId()).thenReturn(defaultGoalId);
when(editedCommand.getGoal()).thenReturn("");
page.initialize();
verify(goalRegistry).getAllGoals();
verify(view).setAvailableGoals(anySet());
verify(view).setGoal(defaultGoalId);
}
@Test
public void shouldCreateGoal() throws Exception {
// given

View File

@ -35,7 +35,7 @@ import org.eclipse.che.api.project.shared.dto.CopyOptions;
import org.eclipse.che.api.project.shared.dto.ItemReference;
import org.eclipse.che.api.project.shared.dto.MoveOptions;
import org.eclipse.che.api.project.shared.dto.NewProjectConfigDto;
import org.eclipse.che.api.project.shared.dto.SearchResultDto;
import org.eclipse.che.api.project.shared.dto.ProjectSearchResponseDto;
import org.eclipse.che.api.project.shared.dto.SourceEstimation;
import org.eclipse.che.api.project.shared.dto.TreeElement;
import org.eclipse.che.api.promises.client.Promise;
@ -43,6 +43,7 @@ import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto;
import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto;
import org.eclipse.che.ide.MimeType;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.dto.DtoFactory;
import org.eclipse.che.ide.resource.Path;
import org.eclipse.che.ide.rest.AsyncRequest;
@ -84,8 +85,8 @@ public class ProjectServiceClientTest {
@Mock private Unmarshallable<ItemReference> unmarshallableItemRef;
@Mock private Unmarshallable<List<ProjectConfigDto>> unmarshallablePrjsConf;
@Mock private Unmarshallable<ProjectConfigDto> unmarshallablePrjConf;
@Mock private Unmarshallable<List<SearchResultDto>> unmarshallableSearch;
@Mock private Promise<List<SearchResultDto>> searchPromise;
@Mock private Unmarshallable<ProjectSearchResponseDto> unmarshallableSearch;
@Mock private Promise<ProjectSearchResponseDto> searchPromise;
@Mock private Unmarshallable<List<SourceEstimation>> unmarshallbleSourcesEstimation;
@Mock private Unmarshallable<SourceEstimation> unmarshallbleSourceEstimation;
@Mock private Unmarshallable<TreeElement> unmarshallableTreeElem;
@ -121,7 +122,8 @@ public class ProjectServiceClientTest {
.thenReturn(unmarshallbleSourcesEstimation);
when(unmarshaller.newUnmarshaller(SourceEstimation.class))
.thenReturn(unmarshallbleSourceEstimation);
when(unmarshaller.newListUnmarshaller(SearchResultDto.class)).thenReturn(unmarshallableSearch);
when(unmarshaller.newUnmarshaller(ProjectSearchResponseDto.class))
.thenReturn(unmarshallableSearch);
when(unmarshaller.newUnmarshaller(TreeElement.class)).thenReturn(unmarshallableTreeElem);
when(unmarshaller.newUnmarshaller(ProjectConfigDto.class)).thenReturn(unmarshallablePrjConf);
@ -140,7 +142,7 @@ public class ProjectServiceClientTest {
client.getTree(Path.EMPTY, 1, true);
verify(asyncRequest, never()).loader(any(AsyncRequestLoader.class)); //see CHE-3467
verify(asyncRequest, never()).loader(any(AsyncRequestLoader.class)); // see CHE-3467
}
@Test
@ -203,7 +205,7 @@ public class ProjectServiceClientTest {
verify(asyncRequest).header(ACCEPT, MimeType.APPLICATION_JSON);
verify(loaderFactory).newLoader("Searching...");
verify(asyncRequest).loader(messageLoader);
verify(unmarshaller).newListUnmarshaller(SearchResultDto.class);
verify(unmarshaller).newUnmarshaller(ProjectSearchResponseDto.class);
verify(asyncRequest).send(unmarshallableSearch);
}

View File

@ -86,18 +86,6 @@ public class ZipImporterPagePresenterTest {
verify(delegate).updateControls();
}
@Test
public void projectUrlWithoutZipEnteredTest() {
//url without .zip was entered
String incorrectUrl = "https://host.com/some/path/angularjs.ip";
when(view.getProjectName()).thenReturn("");
presenter.projectUrlChanged(incorrectUrl);
verify(view).showUrlError(anyString());
verify(delegate).updateControls();
}
@Test
public void projectUrlStartWithWhiteSpaceEnteredTest() {
when(view.getProjectName()).thenReturn("name");

View File

@ -11,6 +11,7 @@
package org.eclipse.che.ide.search;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
@ -26,7 +27,9 @@ import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.api.resources.Container;
import org.eclipse.che.ide.api.resources.SearchItemReference;
import org.eclipse.che.ide.api.resources.SearchResult;
import org.eclipse.che.ide.resource.Path;
import org.eclipse.che.ide.search.presentation.FindResultPresenter;
@ -51,12 +54,14 @@ public class FullTextSearchPresenterTest {
@Mock private AppContext appContext;
@Mock private Container workspaceRoot;
@Mock private Container searchContainer;
@Mock private SearchResult searchResult;
@Mock private QueryExpression queryExpression;
@Mock private Promise<Optional<Container>> optionalContainerPromise;
@Captor private ArgumentCaptor<Operation<Optional<Container>>> optionalContainerCaptor;
@Mock private Promise<List<SearchResult>> searchResultPromise;
@Captor private ArgumentCaptor<Operation<List<SearchResult>>> searchResultCaptor;
@Mock private Promise<SearchResult> searchResultPromise;
@Captor private ArgumentCaptor<Operation<SearchResult>> searchResultCaptor;
@Captor private ArgumentCaptor<Operation<PromiseError>> operationErrorCapture;
@Captor private ArgumentCaptor<Operation<List<SearchResult>>> operationSuccessCapture;
@Captor private ArgumentCaptor<Operation<List<SearchItemReference>>> operationSuccessCapture;
FullTextSearchPresenter fullTextSearchPresenter;
@ -80,7 +85,9 @@ public class FullTextSearchPresenterTest {
when(view.getPathToSearch()).thenReturn("/search");
when(appContext.getWorkspaceRoot()).thenReturn(workspaceRoot);
when(workspaceRoot.getContainer(any(Path.class))).thenReturn(optionalContainerPromise);
when(searchContainer.search(anyString(), anyString())).thenReturn(searchResultPromise);
when(searchContainer.search(any(QueryExpression.class))).thenReturn(searchResultPromise);
when(searchContainer.createSearchQueryExpression(anyString(), anyString()))
.thenReturn(queryExpression);
fullTextSearchPresenter.search(SEARCHED_TEXT);
@ -88,11 +95,12 @@ public class FullTextSearchPresenterTest {
optionalContainerCaptor.getValue().apply(Optional.of(searchContainer));
verify(searchResultPromise).then(searchResultCaptor.capture());
searchResultCaptor.getValue().apply(Collections.emptyList());
searchResultCaptor.getValue().apply(searchResult);
verify(view, never()).showErrorMessage(anyString());
verify(view).close();
verify(findResultPresenter).handleResponse(eq(Collections.emptyList()), eq(SEARCHED_TEXT));
verify(findResultPresenter)
.handleResponse(eq(searchResult), eq(queryExpression), eq(SEARCHED_TEXT));
}
@Test
@ -101,7 +109,9 @@ public class FullTextSearchPresenterTest {
when(view.isWholeWordsOnly()).thenReturn(false);
when(appContext.getWorkspaceRoot()).thenReturn(workspaceRoot);
when(workspaceRoot.getContainer(any(Path.class))).thenReturn(optionalContainerPromise);
when(searchContainer.search(anyString(), anyString())).thenReturn(searchResultPromise);
when(searchContainer.search(any(QueryExpression.class))).thenReturn(searchResultPromise);
when(searchContainer.createSearchQueryExpression(anyString(), anyString()))
.thenReturn(queryExpression);
final String search = NameGenerator.generate("test", 10);
fullTextSearchPresenter.search(search);
@ -110,13 +120,13 @@ public class FullTextSearchPresenterTest {
optionalContainerCaptor.getValue().apply(Optional.of(searchContainer));
verify(searchResultPromise).then(searchResultCaptor.capture());
searchResultCaptor.getValue().apply(Collections.emptyList());
searchResultCaptor.getValue().apply(searchResult);
verify(searchContainer).search(anyString(), eq("*" + search + "*"));
verify(searchContainer).search(queryExpression);
verify(view).isWholeWordsOnly();
verify(view, never()).showErrorMessage(anyString());
verify(view).close();
verify(findResultPresenter).handleResponse(eq(Collections.emptyList()), eq(search));
verify(findResultPresenter).handleResponse(eq(searchResult), eq(queryExpression), eq(search));
}
@Test
@ -125,7 +135,9 @@ public class FullTextSearchPresenterTest {
when(view.isWholeWordsOnly()).thenReturn(true);
when(appContext.getWorkspaceRoot()).thenReturn(workspaceRoot);
when(workspaceRoot.getContainer(any(Path.class))).thenReturn(optionalContainerPromise);
when(searchContainer.search(anyString(), anyString())).thenReturn(searchResultPromise);
when(searchContainer.search(any(QueryExpression.class))).thenReturn(searchResultPromise);
when(searchContainer.createSearchQueryExpression(anyString(), anyString()))
.thenReturn(queryExpression);
final String search = NameGenerator.generate("test", 10);
fullTextSearchPresenter.search(search);
@ -134,13 +146,13 @@ public class FullTextSearchPresenterTest {
optionalContainerCaptor.getValue().apply(Optional.of(searchContainer));
verify(searchResultPromise).then(searchResultCaptor.capture());
searchResultCaptor.getValue().apply(Collections.emptyList());
searchResultCaptor.getValue().apply(searchResult);
verify(searchContainer).search(anyString(), eq(search));
verify(searchContainer).search(queryExpression);
verify(view).isWholeWordsOnly();
verify(view, never()).showErrorMessage(anyString());
verify(view).close();
verify(findResultPresenter).handleResponse(eq(Collections.emptyList()), eq(search));
verify(findResultPresenter).handleResponse(eq(searchResult), eq(queryExpression), eq(search));
}
@Test
@ -156,7 +168,8 @@ public class FullTextSearchPresenterTest {
verify(view).showErrorMessage(anyString());
verify(view, never()).close();
verify(findResultPresenter, never()).handleResponse(any(List.class), anyString());
verify(findResultPresenter, never())
.handleResponse(anyObject(), eq(queryExpression), anyString());
}
@Test
@ -167,8 +180,10 @@ public class FullTextSearchPresenterTest {
when(view.getPathToSearch()).thenReturn("/search");
when(appContext.getWorkspaceRoot()).thenReturn(workspaceRoot);
when(workspaceRoot.getContainer(any(Path.class))).thenReturn(optionalContainerPromise);
when(searchContainer.search(anyString(), anyString())).thenReturn(searchResultPromise);
List<SearchResult> result = Collections.emptyList();
when(searchContainer.search(any(QueryExpression.class))).thenReturn(searchResultPromise);
when(searchContainer.createSearchQueryExpression(anyString(), anyString()))
.thenReturn(queryExpression);
List<SearchItemReference> result = Collections.emptyList();
fullTextSearchPresenter.onEnterClicked();
@ -176,11 +191,12 @@ public class FullTextSearchPresenterTest {
optionalContainerCaptor.getValue().apply(Optional.of(searchContainer));
verify(searchResultPromise).then(searchResultCaptor.capture());
searchResultCaptor.getValue().apply(result);
searchResultCaptor.getValue().apply(searchResult);
verify(view, never()).showErrorMessage(anyString());
verify(view).close();
verify(findResultPresenter).handleResponse(eq(result), eq(SEARCHED_TEXT));
verify(findResultPresenter)
.handleResponse(eq(searchResult), eq(queryExpression), eq(SEARCHED_TEXT));
}
@Test

View File

@ -10,20 +10,36 @@
*/
package org.eclipse.che.ide.search.presentation;
import static java.util.Collections.emptyList;
import static org.eclipse.che.ide.search.FullTextSearchPresenter.SEARCH_RESULT_ITEMS;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwtmockito.GwtMockitoTestRunner;
import com.google.web.bindery.event.shared.EventBus;
import java.util.ArrayList;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.Resources;
import org.eclipse.che.ide.api.parts.PartStackType;
import org.eclipse.che.ide.api.parts.WorkspaceAgent;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.api.resources.SearchItemReference;
import org.eclipse.che.ide.api.resources.SearchResult;
import org.eclipse.che.ide.project.ProjectServiceClient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
@ -39,9 +55,29 @@ public class FindResultPresenterTest {
@Mock private FindResultView view;
@Mock private WorkspaceAgent workspaceAgent;
@Mock private Resources resources;
@Mock private ProjectServiceClient projectServiceClient;
@Mock private EventBus eventBus;
@Mock private QueryExpression queryExpression;
@Mock private Promise<SearchResult> searchResultPromise;
@Mock private SearchItemReference searchItemReference;
@Captor private ArgumentCaptor<Operation<SearchResult>> argumentCaptor;
@Mock private SearchResult result;
@InjectMocks FindResultPresenter findResultPresenter;
private ArrayList<SearchItemReference> items = new ArrayList<>(SEARCH_RESULT_ITEMS);
@Before
public void setUp() throws Exception {
for (int i = 0; i < SEARCH_RESULT_ITEMS; i++) {
items.add(searchItemReference);
}
when(projectServiceClient.search(queryExpression)).thenReturn(searchResultPromise);
when(searchResultPromise.then(Matchers.<Operation<SearchResult>>any()))
.thenReturn(searchResultPromise);
when(result.getItemReferences()).thenReturn(items);
}
@Test
public void titleShouldBeReturned() {
@ -72,10 +108,105 @@ public class FindResultPresenterTest {
@Test
public void responseShouldBeHandled() throws Exception {
findResultPresenter.handleResponse(Matchers.any(), anyString());
QueryExpression queryExpression = mock(QueryExpression.class);
findResultPresenter.handleResponse(result, queryExpression, "request");
verify(workspaceAgent).openPart(findResultPresenter, PartStackType.INFORMATION);
verify(workspaceAgent).setActivePart(findResultPresenter);
verify(view).showResults(Matchers.any(), anyString());
verify(view).showResults(result, "request");
verify(view).setPreviousBtnActive(false);
verify(view).setNextBtnActive(true);
}
@Test
public void nextPageShouldNotBeShownIfNoResults() throws Exception {
findResultPresenter.handleResponse(result, queryExpression, "request");
reset(view);
findResultPresenter.onNextButtonClicked();
verify(queryExpression).setSkipCount(SEARCH_RESULT_ITEMS);
verify(searchResultPromise).then(argumentCaptor.capture());
argumentCaptor.getValue().apply(new SearchResult(emptyList(), 0));
verify(view).setPreviousBtnActive(true);
verify(view).setNextBtnActive(false);
verify(view, never()).showResults(anyObject(), anyString());
}
@Test
public void nextButtonShouldBeActiveIfResultHasMaxValueElements() throws Exception {
findResultPresenter.handleResponse(result, queryExpression, "request");
findResultPresenter.handleResponse(result, queryExpression, "request");
reset(view);
findResultPresenter.onNextButtonClicked();
verify(queryExpression).setSkipCount(SEARCH_RESULT_ITEMS);
verify(searchResultPromise).then(argumentCaptor.capture());
SearchResult searchResult = new SearchResult(items, 0);
argumentCaptor.getValue().apply(searchResult);
verify(view).setPreviousBtnActive(true);
verify(view).setNextBtnActive(true);
verify(view).showResults(searchResult, "request");
}
@Test
public void nextButtonShouldBeDisableIfResultHasLessThanMaxValue() throws Exception {
items.remove(0);
findResultPresenter.handleResponse(result, queryExpression, "request");
reset(view);
findResultPresenter.onNextButtonClicked();
verify(queryExpression).setSkipCount(SEARCH_RESULT_ITEMS);
verify(searchResultPromise).then(argumentCaptor.capture());
SearchResult searchResult = new SearchResult(items, 0);
argumentCaptor.getValue().apply(searchResult);
verify(view).setPreviousBtnActive(true);
verify(view).setNextBtnActive(false);
verify(view).showResults(searchResult, "request");
}
@Test
public void previousButtonShouldBeActiveIfResultHasLessThanMaxValue() throws Exception {
items.remove(0);
findResultPresenter.handleResponse(result, queryExpression, "request");
reset(view);
findResultPresenter.onPreviousButtonClicked();
verify(queryExpression).setSkipCount(-SEARCH_RESULT_ITEMS);
verify(searchResultPromise).then(argumentCaptor.capture());
SearchResult searchResult = new SearchResult(items, 0);
argumentCaptor.getValue().apply(searchResult);
verify(view).setNextBtnActive(true);
verify(view).setPreviousBtnActive(false);
verify(view).showResults(searchResult, "request");
}
@Test
public void previousButtonShouldBeActiveIfResultHasMaxValueElements() throws Exception {
findResultPresenter.handleResponse(result, queryExpression, "request");
reset(view);
findResultPresenter.onPreviousButtonClicked();
verify(queryExpression).setSkipCount(-SEARCH_RESULT_ITEMS);
verify(searchResultPromise).then(argumentCaptor.capture());
SearchResult searchResult = new SearchResult(items, 0);
argumentCaptor.getValue().apply(searchResult);
verify(view).setNextBtnActive(true);
verify(view).setPreviousBtnActive(true);
verify(view).showResults(searchResult, "request");
}
}

View File

@ -16,6 +16,7 @@ import static org.eclipse.che.ide.editor.orion.client.KeyMode.VI;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.BlurEvent;
@ -256,7 +257,7 @@ public class OrionEditorWidget extends Composite
@Override
public void onEvent(OrionInputChangedEventOverlay event) {
if (initializationHandler != null) {
initializationHandler.onContentInitialized();
Scheduler.get().scheduleDeferred(initializationHandler::onContentInitialized);
}
}
},

View File

@ -93,6 +93,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-mail</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-test</artifactId>

View File

@ -20,6 +20,7 @@ import org.eclipse.che.multiuser.api.permission.shared.model.PermissionsDomain;
import org.eclipse.che.multiuser.organization.api.listener.MemberEventsPublisher;
import org.eclipse.che.multiuser.organization.api.listener.OrganizationEventsWebsocketBroadcaster;
import org.eclipse.che.multiuser.organization.api.listener.RemoveOrganizationOnLastUserRemovedEventSubscriber;
import org.eclipse.che.multiuser.organization.api.notification.OrganizationNotificationEmailSender;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationDomain;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationPermissionsFilter;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationResourceDistributionServicePermissionsFilter;
@ -75,5 +76,7 @@ public class OrganizationApiModule extends AbstractModule {
Names.named(SuperPrivilegesChecker.SUPER_PRIVILEGED_DOMAINS))
.addBinding()
.to(OrganizationDomain.class);
bind(OrganizationNotificationEmailSender.class).asEagerSingleton();
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.organization.api.notification;
import static javax.ws.rs.core.MediaType.TEXT_HTML;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.mail.EmailBean;
import org.eclipse.che.mail.template.TemplateProcessor;
import org.eclipse.che.mail.template.exception.TemplateException;
/**
* Builds emails notification about organization changes.
*
* @author Sergii Leshchenko
*/
public class OrganizationEmailNotifications {
private final String mailFrom;
private final String memberAddedSubject;
private final String memberAddedTemplate;
private final String memberRemovedSubject;
private final String memberRemovedTemplate;
private final String orgRemovedSubject;
private final String orgRemovedTemplate;
private final String orgRenamedSubject;
private final String orgRenamedTemplate;
private final TemplateProcessor templateProcessor;
@Inject
public OrganizationEmailNotifications(
@Named("che.mail.from_email_address") String mailFrom,
@Named("che.organization.email.member_added_subject") String memberAddedSubject,
@Named("che.organization.email.member_added_template") String memberAddedTemplate,
@Named("che.organization.email.member_removed_subject") String memberRemovedSubject,
@Named("che.organization.email.member_removed_template") String memberRemovedTemplate,
@Named("che.organization.email.org_removed_subject") String orgRemovedSubject,
@Named("che.organization.email.org_removed_template") String orgRemovedTemplate,
@Named("che.organization.email.org_renamed_subject") String orgRenamedSubject,
@Named("che.organization.email.org_renamed_template") String orgRenamedTemplate,
TemplateProcessor templateProcessor) {
this.mailFrom = mailFrom;
this.memberAddedSubject = memberAddedSubject;
this.memberAddedTemplate = memberAddedTemplate;
this.memberRemovedSubject = memberRemovedSubject;
this.memberRemovedTemplate = memberRemovedTemplate;
this.orgRemovedSubject = orgRemovedSubject;
this.orgRemovedTemplate = orgRemovedTemplate;
this.orgRenamedSubject = orgRenamedSubject;
this.orgRenamedTemplate = orgRenamedTemplate;
this.templateProcessor = templateProcessor;
}
public EmailBean memberAdded(
String organizationName, String dashboardEndpoint, String orgQualifiedName, String initiator)
throws ServerException {
Map<String, Object> attributes = new HashMap<>();
attributes.put("organizationName", organizationName);
attributes.put("dashboardEndpoint", dashboardEndpoint);
attributes.put("orgQualifiedName", orgQualifiedName);
attributes.put("initiator", initiator);
return doBuildEmail(memberAddedSubject, memberAddedTemplate, attributes);
}
public EmailBean memberRemoved(String organizationName, String initiator) throws ServerException {
Map<String, Object> attributes = new HashMap<>();
attributes.put("organizationName", organizationName);
attributes.put("initiator", initiator);
return doBuildEmail(memberRemovedSubject, memberRemovedTemplate, attributes);
}
public EmailBean organizationRenamed(String oldName, String newName) throws ServerException {
Map<String, Object> attributes = new HashMap<>();
attributes.put("orgOldName", oldName);
attributes.put("orgNewName", newName);
return doBuildEmail(orgRenamedSubject, orgRenamedTemplate, attributes);
}
public EmailBean organizationRemoved(String organizationName) throws ServerException {
Map<String, Object> attributes = new HashMap<>();
attributes.put("organizationName", organizationName);
return doBuildEmail(orgRemovedSubject, orgRemovedTemplate, attributes);
}
protected EmailBean doBuildEmail(
String subject, String templatePath, Map<String, Object> attributes) throws ServerException {
try {
return new EmailBean()
.withSubject(subject)
.withBody(templateProcessor.process(templatePath, attributes))
.withFrom(mailFrom)
.withReplyTo(mailFrom)
.withMimeType(TEXT_HTML);
} catch (TemplateException e) {
throw new ServerException(e.getMessage());
}
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.organization.api.notification;
import static org.eclipse.che.api.core.Pages.iterate;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.notification.EventSubscriber;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.mail.EmailBean;
import org.eclipse.che.mail.MailSender;
import org.eclipse.che.multiuser.organization.api.OrganizationManager;
import org.eclipse.che.multiuser.organization.api.event.MemberAddedEvent;
import org.eclipse.che.multiuser.organization.api.event.MemberRemovedEvent;
import org.eclipse.che.multiuser.organization.api.event.OrganizationRemovedEvent;
import org.eclipse.che.multiuser.organization.api.event.OrganizationRenamedEvent;
import org.eclipse.che.multiuser.organization.shared.event.OrganizationEvent;
import org.eclipse.che.multiuser.organization.shared.model.Member;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Notify users about organization changes.
*
* @author Anton Korneta
* @author Sergii Leshchenko
*/
@Singleton
public class OrganizationNotificationEmailSender implements EventSubscriber<OrganizationEvent> {
private static final Logger LOG =
LoggerFactory.getLogger(OrganizationNotificationEmailSender.class);
private final String apiEndpoint;
private final MailSender mailSender;
private final OrganizationManager organizationManager;
private final UserManager userManager;
private final OrganizationEmailNotifications emails;
@Inject
public OrganizationNotificationEmailSender(
@Named("che.api") String apiEndpoint,
MailSender mailSender,
OrganizationManager organizationManager,
UserManager userManager,
OrganizationEmailNotifications emails) {
this.apiEndpoint = apiEndpoint;
this.mailSender = mailSender;
this.organizationManager = organizationManager;
this.userManager = userManager;
this.emails = emails;
}
@Inject
public void subscribe(EventService eventService) {
eventService.subscribe(this);
}
@Override
public void onEvent(OrganizationEvent event) {
try {
if (event.getInitiator() != null) {
if (event.getOrganization().getParent() == null) {
try {
userManager.getByName(event.getOrganization().getName());
return;
} catch (NotFoundException ex) {
//it is not personal organization
}
}
switch (event.getType()) {
case MEMBER_ADDED:
send((MemberAddedEvent) event);
break;
case MEMBER_REMOVED:
send((MemberRemovedEvent) event);
break;
case ORGANIZATION_REMOVED:
send((OrganizationRemovedEvent) event);
break;
case ORGANIZATION_RENAMED:
send((OrganizationRenamedEvent) event);
}
}
} catch (Exception ex) {
LOG.error("Failed to send email notification '{}' cause : '{}'", ex.getMessage());
}
}
private void send(MemberAddedEvent event) throws ServerException {
final String orgName = event.getOrganization().getName();
final String emailTo = event.getMember().getEmail();
final String initiator = event.getInitiator();
final String dashboardEndpoint = apiEndpoint.replace("api", "dashboard");
final String orgQualifiedName = event.getOrganization().getQualifiedName();
EmailBean memberAddedEmail =
emails.memberAdded(orgName, dashboardEndpoint, orgQualifiedName, initiator);
mailSender.sendAsync(memberAddedEmail.withTo(emailTo));
}
private void send(MemberRemovedEvent event) throws ServerException {
final String organizationName = event.getOrganization().getName();
final String initiator = event.getInitiator();
final String emailTo = event.getMember().getEmail();
EmailBean memberRemovedEmail = emails.memberRemoved(organizationName, initiator);
mailSender.sendAsync(memberRemovedEmail.withTo(emailTo));
}
private void send(OrganizationRemovedEvent event) throws ServerException, NotFoundException {
String organizationName = event.getOrganization().getName();
EmailBean orgRemovedEmail = emails.organizationRemoved(organizationName);
for (Member member : event.getMembers()) {
try {
final String emailTo = userManager.getById(member.getUserId()).getEmail();
mailSender.sendAsync(new EmailBean(orgRemovedEmail).withTo(emailTo));
} catch (Exception ignore) {
}
}
}
private void send(OrganizationRenamedEvent event) throws ServerException, NotFoundException {
EmailBean orgRenamedEmail = emails.organizationRenamed(event.getOldName(), event.getNewName());
for (Member member :
iterate(
(max, skip) ->
organizationManager.getMembers(event.getOrganization().getId(), max, skip))) {
try {
final String emailTo = userManager.getById(member.getUserId()).getEmail();
mailSender.sendAsync(new EmailBean(orgRenamedEmail).withTo(emailTo));
} catch (Exception ignore) {
}
}
}
}

View File

@ -0,0 +1,23 @@
\<html>
\<head>
\<style>@import url(https://fonts.googleapis.com/css?family=Roboto);\</style>
\</head>
\<body style="width: 700px; margin: 0 auto; font-size: 16px; font-family: Roboto;">
\<div style="background-color: #292c2f; color: white; padding: 25px 10px;">
\<span style="font-size: 26px; color: #cccccc;">Eclipse Che\</span>
\<div style="height: 50px">\</div>
\<span style="font-size: 24px; color: white;">Organization has been deleted\</span>
\</div>
\<div style="padding: 10px;">
\<p>Hi,\</p>
\<p>\<b><organizationName>\</b> organization has been deleted.\</p>
\</div>
\<div style="background-color: #292c2f; color: white; padding: 15px 10px 47px 10px;">
\<a href="http://www.eclipse.org/che/">
\<img src="https://www.eclipse.org/che/images/logo-eclipseche.svg" height="32" align="left" alt=" " />
\</a>
\</div>
\</body>
\</html>

View File

@ -0,0 +1,23 @@
\<html>
\<head>
\<style>@import url(https://fonts.googleapis.com/css?family=Roboto);\</style>
\</head>
\<body style="width: 700px; margin: 0 auto; font-size: 16px; font-family: Roboto;">
\<div style="background-color: #292c2f; color: white; padding: 25px 10px;">
\<span style="font-size: 26px; color: #cccccc;">Eclipse Che\</span>
\<div style="height: 50px">\</div>
\<span style="font-size: 24px; color: white;">Organization has been renamed\</span>
\</div>
\<div style="padding: 10px;">
\<p>Hi,\</p>
\<p>\<b><orgOldName>\</b> organization has been renamed to \<b><orgNewName>\</b>\</p>
\</div>
\<div style="background-color: #292c2f; color: white; padding: 15px 10px 47px 10px;">
\<a href="http://www.eclipse.org/che/">
\<img src="https://www.eclipse.org/che/images/logo-eclipseche.svg" height="32" align="left" alt=" " />
\</a>
\</div>
\</body>
\</html>

View File

@ -0,0 +1,24 @@
\<html>
\<head>
\<style>@import url(https://fonts.googleapis.com/css?family=Roboto);\</style>
\</head>
\<body style="width: 700px; margin: 0 auto; font-size: 16px; font-family: Roboto;">
\<div style="background-color: #292c2f; color: white; padding: 25px 10px;">
\<span style="font-size: 26px; color: #cccccc;">Eclipse Che\</span>
\<div style="height: 50px">\</div>
\<span style="font-size: 24px; color: white;">User added to organization\</span>
\</div>
\<div style="padding: 10px;">
\<p>Hi,\</p>
\<p>\<b><initiator>\</b> added you to a Che organization called \<b><organizationName>\</b>.\</p>
\<p>Access the organization here \<a href="<dashboardEndpoint>/#/organization/<orgQualifiedName>">link\</a>.\</p>
\</div>
\<div style="background-color: #292c2f; color: white; padding: 15px 10px 47px 10px;">
\<a href="http://www.eclipse.org/che/">
\<img src="https://www.eclipse.org/che/images/logo-eclipseche.svg" height="32" align="left" alt=" " />
\</a>
\</div>
\</body>
\</html>

View File

@ -0,0 +1,24 @@
\<html>
\<head>
\<style>@import url(https://fonts.googleapis.com/css?family=Roboto);\</style>
\</head>
\<body style="width: 700px; margin: 0 auto; font-size: 16px; font-family: Roboto;">
\<div style="background-color: #292c2f; color: white; padding: 25px 10px;">
\<span style="font-size: 26px; color: #cccccc;">Eclipse Che\</span>
\<div style="height: 50px">\</div>
\<span style="font-size: 24px; color: white;">User removed from organization\</span>
\</div>
\<div style="padding: 10px;">
\<p>Hi,\</p>
\<p>\<b><initiator>\</b> removed you from a Che organization called \<b><organizationName>\</b>.\</p>
\</div>
\<div style="background-color: #292c2f; color: white; padding: 15px 10px 47px 10px;">
\<a href="http://www.eclipse.org/che/">
\<img src="https://www.eclipse.org/che/images/logo-eclipseche.svg" height="32" align="left" alt=" " />
\</a>
\</div>
\</body>
\</html>

View File

@ -0,0 +1,132 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.organization.api.notification;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.testng.Assert.assertEquals;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import org.eclipse.che.mail.EmailBean;
import org.eclipse.che.mail.template.TemplateProcessor;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/**
* Test for {@link OrganizationEmailNotifications}.
*
* @author Sergii Leshchenko
*/
@Listeners(MockitoTestNGListener.class)
public class OrganizationEmailNotificationsTest {
private static final String MAIL_FROM = "mail@from.com";
private static final String MEMBER_ADDED_SUBJECT = "Member Added";
private static final String MEMBER_ADDED_TEMPLATE = "/member-added";
private static final String MEMBER_REMOVED_SUBJECT = "Member Removed";
private static final String MEMBER_REMOVED_TEMPLATE = "/member-removed";
private static final String ORG_REMOVED_SUBJECT = "Org Removed";
private static final String ORG_REMOVED_TEMPLATE = "/org-removed";
private static final String ORG_RENAMED_SUBJECT = "Org Renamed";
private static final String ORG_RENAMED_TEMPLATE = "/org-renamed";
@Mock private TemplateProcessor templateProcessor;
@Captor private ArgumentCaptor<Map<String, Object>> attributesCaptor;
private OrganizationEmailNotifications emails;
@BeforeMethod
public void setUp() throws Exception {
initMocks(this);
emails =
new OrganizationEmailNotifications(
MAIL_FROM,
MEMBER_ADDED_SUBJECT,
MEMBER_ADDED_TEMPLATE,
MEMBER_REMOVED_SUBJECT,
MEMBER_REMOVED_TEMPLATE,
ORG_REMOVED_SUBJECT,
ORG_REMOVED_TEMPLATE,
ORG_RENAMED_SUBJECT,
ORG_RENAMED_TEMPLATE,
templateProcessor);
}
@Test
public void shouldReturnMemberAddedEmail() throws Exception {
EmailBean email =
emails.memberAdded("SuperOrg", "localhost:8080/dashboard", "/superOrg/org", "admin");
assertEquals(email.getFrom(), MAIL_FROM);
assertEquals(email.getReplyTo(), MAIL_FROM);
assertEquals(email.getMimeType(), MediaType.TEXT_HTML);
assertEquals(email.getSubject(), MEMBER_ADDED_SUBJECT);
verify(templateProcessor).process(eq(MEMBER_ADDED_TEMPLATE), attributesCaptor.capture());
Map<String, Object> attributes = attributesCaptor.getValue();
assertEquals(attributes.get("organizationName"), "SuperOrg");
assertEquals(attributes.get("dashboardEndpoint"), "localhost:8080/dashboard");
assertEquals(attributes.get("orgQualifiedName"), "/superOrg/org");
assertEquals(attributes.get("initiator"), "admin");
}
@Test
public void shouldReturnMemberRemovedEmail() throws Exception {
EmailBean email = emails.memberRemoved("SuperOrg", "admin");
assertEquals(email.getFrom(), MAIL_FROM);
assertEquals(email.getReplyTo(), MAIL_FROM);
assertEquals(email.getMimeType(), MediaType.TEXT_HTML);
assertEquals(email.getSubject(), MEMBER_REMOVED_SUBJECT);
verify(templateProcessor).process(eq(MEMBER_REMOVED_TEMPLATE), attributesCaptor.capture());
Map<String, Object> attributes = attributesCaptor.getValue();
assertEquals(attributes.get("organizationName"), "SuperOrg");
assertEquals(attributes.get("initiator"), "admin");
}
@Test
public void shouldReturnOrgRenamedEmail() throws Exception {
EmailBean email = emails.organizationRenamed("Org", "SuperOrg");
assertEquals(email.getFrom(), MAIL_FROM);
assertEquals(email.getReplyTo(), MAIL_FROM);
assertEquals(email.getMimeType(), MediaType.TEXT_HTML);
assertEquals(email.getSubject(), ORG_RENAMED_SUBJECT);
verify(templateProcessor).process(eq(ORG_RENAMED_TEMPLATE), attributesCaptor.capture());
Map<String, Object> attributes = attributesCaptor.getValue();
assertEquals(attributes.get("orgOldName"), "Org");
assertEquals(attributes.get("orgNewName"), "SuperOrg");
}
@Test
public void shouldReturnOrgRemovedEmail() throws Exception {
EmailBean email = emails.organizationRemoved("Org");
assertEquals(email.getFrom(), MAIL_FROM);
assertEquals(email.getReplyTo(), MAIL_FROM);
assertEquals(email.getMimeType(), MediaType.TEXT_HTML);
assertEquals(email.getSubject(), ORG_REMOVED_SUBJECT);
verify(templateProcessor).process(eq(ORG_REMOVED_TEMPLATE), attributesCaptor.capture());
Map<String, Object> attributes = attributesCaptor.getValue();
assertEquals(attributes.get("organizationName"), "Org");
}
}

View File

@ -0,0 +1,230 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.organization.api.notification;
import static java.util.Arrays.*;
import static java.util.Collections.emptyList;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.Page;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.user.server.model.impl.UserImpl;
import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer;
import org.eclipse.che.mail.EmailBean;
import org.eclipse.che.mail.MailSender;
import org.eclipse.che.multiuser.organization.api.OrganizationManager;
import org.eclipse.che.multiuser.organization.api.event.MemberAddedEvent;
import org.eclipse.che.multiuser.organization.api.event.MemberRemovedEvent;
import org.eclipse.che.multiuser.organization.api.event.OrganizationRemovedEvent;
import org.eclipse.che.multiuser.organization.api.event.OrganizationRenamedEvent;
import org.eclipse.che.multiuser.organization.shared.model.Member;
import org.eclipse.che.multiuser.organization.spi.impl.MemberImpl;
import org.eclipse.che.multiuser.organization.spi.impl.OrganizationImpl;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/**
* Test for {@link OrganizationNotificationEmailSender}.
*
* @author Sergii Leshchenko
*/
@Listeners(MockitoTestNGListener.class)
public class OrganizationNotificationEmailSenderTest {
public static final String API_ENDPOINT = "http://localhost/api";
public static final String DASHBOARD_ENDPOINT = API_ENDPOINT.replace("/api", "/dashboard");
@Mock private MailSender mailSender;
@Mock private OrganizationManager organizationManager;
@Mock private UserManager userManager;
@Mock private OrganizationEmailNotifications emails;
@Mock private EventService eventService;
OrganizationNotificationEmailSender emailSender;
@BeforeMethod
public void setUp() throws Exception {
emailSender =
new OrganizationNotificationEmailSender(
API_ENDPOINT, mailSender, organizationManager, userManager, emails);
}
@Test
public void shouldSelfSubscribe() {
//when
emailSender.subscribe(eventService);
//then
verify(eventService).subscribe(emailSender);
}
@Test
public void shouldSendNotificationAboutMembershipAdding() throws Exception {
//given
EmailBean email = mock(EmailBean.class, new SelfReturningAnswer());
when(emails.memberAdded(anyString(), anyString(), anyString(), anyString())).thenReturn(email);
//when
emailSender.onEvent(
new MemberAddedEvent(
"admin",
new UserImpl("id", "email", null),
new OrganizationImpl("id", "/parent/name", "parent")));
//then
verify(emails).memberAdded("name", DASHBOARD_ENDPOINT, "/parent/name", "admin");
verify(email).withTo("email");
verify(mailSender).sendAsync(email);
}
@Test
public void shouldSendNotificationAboutMembershipRemoving() throws Exception {
//given
EmailBean email = mock(EmailBean.class, new SelfReturningAnswer());
when(emails.memberRemoved(anyString(), anyString())).thenReturn(email);
//when
emailSender.onEvent(
new MemberRemovedEvent(
"admin",
new UserImpl("id", "email", null),
new OrganizationImpl("id", "/parent/name", "parent")));
//then
verify(emails).memberRemoved("name", "admin");
verify(email).withTo("email");
verify(mailSender).sendAsync(email);
}
@Test
public void shouldSendNotificationAboutOrganizationRenaming() throws Exception {
//given
MemberImpl member1 = new MemberImpl("user1", "org123", ImmutableList.of());
MemberImpl member2 = new MemberImpl("user2", "org123", ImmutableList.of());
doReturn(new Page<Member>(asList(member1, member2), 0, 2, 2))
.when(organizationManager)
.getMembers(anyString(), anyInt(), anyInt());
when(userManager.getById("user1"))
.thenReturn(new UserImpl("user1", "email1", null, null, emptyList()));
when(userManager.getById("user2"))
.thenReturn(new UserImpl("user2", "email2", null, null, emptyList()));
EmailBean email = new EmailBean().withBody("Org Remaned Notification");
when(emails.organizationRenamed(anyString(), anyString())).thenReturn(email);
//when
emailSender.onEvent(
new OrganizationRenamedEvent(
"admin",
"oldName",
"newName",
new OrganizationImpl("org123", "/parent/newName", "parent")));
//then
verify(emails).organizationRenamed("oldName", "newName");
verify(mailSender).sendAsync(new EmailBean(email).withTo("email1"));
verify(mailSender).sendAsync(new EmailBean(email).withTo("email2"));
}
@Test
public void
shouldDoNotBreakSendingOfNotificationAboutOrganizationRenamingWhenUnableToRetrieveAUser()
throws Exception {
//given
MemberImpl member1 = new MemberImpl("user1", "org123", emptyList());
MemberImpl member2 = new MemberImpl("user2", "org123", emptyList());
doReturn(new Page<Member>(asList(member1, member2), 0, 2, 2))
.when(organizationManager)
.getMembers(anyString(), anyInt(), anyInt());
when(userManager.getById("user1")).thenThrow(new NotFoundException(""));
when(userManager.getById("user2"))
.thenReturn(new UserImpl("user2", "email2", null, null, emptyList()));
EmailBean email = new EmailBean().withBody("Org Renamed Notification");
when(emails.organizationRenamed(anyString(), anyString())).thenReturn(email);
//when
emailSender.onEvent(
new OrganizationRenamedEvent(
"admin",
"oldName",
"newName",
new OrganizationImpl("org123", "/parent/newName", "parent")));
//then
verify(emails).organizationRenamed("oldName", "newName");
verify(mailSender).sendAsync(new EmailBean(email).withTo("email2"));
}
@Test
public void shouldSendNotificationAboutOrganizationRemoving() throws Exception {
//given
MemberImpl member1 = new MemberImpl("user1", "org123", emptyList());
MemberImpl member2 = new MemberImpl("user2", "org123", emptyList());
when(userManager.getById("user1"))
.thenReturn(new UserImpl("user1", "email1", null, null, emptyList()));
when(userManager.getById("user2"))
.thenReturn(new UserImpl("user2", "email2", null, null, emptyList()));
EmailBean email = new EmailBean().withBody("Org Removed Notification");
when(emails.organizationRemoved(anyString())).thenReturn(email);
//when
emailSender.onEvent(
new OrganizationRemovedEvent(
"admin", new OrganizationImpl("id", "/parent/q", "parent"), asList(member1, member2)));
//then
verify(emails).organizationRemoved("q");
verify(mailSender).sendAsync(new EmailBean(email).withTo("email1"));
verify(mailSender).sendAsync(new EmailBean(email).withTo("email2"));
}
@Test
public void
shouldDoNotBreakSendingOfNotificationAboutOrganizationRemovingWhenUnableToRetrieveAUser()
throws Exception {
//given
MemberImpl member1 = new MemberImpl("user1", "org123", emptyList());
MemberImpl member2 = new MemberImpl("user2", "org123", emptyList());
when(userManager.getById("user1")).thenThrow(new NotFoundException(""));
when(userManager.getById("user2"))
.thenReturn(new UserImpl("user2", "email2", null, null, emptyList()));
EmailBean email = new EmailBean().withBody("Org Removed Notification");
when(emails.organizationRemoved(anyString())).thenReturn(email);
//when
emailSender.onEvent(
new OrganizationRemovedEvent(
"admin", new OrganizationImpl("id", "/parent/q", "parent"), asList(member1, member2)));
//then
verify(emails).organizationRemoved("q");
verify(mailSender).sendAsync(new EmailBean(email).withTo("email2"));
}
}

View File

@ -52,6 +52,7 @@ public class KeycloakAuthenticationFilter extends AbstractKeycloakFilter {
private String authServerUrl;
private String realm;
private long allowedClockSkewSec;
private PublicKey publicKey = null;
private RequestTokenExtractor tokenExtractor;
@ -59,9 +60,11 @@ public class KeycloakAuthenticationFilter extends AbstractKeycloakFilter {
public KeycloakAuthenticationFilter(
@Named(KeycloakConstants.AUTH_SERVER_URL_SETTING) String authServerUrl,
@Named(KeycloakConstants.REALM_SETTING) String realm,
@Named(KeycloakConstants.ALLOWED_CLOCK_SKEW_SEC) long allowedClockSkewSec,
RequestTokenExtractor tokenExtractor) {
this.authServerUrl = authServerUrl;
this.realm = realm;
this.allowedClockSkewSec = allowedClockSkewSec;
this.tokenExtractor = tokenExtractor;
}
@ -85,7 +88,11 @@ public class KeycloakAuthenticationFilter extends AbstractKeycloakFilter {
Jws<Claims> jwt;
try {
jwt = Jwts.parser().setSigningKey(getJwtPublicKey(false)).parseClaimsJws(token);
jwt =
Jwts.parser()
.setAllowedClockSkewSeconds(allowedClockSkewSec)
.setSigningKey(getJwtPublicKey(false))
.parseClaimsJws(token);
LOG.debug("JWT = ", jwt);
//OK, we can trust this JWT
} catch (SignatureException
@ -96,7 +103,11 @@ public class KeycloakAuthenticationFilter extends AbstractKeycloakFilter {
LOG.error("Failed verifying the JWT token", e);
try {
LOG.info("Retrying after updating the public key", e);
jwt = Jwts.parser().setSigningKey(getJwtPublicKey(true)).parseClaimsJws(token);
jwt =
Jwts.parser()
.setAllowedClockSkewSeconds(allowedClockSkewSec)
.setSigningKey(getJwtPublicKey(true))
.parseClaimsJws(token);
LOG.debug("JWT = ", jwt);
//OK, we can trust this JWT
} catch (SignatureException

View File

@ -20,12 +20,11 @@ public class KeycloakServletModule extends ServletModule {
protected void configureServlets() {
bind(KeycloakAuthenticationFilter.class).in(Singleton.class);
// Not contains '/websocket', /docs/ (for swagger) and not ends with '/ws' or '/eventbus' or '/settings/'
filterRegex(
"^(?!.*(/websocket/?|/docs/))(?!.*(/ws/?|/eventbus/?|/settings/?|/api/oauth/callback/?)$).*")
// Not contains /docs/ (for swagger) and not ends with '/api/oauth/callback/' or
// '/keycloak/settings/'
filterRegex("^(?!.*(/docs/))(?!.*(/keycloak/settings/?|/api/oauth/callback/?)$).*")
.through(KeycloakAuthenticationFilter.class);
filterRegex(
"^(?!.*(/websocket/?|/docs/))(?!.*(/ws/?|/eventbus/?|/settings/?|/api/oauth/callback/?)$).*")
filterRegex("^(?!.*(/docs/))(?!.*(/keycloak/settings/?|/api/oauth/callback/?)$).*")
.through(KeycloakEnvironmentInitalizationFilter.class);
}
}

View File

@ -19,6 +19,8 @@ public class KeycloakConstants {
public static final String AUTH_SERVER_URL_SETTING = KEYCLOAK_SETTING_PREFIX + "auth_server_url";
public static final String REALM_SETTING = KEYCLOAK_SETTING_PREFIX + "realm";
public static final String CLIENT_ID_SETTING = KEYCLOAK_SETTING_PREFIX + "client_id";
public static final String ALLOWED_CLOCK_SKEW_SEC =
KEYCLOAK_SETTING_PREFIX + "allowed_clock_skew_sec";
public static final String OSO_ENDPOINT_SETTING = KEYCLOAK_SETTING_PREFIX + "oso.endpoint";
public static final String PROFILE_ENDPOINT_SETTING =

View File

@ -59,11 +59,15 @@ var ActivityTracker = new function () {
request.open("PUT", ActivityTracker.url, true);
var keycloak = window['_keycloak'];
if (keycloak && keycloak.token) {
var token = "Bearer " + keycloak.token;
request.setRequestHeader("Authorization", token);
if (keycloak) {
keycloak.updateToken(5)
.success(function (refreshed) {
var token = "Bearer " + keycloak.token;
request.setRequestHeader("Authorization", token);
request.send();
});
} else {
request.send();
}
request.send();
};
};

View File

@ -15,6 +15,7 @@ import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.List;
import org.eclipse.che.api.debug.shared.model.Location;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.editor.EditorAgent;
@ -23,6 +24,7 @@ import org.eclipse.che.ide.api.editor.text.TextPosition;
import org.eclipse.che.ide.api.editor.texteditor.TextEditor;
import org.eclipse.che.ide.api.resources.Project;
import org.eclipse.che.ide.api.resources.Resource;
import org.eclipse.che.ide.api.resources.SearchItemReference;
import org.eclipse.che.ide.api.resources.VirtualFile;
/** @author Anatoliy Bazko */
@ -197,7 +199,8 @@ public class DefaultDebuggerResourceHandler implements DebuggerResourceHandler {
.getWorkspaceRoot()
.search(location.getTarget(), "")
.then(
resources -> {
result -> {
List<SearchItemReference> resources = result.getItemReferences();
if (resources.isEmpty()) {
callback.onFailure(
new IllegalArgumentException(location.getTarget() + " not found."));

View File

@ -21,7 +21,6 @@ import javax.inject.Singleton;
import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator;
import org.eclipse.che.api.git.shared.Status;
import org.eclipse.che.api.project.shared.dto.event.GitChangeEventDto;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.editor.EditorAgent;
import org.eclipse.che.ide.api.parts.EditorMultiPartStack;
import org.eclipse.che.ide.api.parts.EditorTab;
@ -42,7 +41,6 @@ import org.eclipse.che.ide.ui.smartTree.Tree;
@Singleton
public class GitChangesHandler {
private final AppContext appContext;
private final Provider<EditorAgent> editorAgentProvider;
private final Provider<ProjectExplorerPresenter> projectExplorerPresenterProvider;
private final Provider<EditorMultiPartStack> multiPartStackProvider;
@ -50,11 +48,9 @@ public class GitChangesHandler {
@Inject
public GitChangesHandler(
RequestHandlerConfigurator configurator,
AppContext appContext,
Provider<EditorAgent> editorAgentProvider,
Provider<ProjectExplorerPresenter> projectExplorerPresenterProvider,
Provider<EditorMultiPartStack> multiPartStackProvider) {
this.appContext = appContext;
this.editorAgentProvider = editorAgentProvider;
this.projectExplorerPresenterProvider = projectExplorerPresenterProvider;
this.multiPartStackProvider = multiPartStackProvider;
@ -114,9 +110,6 @@ public class GitChangesHandler {
tab.setTitleColor(vcsStatus.getColor());
}
});
//TODO: temporary comment this line because its freeze browser for big project details see in che#6208
//appContext.getWorkspaceRoot().synchronize();
}
public void apply(String endpointId, Status status) {
@ -130,19 +123,21 @@ public class GitChangesHandler {
Resource resource = ((ResourceNode) node).getData();
File file = resource.asFile();
String nodeLocation = resource.getLocation().removeFirstSegments(1).toString();
if (status.getUntracked().contains(nodeLocation)
&& file.getVcsStatus() != UNTRACKED) {
file.setVcsStatus(UNTRACKED);
tree.refresh(node);
VcsStatus newVcsStatus;
if (status.getUntracked().contains(nodeLocation)) {
newVcsStatus = UNTRACKED;
} else if (status.getModified().contains(nodeLocation)
|| status.getChanged().contains(nodeLocation)) {
file.setVcsStatus(MODIFIED);
tree.refresh(node);
} else if (status.getAdded().contains(nodeLocation) && file.getVcsStatus() != ADDED) {
file.setVcsStatus(ADDED);
tree.refresh(node);
} else if (file.getVcsStatus() != NOT_MODIFIED) {
file.setVcsStatus(VcsStatus.NOT_MODIFIED);
newVcsStatus = MODIFIED;
} else if (status.getAdded().contains(nodeLocation)) {
newVcsStatus = ADDED;
} else {
newVcsStatus = NOT_MODIFIED;
}
if (file.getVcsStatus() != newVcsStatus) {
file.setVcsStatus(newVcsStatus);
tree.refresh(node);
}
});
@ -161,11 +156,9 @@ public class GitChangesHandler {
tab.setTitleColor(MODIFIED.getColor());
} else if (status.getAdded().contains(nodeLocation)) {
tab.setTitleColor(ADDED.getColor());
} else if (((File) tab.getFile()).getVcsStatus() != NOT_MODIFIED) {
} else {
tab.setTitleColor(NOT_MODIFIED.getColor());
}
});
appContext.getWorkspaceRoot().synchronize();
}
}

View File

@ -39,6 +39,7 @@ import org.eclipse.che.ide.ext.git.client.action.PushAction;
import org.eclipse.che.ide.ext.git.client.action.RemoveFromIndexAction;
import org.eclipse.che.ide.ext.git.client.action.ResetFilesAction;
import org.eclipse.che.ide.ext.git.client.action.ResetToCommitAction;
import org.eclipse.che.ide.ext.git.client.action.RevertCommitAction;
import org.eclipse.che.ide.ext.git.client.action.ShowBranchesAction;
import org.eclipse.che.ide.ext.git.client.action.ShowMergeAction;
import org.eclipse.che.ide.ext.git.client.action.ShowRemoteAction;
@ -74,6 +75,7 @@ public class GitExtension {
DeleteRepositoryAction deleteAction,
AddToIndexAction addToIndexAction,
ResetToCommitAction resetToCommitAction,
RevertCommitAction revertCommitAction,
RemoveFromIndexAction removeFromIndexAction,
CommitAction commitAction,
CheckoutReferenceAction checkoutReferenceAction,
@ -133,6 +135,8 @@ public class GitExtension {
commandGroup.add(compareGroup);
actionManager.registerAction("gitResetToCommit", resetToCommitAction);
commandGroup.add(resetToCommitAction);
actionManager.registerAction("gitRevertCommit", revertCommitAction);
commandGroup.add(revertCommitAction);
actionManager.registerAction("gitRemoveFromIndexCommit", removeFromIndexAction);
commandGroup.add(removeFromIndexAction);
actionManager.registerAction(GIT_SHOW_COMMIT_WINDOW, commitAction);

View File

@ -72,6 +72,9 @@ public interface GitLocalizationConstant extends Messages {
@Key("button.compare")
String buttonCompare();
@Key("button.revert")
String buttonRevert();
@Key("button.save_changes")
String buttonSaveChanges();
@ -223,6 +226,12 @@ public interface GitLocalizationConstant extends Messages {
@Key("messages.reset_fail")
String resetFail();
@Key("messages.revert_commit_failed")
String revertCommitFailed();
@Key("messages.revert_commit_successfully")
String revertCommitSuccessfully();
@Key("messages.status_failed")
String statusFailed();
@ -425,6 +434,37 @@ public interface GitLocalizationConstant extends Messages {
@Key("view.reset.hard.type.description")
String resetHardTypeDescription();
// Revert
@Key("view.revert.commit.title")
String revertCommitViewTitle();
@Key("view.revert.no_commit.type.title")
String revertNoCommitTypeTitle();
@Key("view.revert.no_commit.type.description")
String revertNoCommitTypeDescription();
@Key("view.revert.revision.table.id.title")
String viewRevertRevisionTableIdTitle();
@Key("view.revert.revision.table.date.title")
String viewRevertRevisionTableDateTitle();
@Key("view.revert.revision.table.author.title")
String viewRevertRevisionTableAuthorTitle();
@Key("view.revert.revision.table.comment.title")
String viewRevertRevisionTableCommentTitle();
@Key("reverted.commits")
String revertedCommits(String commits);
@Key("reverted.new.head")
String revertedNewHead(String newHead);
@Key("reverted.conflicts")
String revertedConflicts();
// Remove
@Key("view.remove_from_index.all")
String removeFromIndexAll();
@ -679,6 +719,12 @@ public interface GitLocalizationConstant extends Messages {
@Key("control.resetToCommit.prompt")
String resetToCommitControlPrompt();
@Key("control.revert.commit.title")
String revertCommitControlTitle();
@Key("control.revert.commit.prompt")
String revertCommitControlPrompt();
@Key("control.history.title")
String historyControlTitle();
@ -723,4 +769,10 @@ public interface GitLocalizationConstant extends Messages {
@Key("console.project.name")
String consoleProjectName(String projectName);
@Key("console.log.command.name")
String consoleLogCommandName();
@Key("console.revert.command.name")
String consoleRevertCommandName();
}

View File

@ -101,4 +101,7 @@ public interface GitResources extends ClientBundle {
@Source("controls/git-output-icon.svg")
SVGResource gitOutput();
@Source("controls/revert.svg")
SVGResource revert();
}

View File

@ -22,6 +22,7 @@ import org.eclipse.che.api.git.shared.PullResponse;
import org.eclipse.che.api.git.shared.PushResponse;
import org.eclipse.che.api.git.shared.Remote;
import org.eclipse.che.api.git.shared.ResetRequest;
import org.eclipse.che.api.git.shared.RevertResult;
import org.eclipse.che.api.git.shared.Revision;
import org.eclipse.che.api.git.shared.ShowFileContentResponse;
import org.eclipse.che.api.git.shared.Status;
@ -366,4 +367,12 @@ public interface GitServiceClient {
* @return the promise with success status
*/
Promise<Void> deleteRepository(Path project);
/**
* Revert the specified commit
*
* @param project project (root of GIT repository)
* @param commit commit to revert
*/
Promise<RevertResult> revert(Path project, String commit);
}

View File

@ -43,6 +43,8 @@ import org.eclipse.che.api.git.shared.PushResponse;
import org.eclipse.che.api.git.shared.Remote;
import org.eclipse.che.api.git.shared.RemoteAddRequest;
import org.eclipse.che.api.git.shared.ResetRequest;
import org.eclipse.che.api.git.shared.RevertRequest;
import org.eclipse.che.api.git.shared.RevertResult;
import org.eclipse.che.api.git.shared.Revision;
import org.eclipse.che.api.git.shared.ShowFileContentResponse;
import org.eclipse.che.api.git.shared.Status;
@ -86,6 +88,7 @@ public class GitServiceClientImpl implements GitServiceClient {
private static final String REMOVE = "/git/remove";
private static final String RESET = "/git/reset";
private static final String REPOSITORY = "/git/repository";
private static final String REVERT = "/git/revert";
/** Loader to be displayed. */
private final AsyncRequestLoader loader;
@ -506,4 +509,14 @@ public class GitServiceClientImpl implements GitServiceClient {
private String getWsAgentBaseUrl() {
return appContext.getWsAgentServerApiEndpoint();
}
@Override
public Promise<RevertResult> revert(Path project, String commit) {
RevertRequest revertRequest = dtoFactory.createDto(RevertRequest.class).withCommit(commit);
String url = getWsAgentBaseUrl() + REVERT + "?projectPath=" + project;
return asyncRequestFactory
.createPostRequest(url, revertRequest)
.loader(loader)
.send(dtoUnmarshallerFactory.newUnmarshaller(RevertResult.class));
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.ext.git.client.action;
import static com.google.common.base.Preconditions.checkState;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.resources.Project;
import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant;
import org.eclipse.che.ide.ext.git.client.GitResources;
import org.eclipse.che.ide.ext.git.client.revert.RevertCommitPresenter;
/** @author Dmitrii Bocharov (bdshadow) */
@Singleton
public class RevertCommitAction extends GitAction {
private final RevertCommitPresenter presenter;
@Inject
public RevertCommitAction(
RevertCommitPresenter presenter,
AppContext appContext,
GitLocalizationConstant constant,
GitResources resources) {
super(
constant.revertCommitControlTitle(),
constant.revertCommitControlPrompt(),
resources.revert(),
appContext);
this.presenter = presenter;
}
@Override
public void actionPerformed(ActionEvent e) {
final Project project = appContext.getRootProject();
checkState(project != null, "Null project occurred");
presenter.show(project);
}
}

View File

@ -62,6 +62,8 @@ import org.eclipse.che.ide.ext.git.client.reset.commit.ResetToCommitView;
import org.eclipse.che.ide.ext.git.client.reset.commit.ResetToCommitViewImpl;
import org.eclipse.che.ide.ext.git.client.reset.files.ResetFilesView;
import org.eclipse.che.ide.ext.git.client.reset.files.ResetFilesViewImpl;
import org.eclipse.che.ide.ext.git.client.revert.RevertCommitView;
import org.eclipse.che.ide.ext.git.client.revert.RevertCommitViewImpl;
/** @author Andrey Plotnikov */
@ExtensionGinModule
@ -81,6 +83,7 @@ public class GitGinModule extends AbstractGinModule {
bind(AddToIndexView.class).to(AddToIndexViewImpl.class).in(Singleton.class);
bind(ResetToCommitView.class).to(ResetToCommitViewImpl.class).in(Singleton.class);
bind(RevertCommitView.class).to(RevertCommitViewImpl.class).in(Singleton.class);
bind(RemoveFromIndexView.class).to(RemoveFromIndexViewImpl.class).in(Singleton.class);
bind(RevisionListView.class).to(RevisionListViewImpl.class).in(Singleton.class);
bind(CommitView.class).to(CommitViewImpl.class).in(Singleton.class);

View File

@ -0,0 +1,210 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.ext.git.client.revert;
import static org.eclipse.che.api.git.shared.Constants.DEFAULT_PAGE_SIZE;
import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
import static org.eclipse.che.ide.util.ExceptionUtils.getErrorCode;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.che.api.core.ErrorCodes;
import org.eclipse.che.api.git.shared.RevertResult;
import org.eclipse.che.api.git.shared.RevertResult.RevertStatus;
import org.eclipse.che.api.git.shared.Revision;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.notification.NotificationManager;
import org.eclipse.che.ide.api.resources.Project;
import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant;
import org.eclipse.che.ide.ext.git.client.GitServiceClient;
import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsole;
import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsoleFactory;
import org.eclipse.che.ide.ext.git.client.revert.RevertCommitView.ActionDelegate;
import org.eclipse.che.ide.processes.panel.ProcessesPanelPresenter;
import org.eclipse.che.ide.ui.dialogs.DialogFactory;
/**
* Presenter for reverting commits
*
* @author Dmitrii Bocharov (bdshadow)
*/
@Singleton
public class RevertCommitPresenter implements ActionDelegate {
private final RevertCommitView view;
private final GitServiceClient service;
private final DialogFactory dialogFactory;
private final GitLocalizationConstant constant;
private final GitOutputConsoleFactory gitOutputConsoleFactory;
private final ProcessesPanelPresenter consolesPanelPresenter;
private final AppContext appContext;
private final NotificationManager notificationManager;
private Revision selectedRevision;
private List<Revision> revisions;
private Project project;
private int skip;
@Inject
public RevertCommitPresenter(
RevertCommitView view,
GitServiceClient service,
DialogFactory dialogFactory,
GitLocalizationConstant constant,
GitOutputConsoleFactory gitOutputConsoleFactory,
ProcessesPanelPresenter consolesPanelPresenter,
AppContext appContext,
NotificationManager notificationManager) {
this.view = view;
this.service = service;
this.dialogFactory = dialogFactory;
this.constant = constant;
this.gitOutputConsoleFactory = gitOutputConsoleFactory;
this.consolesPanelPresenter = consolesPanelPresenter;
this.appContext = appContext;
this.notificationManager = notificationManager;
this.view.setDelegate(this);
}
public void show(final Project project) {
this.project = project;
this.skip = 0;
this.revisions = new ArrayList<>();
fetchRevisions();
}
@Override
public void onRevertClicked() {
this.view.close();
revert();
}
@Override
public void onCancelClicked() {
this.view.close();
}
@Override
public void onRevisionSelected(Revision revision) {
this.selectedRevision = revision;
view.setEnableRevertButton(true);
}
@Override
public void onScrolledToBottom() {
fetchRevisions();
}
private void fetchRevisions() {
service
.log(project.getLocation(), null, skip, DEFAULT_PAGE_SIZE, false)
.then(
log -> {
List<Revision> commits = log.getCommits();
if (!commits.isEmpty()) {
skip += commits.size();
revisions.addAll(commits);
view.setEnableRevertButton(selectedRevision != null);
view.setRevisions(revisions);
view.showDialog();
}
})
.catchError(
error -> {
if (getErrorCode(error.getCause()) == ErrorCodes.INIT_COMMIT_WAS_NOT_PERFORMED) {
dialogFactory
.createMessageDialog(
constant.revertCommitViewTitle(),
constant.initCommitWasNotPerformed(),
null)
.show();
return;
}
String errorMessage =
(error.getMessage() != null) ? error.getMessage() : constant.logFailed();
GitOutputConsole console =
gitOutputConsoleFactory.create(constant.consoleLogCommandName());
console.printError(errorMessage);
consolesPanelPresenter.addCommandOutput(console);
notificationManager.notify(constant.logFailed(), FAIL, FLOAT_MODE);
});
}
private void revert() {
final GitOutputConsole console =
gitOutputConsoleFactory.create(constant.consoleRevertCommandName());
service
.revert(project.getLocation(), selectedRevision.getId())
.then(
result -> {
console.print(formRevertMessage(result));
String conflictsMessage = formConflictsMessage(result.getConflicts());
if (!conflictsMessage.isEmpty()) {
console.printError(conflictsMessage);
}
consolesPanelPresenter.addCommandOutput(console);
notificationManager.notify(constant.revertCommitSuccessfully());
project.synchronize();
})
.catchError(
error -> {
String errorMessage =
(error.getMessage() != null) ? error.getMessage() : constant.revertCommitFailed();
console.printError(errorMessage);
consolesPanelPresenter.addCommandOutput(console);
notificationManager.notify(constant.revertCommitFailed(), FAIL, FLOAT_MODE);
});
}
private String formRevertMessage(RevertResult revertResult) {
StringBuilder message = new StringBuilder();
if (revertResult.getNewHead() != null) {
message.append(constant.revertedNewHead(revertResult.getNewHead()));
}
List<String> commits = revertResult.getRevertedCommits();
if (commits != null && commits.size() > 0) {
StringBuilder revertedCommits = new StringBuilder();
for (String commit : commits) {
revertedCommits.append("- " + commit);
}
message.append(
revertedCommits.length() > 0
? " " + constant.revertedCommits(revertedCommits.toString()) + "\n"
: "\n");
}
return message.toString();
}
private String formConflictsMessage(Map<String, RevertStatus> conflicts) {
if (conflicts != null && conflicts.size() > 0) {
StringBuilder conflictsMessage = new StringBuilder(constant.revertedConflicts() + "\n");
for (Map.Entry<String, RevertStatus> conflictEntry : conflicts.entrySet()) {
conflictsMessage
.append(" ")
.append(conflictEntry.getKey())
.append(" - ")
.append(conflictEntry.getValue().getValue())
.append("\n");
}
return conflictsMessage.toString();
}
return "";
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.ext.git.client.revert;
import java.util.List;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.git.shared.Revision;
import org.eclipse.che.ide.api.mvp.View;
/**
* The view of {@link org.eclipse.che.ide.ext.git.client.revert.RevertCommitPresenter}
*
* @author dbocharo
*/
public interface RevertCommitView extends View<RevertCommitView.ActionDelegate> {
public interface ActionDelegate {
void onRevertClicked();
void onCancelClicked();
void onRevisionSelected(@NotNull Revision revision);
void onScrolledToBottom();
}
/**
* Set available revisions.
*
* @param revisions git revisions
*/
void setRevisions(@NotNull List<Revision> revisions);
/**
* Change the enable state of the revert button.
*
* @param enabled <code>true</code> to enable the button, <code>false</code> to disable it
*/
void setEnableRevertButton(boolean enabled);
/** Close dialog. */
void close();
/** Show dialog. */
void showDialog();
}

View File

@ -0,0 +1,197 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.ext.git.client.revert;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.List;
import org.eclipse.che.api.git.shared.Revision;
import org.eclipse.che.ide.ext.git.client.DateTimeFormatter;
import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant;
import org.eclipse.che.ide.ext.git.client.GitResources;
import org.eclipse.che.ide.ui.window.Window;
/**
* The implementation of {@link RevertCommitView}.
*
* @author Dmitrii Bocharov (bdshadow)
*/
@Singleton
public class RevertCommitViewImpl extends Window implements RevertCommitView {
interface RevertCommitViewImplUiBinder extends UiBinder<Widget, RevertCommitViewImpl> {}
private static RevertCommitViewImplUiBinder uiBinder =
GWT.create(RevertCommitViewImplUiBinder.class);
@UiField ScrollPanel revisionsPanel;
Button btnRevert;
Button btnCancel;
@UiField(provided = true)
final GitResources res;
@UiField(provided = true)
final GitLocalizationConstant locale;
private ActionDelegate delegate;
private CellTable<Revision> revisions;
private SingleSelectionModel<Revision> selectionModel;
private final DateTimeFormatter dateTimeFormatter;
@Inject
protected RevertCommitViewImpl(
GitResources resources,
GitLocalizationConstant locale,
org.eclipse.che.ide.Resources coreRes,
DateTimeFormatter dateTimeFormatter) {
this.res = resources;
this.locale = locale;
this.dateTimeFormatter = dateTimeFormatter;
this.ensureDebugId("git-revert-window");
Widget widget = uiBinder.createAndBindUi(this);
this.setTitle(locale.revertCommitViewTitle());
this.setWidget(widget);
createRevisionsTable(coreRes);
createButtons();
}
@Override
public void setDelegate(ActionDelegate delegate) {
this.delegate = delegate;
}
@Override
public void setRevisions(List<Revision> revisions) {
this.revisions.setRowData(revisions);
}
@Override
public void setEnableRevertButton(boolean enabled) {
btnRevert.setEnabled(enabled);
}
@Override
public void close() {
onClose();
}
@Override
protected void onClose() {
selectionModel.clear();
super.onClose();
}
@Override
public void showDialog() {
this.show();
}
private void createRevisionsTable(org.eclipse.che.ide.Resources coreRes) {
Column<Revision, String> idColumn =
new Column<Revision, String>(new TextCell()) {
@Override
public String getValue(Revision revision) {
return revision.getId().substring(0, 8);
}
};
Column<Revision, String> dateColumn =
new Column<Revision, String>(new TextCell()) {
@Override
public String getValue(Revision revision) {
return dateTimeFormatter.getFormattedDate(revision.getCommitTime());
}
};
Column<Revision, String> authorColumn =
new Column<Revision, String>(new TextCell()) {
@Override
public String getValue(Revision revision) {
return revision.getCommitter().getName();
}
};
Column<Revision, String> commentColumn =
new Column<Revision, String>(new TextCell()) {
@Override
public String getValue(Revision revision) {
return revision.getMessage().substring(0, 50);
}
};
revisions = new CellTable<>(15, coreRes);
revisions.setWidth("100%");
revisions.addColumn(idColumn, locale.viewRevertRevisionTableIdTitle());
revisions.setColumnWidth(idColumn, "10%");
revisions.addColumn(dateColumn, locale.viewRevertRevisionTableDateTitle());
revisions.setColumnWidth(dateColumn, "20%");
revisions.addColumn(authorColumn, locale.viewRevertRevisionTableAuthorTitle());
revisions.setColumnWidth(authorColumn, "20%");
revisions.addColumn(commentColumn, locale.viewRevertRevisionTableCommentTitle());
revisions.setColumnWidth(commentColumn, "50%");
this.selectionModel = new SingleSelectionModel<>();
this.selectionModel.addSelectionChangeHandler(
event -> {
Revision selectedObject = selectionModel.getSelectedObject();
delegate.onRevisionSelected(selectedObject);
});
revisions.setSelectionModel(this.selectionModel);
this.revisionsPanel.add(revisions);
}
private void createButtons() {
btnCancel =
createButton(
locale.buttonCancel(),
"git-revert-cancel",
event -> {
delegate.onCancelClicked();
});
addButtonToFooter(btnCancel);
btnRevert =
createButton(
locale.buttonRevert(),
"git-revert",
event -> {
delegate.onRevertClicked();
});
addButtonToFooter(btnRevert);
}
@UiHandler("revisionsPanel")
public void onPanelScrolled(ScrollEvent ignored) {
// We cannot rely on exact equality of scroll positions because GWT sometimes round such values
// and it is possible that the actual max scroll position is a pixel less then declared.
if (revisionsPanel.getMaximumVerticalScrollPosition()
- revisionsPanel.getVerticalScrollPosition()
<= 1) {
// to avoid autoscrolling to selected item
revisionsPanel.getElement().focus();
delegate.onScrolledToBottom();
}
}
}

View File

@ -0,0 +1,23 @@
<!--
Copyright (c) 2012-2017 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<ui:with field='locale' type='org.eclipse.che.ide.ext.git.client.GitLocalizationConstant'/>
<ui:with field='res' type='org.eclipse.che.ide.ext.git.client.GitResources'/>
<g:DockLayoutPanel unit="PX" width="750px" height="400px" debugId="git-revert-mainForm">
<g:center>
<g:ScrollPanel ui:field="revisionsPanel" debugId="revert-commit-panel"/>
</g:center>
</g:DockLayoutPanel>
</ui:UiBinder>

View File

@ -21,6 +21,7 @@ button.refresh=Refresh
button.commit=Commit
button.merge=Merge
button.compare=Compare
button.revert=Revert
button.reset=Reset
button.remove=Remove
button.ok=OK
@ -81,6 +82,8 @@ messages.reset_files_successfully=Index successfully reset.
messages.nothing_to_reset=Nothing to reset.
messages.reset_successfully=Index reset
messages.reset_fail=Failed to reset index
messages.revert_commit_failed=Failed to revert commit
messages.revert_commit_successfully=Commit reverted successfully
messages.status_failed=Failed to get git status
messages.selected_remote_fail=Remote repository must be selected
messages.delete_remote_repository.title=Remove Remote Repository
@ -88,7 +91,7 @@ messages.delete_remote_repository.question=Are you sure you want to delete <b>{0
messages.delete_repository.title=Delete Git repository
messages.delete_repository.question=Are you sure you want to delete <b>{0}</b>?
messages.delete_success=Git repository deleted
messages.compare_save.title=Git compare
messages.compare_save.title=Git compare
messages.compare_save.question=Would you like to save changes?
messages.notAuthorizedTitle=You are not authorized to perform this operation
messages.notAuthorizedContent=This may be because you have not setup SSH keys or oAuth.\
@ -139,6 +142,19 @@ view.reset.mixed.type.title=mixed
view.reset.mixed.type.description=(Change the ref and the index, the workdir is not changed.)
view.reset.hard.type.title=hard
view.reset.hard.type.description=(Change the ref, the index and the workdir)
#Revert
view.revert.commit.title=Revert commit
view.revert.no_commit.type.title=no-commit
view.revert.no_commit.type.description=(do NOT create an automatic commit with log message stating which commit was reverted)
view.revert.revision.table.id.title=Id
view.revert.revision.table.date.title=Date
view.revert.revision.table.author.title=Author
view.revert.revision.table.comment.title=Comment
reverted.commits=Reverted commits: {0}
reverted.new.head=New HEAD: {0}
reverted.conflicts=Conflicts detected:
#Remove
view.remove_from_index.all=Are you sure you want to remove selected items from index?
view.remove_from_index.only=Remove only from index (file will be untouched)
@ -259,6 +275,8 @@ control.resetFiles.title=Reset Index...
control.resetFiles.prompt=Reset Index...
control.resetToCommit.title=Reset...
control.resetToCommit.prompt=Reset to Revision...
control.revert.commit.title=Revert commit...
control.revert.commit.prompt=Revert commit...
control.history.title=Show History...
control.history.prompt=Show History...
control.status.title=Status
@ -277,6 +295,8 @@ committer.title = Committer
console.tooltip.scroll=Click this button to navigate to the bottom of the console.
console.tooltip.clear=Click this button to remove all text from the console.
console.project.name=Project name: {0}
console.log.command.name=Git log
console.revert.command.name=Git revert commit
commited=Commited
failed.to.delete.repository=Failed to delete repository

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2012-2017 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<!-- The icon can be used freely in both personal and commercial projects with no attribution required, but always appreciated.
You may NOT sub-license, resell, rent, redistribute or otherwise transfer the icon without express written permission from iconmonstr.com -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<path id="x-mark-4-icon" d="M462,256c0,113.771-92.229,206-206,206S50,369.771,50,256S142.229,50,256,50S462,142.229,462,256z
M422,256c0-91.755-74.258-166-166-166c-91.755,0-166,74.259-166,166c0,91.755,74.258,166,166,166C347.755,422,422,347.741,422,256z
M325.329,362.49l-67.327-67.324l-67.329,67.332l-36.164-36.186l67.314-67.322l-67.321-67.317l36.185-36.164l67.31,67.301
l67.3-67.309l36.193,36.17l-67.312,67.315l67.32,67.31L325.329,362.49z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.ext.git.client.revert.commit;
import static java.util.Collections.singletonList;
import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import org.eclipse.che.api.git.shared.LogResponse;
import org.eclipse.che.api.git.shared.RevertResult;
import org.eclipse.che.api.git.shared.Revision;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.ide.ext.git.client.BaseTest;
import org.eclipse.che.ide.ext.git.client.revert.RevertCommitPresenter;
import org.eclipse.che.ide.ext.git.client.revert.RevertCommitView;
import org.eclipse.che.ide.resource.Path;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
public class RevertCommitPresenterTest extends BaseTest {
@Mock private RevertCommitView view;
@InjectMocks private RevertCommitPresenter presenter;
@Mock private Promise<RevertResult> revertPromise;
@Captor private ArgumentCaptor<Operation<RevertResult>> revertCaptor;
@Override
public void disarm() {
super.disarm();
when(service.log(any(Path.class), any(Path[].class), anyInt(), anyInt(), anyBoolean()))
.thenReturn(logPromise);
when(logPromise.then(any(Operation.class))).thenReturn(logPromise);
when(logPromise.catchError(any(Operation.class))).thenReturn(logPromise);
when(service.revert(any(Path.class), anyString())).thenReturn(revertPromise);
when(revertPromise.then(any(Operation.class))).thenReturn(revertPromise);
when(revertPromise.catchError(any(Operation.class))).thenReturn(revertPromise);
}
@Test
public void shouldGetCommitsAndShowDialog() throws Exception {
LogResponse response = mock(LogResponse.class);
List<Revision> revisions = singletonList(mock(Revision.class));
when(response.getCommits()).thenReturn(revisions);
presenter.show(project);
verify(logPromise).then(logCaptor.capture());
logCaptor.getValue().apply(response);
verify(view).setRevisions(revisions);
verify(view).showDialog();
}
@Test
public void shouldShowNotificationOnGetCommitsError() throws Exception {
when(constant.logFailed()).thenReturn("error");
presenter.show(project);
verify(logPromise).catchError(promiseErrorCaptor.capture());
promiseErrorCaptor.getValue().apply(mock(PromiseError.class));
verify(notificationManager).notify(eq("error"), eq(FAIL), eq(FLOAT_MODE));
}
@Test
public void shouldNotifyOnSuccessfulRevert() throws Exception {
RevertResult revertResult = mock(RevertResult.class);
when(revertResult.getNewHead()).thenReturn("1234");
when(revertResult.getRevertedCommits()).thenReturn(Collections.emptyList());
when(revertResult.getConflicts()).thenReturn(Collections.emptyMap());
Revision selectedRevision = mock(Revision.class);
when(selectedRevision.getId()).thenReturn("1234");
presenter.show(project);
presenter.onRevisionSelected(selectedRevision);
presenter.onRevertClicked();
verify(revertPromise).then(revertCaptor.capture());
revertCaptor.getValue().apply(revertResult);
verify(notificationManager).notify(constant.revertCommitSuccessfully());
}
@Test
public void shouldNotifyOnFailedRevert() throws Exception {
RevertResult revertResult = mock(RevertResult.class);
when(revertResult.getNewHead()).thenReturn("1234");
when(revertResult.getRevertedCommits()).thenReturn(Collections.emptyList());
when(revertResult.getConflicts()).thenReturn(Collections.emptyMap());
Revision selectedRevision = mock(Revision.class);
when(selectedRevision.getId()).thenReturn("1234");
presenter.show(project);
presenter.onRevisionSelected(selectedRevision);
presenter.onRevertClicked();
verify(revertPromise).catchError(promiseErrorCaptor.capture());
promiseErrorCaptor.getValue().apply(mock(PromiseError.class));
verify(notificationManager).notify(eq(constant.revertCommitFailed()), eq(FAIL), eq(FLOAT_MODE));
}
}

Some files were not shown because too many files have changed in this diff Show More