Merge branch 'che-multiuser' into spi
commit
4bc18519db
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ declare namespace che {
|
|||
fetchSystemPermissions(): ng.IPromise<any>;
|
||||
getSystemPermissions(): ISystemPermissions;
|
||||
getUserServices(): IUserServices;
|
||||
getPermissionsServicePath(): string;
|
||||
}
|
||||
|
||||
export interface ICheTeam {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
tests
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
########################################################################################
|
||||
|
|
|
|||
|
|
@ -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}')
|
||||
|
|
|
|||
|
|
@ -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 "")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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}.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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."));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,4 +101,7 @@ public interface GitResources extends ClientBundle {
|
|||
|
||||
@Source("controls/git-output-icon.svg")
|
||||
SVGResource gitOutput();
|
||||
|
||||
@Source("controls/revert.svg")
|
||||
SVGResource revert();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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
Loading…
Reference in New Issue