support for managing external managed groups

This commit is contained in:
Sebastian Sdorra
2019-03-21 09:58:36 +01:00
parent 1179fd37fa
commit 3e9f59ef47
9 changed files with 175 additions and 87 deletions

View File

@@ -50,6 +50,7 @@ import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
//~--- JDK imports ------------------------------------------------------------
@@ -195,6 +196,7 @@ public class Group extends BasicPropertiesAware
group.setMembers(members);
group.setType(type);
group.setDescription(description);
group.setExternal(true);
}
/**
@@ -224,6 +226,7 @@ public class Group extends BasicPropertiesAware
&& Objects.equal(description, other.description)
&& Objects.equal(members, other.members)
&& Objects.equal(type, other.type)
&& Objects.equal(external, other.external)
&& Objects.equal(creationDate, other.creationDate)
&& Objects.equal(lastModified, other.lastModified)
&& Objects.equal(properties, other.properties);
@@ -270,6 +273,7 @@ public class Group extends BasicPropertiesAware
.add("description", description)
.add("members", members)
.add("type", type)
.add("external", external)
.add("creationDate", creationDate)
.add("lastModified", lastModified)
.add("properties", properties)
@@ -339,8 +343,9 @@ public class Group extends BasicPropertiesAware
*/
public List<String> getMembers()
{
if (members == null)
{
if (external) {
return Collections.emptyList();
} else if (members == null) {
members = Lists.newArrayList();
}
@@ -370,6 +375,15 @@ public class Group extends BasicPropertiesAware
return type;
}
/**
* Returns {@code true} if the members of the groups managed external of scm-manager.
*
* @return {@code true} if the group is an external group
*/
public boolean isExternal() {
return external;
}
/**
* Returns true if the member is a member of this group.
*
@@ -463,8 +477,21 @@ public class Group extends BasicPropertiesAware
this.type = type;
}
/**
* {@code true} to mark the group as external.
*
* @param {@code true} for a external group
*/
public void setExternal(boolean external)
{
this.external = external;
}
//~--- fields ---------------------------------------------------------------
/** external group */
private boolean external = false;
/** timestamp of the creation date of this group */
private Long creationDate;

View File

@@ -10,6 +10,7 @@ export type Group = Collection & {
name: string,
description: string,
type: string,
external: boolean,
members: string[],
_embedded: {
members: Member[]

View File

@@ -5,6 +5,7 @@
"creationDate": "Erstellt",
"lastModified": "Zuletzt bearbeitet",
"type": "Typ",
"external": "Extern",
"members": "Mitglieder"
},
"groups": {
@@ -49,13 +50,15 @@
},
"groupForm": {
"subtitle": "Gruppe bearbeiten",
"externalSubtitle": "Externe Gruppe bearbeiten",
"submit": "Speichern",
"nameError": "Name ist ungültig",
"descriptionError": "Beschreibung ist ungültig",
"help": {
"nameHelpText": "Eindeutiger Name der Gruppe",
"descriptionHelpText": "Eine kurze Beschreibung der Gruppe",
"memberHelpText": "Benutzername des Mitglieds der Gruppe"
"memberHelpText": "Benutzername des Mitglieds der Gruppe",
"externalHelpText": "Mitglieder dieser Gruppe werden von einem externen System wie z.B.: einem LDAP-Server verwaltet"
}
},
"deleteGroup": {

View File

@@ -5,6 +5,7 @@
"creationDate": "Creation Date",
"lastModified": "Last Modified",
"type": "Type",
"external": "External",
"members": "Members"
},
"groups": {
@@ -49,13 +50,15 @@
},
"groupForm": {
"subtitle": "Edit Group",
"externalSubtitle": "Edit external group",
"submit": "Submit",
"nameError": "Group name is invalid",
"descriptionError": "Description is invalid",
"help": {
"nameHelpText": "Unique name of the group",
"descriptionHelpText": "A short description of the group",
"memberHelpText": "Usernames of the group members"
"memberHelpText": "Usernames of the group members",
"externalHelpText": "Members are managed by an external system such as LDAP"
}
},
"deleteGroup": {

View File

@@ -8,7 +8,8 @@ import {
MemberNameTable,
InputField,
SubmitButton,
Textarea
Textarea,
Checkbox
} from "@scm-manager/ui-components";
import type { Group, SelectValue } from "@scm-manager/ui-types";
@@ -67,16 +68,68 @@ class GroupForm extends React.Component<Props, State> {
submit = (event: Event) => {
event.preventDefault();
if (this.isValid()) {
this.props.submitForm(this.state.group);
const { group } = this.state;
if (group.external) {
group.members = [];
}
this.props.submitForm(group);
}
};
renderMemberfields = (group: Group) => {
if (group.external) {
return null;
}
const { loadUserSuggestions, t } = this.props;
return (
<>
<LabelWithHelpIcon
label={t("group.members")}
helpText={t("groupForm.help.memberHelpText")}
/>
<MemberNameTable
members={group.members}
memberListChanged={this.memberListChanged}
/>
<AutocompleteAddEntryToTableField
addEntry={this.addMember}
disabled={false}
buttonLabel={t("add-member-button.label")}
fieldLabel={t("add-member-textfield.label")}
errorMessage={t("add-member-textfield.error")}
loadSuggestions={loadUserSuggestions}
placeholder={t("add-member-autocomplete.placeholder")}
loadingMessage={t("add-member-autocomplete.loading")}
noOptionsMessage={t("add-member-autocomplete.no-options")}
/>
</>
);
};
renderExternalField = (group: Group) => {
const { t } = this.props;
if (this.isExistingGroup()) {
return null;
}
return (
<Checkbox
label={t("group.external")}
checked={group.external}
helpText={t("groupForm.help.externalHelpText")}
onChange={this.handleExternalChange}
/>
);
};
isExistingGroup = () => !! this.props.group;
render() {
const { loading, t } = this.props;
const { group } = this.state;
let nameField = null;
let subtitle = null;
if (!this.props.group) {
if (!this.isExistingGroup()) {
// create new group
nameField = (
<InputField
@@ -88,8 +141,9 @@ class GroupForm extends React.Component<Props, State> {
helpText={t("groupForm.help.nameHelpText")}
/>
);
} else if (group.external) {
subtitle = <Subtitle subtitle={t("groupForm.externalSubtitle")} />;
} else {
// edit existing group
subtitle = <Subtitle subtitle={t("groupForm.subtitle")} />;
}
@@ -106,26 +160,8 @@ class GroupForm extends React.Component<Props, State> {
validationError={false}
helpText={t("groupForm.help.descriptionHelpText")}
/>
<LabelWithHelpIcon
label={t("group.members")}
helpText={t("groupForm.help.memberHelpText")}
/>
<MemberNameTable
members={group.members}
memberListChanged={this.memberListChanged}
/>
<AutocompleteAddEntryToTableField
addEntry={this.addMember}
disabled={false}
buttonLabel={t("add-member-button.label")}
fieldLabel={t("add-member-textfield.label")}
errorMessage={t("add-member-textfield.error")}
loadSuggestions={this.props.loadUserSuggestions}
placeholder={t("add-member-autocomplete.placeholder")}
loadingMessage={t("add-member-autocomplete.loading")}
noOptionsMessage={t("add-member-autocomplete.no-options")}
/>
{this.renderExternalField(group)}
{this.renderMemberfields(group)}
<SubmitButton
disabled={!this.isValid()}
label={t("groupForm.submit")}
@@ -176,6 +212,12 @@ class GroupForm extends React.Component<Props, State> {
group: { ...this.state.group, description }
});
};
handleExternalChange = (external: boolean) => {
this.setState({
group: { ...this.state.group, external }
});
};
}
export default translate("groups")(GroupForm);

View File

@@ -2,7 +2,7 @@
import React from "react";
import type { Group } from "@scm-manager/ui-types";
import GroupMember from "./GroupMember";
import { DateFromNow } from "@scm-manager/ui-components";
import { DateFromNow, Checkbox } from "@scm-manager/ui-components";
import { translate } from "react-i18next";
import injectSheet from "react-jss";
@@ -34,6 +34,12 @@ class Details extends React.Component<Props> {
<th>{t("group.description")}</th>
<td>{group.description}</td>
</tr>
<tr>
<th>{t("group.external")}</th>
<td>
<Checkbox checked={group.external} />
</td>
</tr>
<tr>
<th>{t("group.type")}</th>
<td>{group.type}</td>

View File

@@ -1,25 +1,29 @@
// @flow
import React from "react";
import { Link } from "react-router-dom";
import type { Group } from "@scm-manager/ui-types";
type Props = {
group: Group
};
export default class GroupRow extends React.Component<Props> {
renderLink(to: string, label: string) {
return <Link to={to}>{label}</Link>;
}
render() {
const { group } = this.props;
const to = `/group/${group.name}`;
return (
<tr>
<td>{this.renderLink(to, group.name)}</td>
<td className="is-hidden-mobile">{group.description}</td>
</tr>
);
}
}
// @flow
import React from "react";
import { Link } from "react-router-dom";
import type { Group } from "@scm-manager/ui-types";
import { Checkbox } from "@scm-manager/ui-components"
type Props = {
group: Group
};
export default class GroupRow extends React.Component<Props> {
renderLink(to: string, label: string) {
return <Link to={to}>{label}</Link>;
}
render() {
const { group } = this.props;
const to = `/group/${group.name}`;
return (
<tr>
<td>{this.renderLink(to, group.name)}</td>
<td className="is-hidden-mobile">{group.description}</td>
<td>
<Checkbox checked={group.external} />
</td>
</tr>
);
}
}

View File

@@ -1,33 +1,34 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import GroupRow from "./GroupRow";
import type { Group } from "@scm-manager/ui-types";
type Props = {
t: string => string,
groups: Group[]
};
class GroupTable extends React.Component<Props> {
render() {
const { groups, t } = this.props;
return (
<table className="card-table table is-hoverable is-fullwidth">
<thead>
<tr>
<th>{t("group.name")}</th>
<th className="is-hidden-mobile">{t("group.description")}</th>
</tr>
</thead>
<tbody>
{groups.map((group, index) => {
return <GroupRow key={index} group={group} />;
})}
</tbody>
</table>
);
}
}
export default translate("groups")(GroupTable);
// @flow
import React from "react";
import { translate } from "react-i18next";
import GroupRow from "./GroupRow";
import type { Group } from "@scm-manager/ui-types";
type Props = {
t: string => string,
groups: Group[]
};
class GroupTable extends React.Component<Props> {
render() {
const { groups, t } = this.props;
return (
<table className="card-table table is-hoverable is-fullwidth">
<thead>
<tr>
<th>{t("group.name")}</th>
<th className="is-hidden-mobile">{t("group.description")}</th>
<th>{t("group.external")}</th>
</tr>
</thead>
<tbody>
{groups.map((group, index) => {
return <GroupRow key={index} group={group} />;
})}
</tbody>
</table>
);
}
}
export default translate("groups")(GroupTable);

View File

@@ -27,6 +27,7 @@ public class GroupDto extends HalRepresentation {
private String type;
private Map<String, String> properties;
private List<String> members;
private boolean external;
GroupDto(Links links, Embedded embedded) {
super(links, embedded);