• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 14191949962

01 Apr 2025 09:19AM UTC coverage: 76.988% (+0.004%) from 76.984%
14191949962

Pull #10973

github

web-flow
Merge 3e6622b42 into 8a82ba272
Pull Request #10973: Fix #10820 Refactor Admin Ui section like HomePage using ResourceGrid Plugin.

30984 of 48230 branches covered (64.24%)

4 of 6 new or added lines in 2 files covered. (66.67%)

5 existing lines in 2 files now uncovered.

38521 of 50035 relevant lines covered (76.99%)

35.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.19
/web/client/components/manager/users/GroupDialog.jsx
1
/**
2
 * Copyright 2016, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8

9
// replacement of MapStore2/web/client/components/manager/users/GroupDialog.jsx
10

11
import React from 'react';
12
import { Alert, Tabs, Tab, Glyphicon, FormControl, FormGroup, ControlLabel } from 'react-bootstrap';
13
import PropTypes from 'prop-types';
14
import assign from 'object-assign';
15
import Spinner from 'react-spinkit';
16
import { findIndex, castArray } from 'lodash';
17

18
import Button from '../../../components/misc/Button';
19
import UsersTable from './UsersTable';
20
import Dialog from '../../../components/misc/Dialog';
21
import Message from '../../../components/I18N/Message';
22
import PagedCombobox from '../../misc/combobox/PagedCombobox';
23
import CloseConfirmButton from './CloseConfirmButton';
24
import './style/userdialog.css';
25

26
import controls from './AttributeControls';
27

28

29
const PAGINATION_LIMIT = 5;
1✔
30

31
/**
32
 * A Modal window to show password reset form
33
 */
34
class GroupDialog extends React.Component {
35
    static propTypes = {
1✔
36
        // props
37
        group: PropTypes.object,
38
        users: PropTypes.array,
39
        availableUsers: PropTypes.array,
40
        availableUsersCount: PropTypes.number,
41
        searchUsers: PropTypes.func,
42
        availableUsersLoading: PropTypes.bool,
43
        showMembersTab: PropTypes.bool,
44
        showAttributesTab: PropTypes.bool,
45
        attributeFields: PropTypes.array,
46
        show: PropTypes.bool,
47
        onClose: PropTypes.func,
48
        onChange: PropTypes.func,
49
        onSave: PropTypes.func,
50
        onRefresh: PropTypes.func,
51
        modal: PropTypes.bool,
52
        closeGlyph: PropTypes.string,
53
        style: PropTypes.object,
54
        buttonSize: PropTypes.string,
55
        descLimit: PropTypes.number,
56
        nameLimit: PropTypes.number,
57
        inputStyle: PropTypes.object
58
    };
59

60
    static contextTypes = {
1✔
61
        intl: PropTypes.object
62
    };
63

64
    static defaultProps = {
1✔
65
        group: {},
66
        availableUsers: [],
67
        attributeFields: [{
68
            name: 'notes',
69
            controlType: "text"
70
        }],
71
        searchUsers: () => {},
72
        showMembersTab: true,
73
        showAttributesTab: true,
74
        onClose: () => {},
75
        onChange: () => {},
76
        onSave: () => {},
77
        onRefresh: () => {},
78
        options: {},
79
        useModal: true,
80
        closeGlyph: "",
81
        descLimit: 255,
82
        nameLimit: 255,
83
        style: {},
84
        buttonSize: "small",
85
        includeCloseButton: true,
86
        inputStyle: {
87
            height: "32px",
88
            width: "260px",
89
            marginTop: "3px",
90
            marginBottom: "20px",
91
            padding: "5px",
92
            border: "1px solid #078AA3"
93
        }
94
    };
95

96
    state = {
16✔
97
        key: 1,
98
        openSelectMember: false,
99
        selectedMember: ''
100
    };
101

102
    componentDidMount() {
103
        this.selectMemberPage = 0;
16✔
104
        this.searchUsers();
16✔
105
    }
106
    // Only to keep the selected button, not for the modal window
107

108
    getCurrentGroupMembers = () => {
16✔
109
        return this.props.group && (this.props.group.newUsers || this.props.group.users) || [];
30!
110
    };
111

112
    renderGeneral = () => {
16✔
113
        return (<div style={{clear: "both", marginTop: "10px"}}>
22✔
114
            <FormGroup>
115
                <ControlLabel><Message msgId="usergroups.groupName"/>{' '}<span style={{ fontWeight: 'bold' }}>*</span></ControlLabel>
116
                <FormControl ref="groupName"
117
                    key="groupName"
118
                    type="text"
119
                    name="groupName"
120
                    readOnly={this.props.group && this.props.group.id}
44✔
121
                    style={this.props.inputStyle}
122
                    onChange={this.handleChange}
123
                    maxLength={this.props.nameLimit}
124
                    value={this.props.group && this.props.group.groupName || ""}/>
44!
125
            </FormGroup>
126
            <FormGroup>
127
                <ControlLabel><Message msgId="usergroups.groupDescription"/></ControlLabel>
128
                <FormControl componentClass="textarea"
129
                    ref="description"
130
                    key="description"
131
                    name="description"
132
                    maxLength={this.props.descLimit}
133
                    style={this.props.inputStyle}
134
                    onChange={this.handleChange}
135
                    value={this.props.group && this.props.group.description || ""}/>
44!
136
            </FormGroup>
137
            <div style={{ fontStyle: 'italic' }}><Message msgId="users.requiredFiedsMessage"/></div>
138
        </div>);
139
    };
140

141
    renderSaveButtonContent = () => {
16✔
142
        let defaultMessage = this.props.group && this.props.group.id ? <Message key="text" msgId="usergroups.saveGroup"/> : <Message key="text" msgId="usergroups.createGroup" />;
22!
143
        let messages = {
22✔
144
            error: defaultMessage,
145
            success: defaultMessage,
146
            modified: defaultMessage,
147
            save: <Message key="text" msgId="usergroups.saveGroup"/>,
148
            saving: <Message key="text" msgId="usergroups.savingGroup" />,
149
            saved: <Message key="text" msgId="usergroups.groupSaved" />,
150
            creating: <Message key="text" msgId="usergroups.creatingGroup" />,
151
            created: <Message key="text" msgId="usergroups.groupCreated" />
152
        };
153
        let message = messages[status] || defaultMessage;
22✔
154
        return [this.isSaving() ? <Spinner key="saving-spinner" spinnerName="circle" noFadeIn overrideSpinnerClassName="spinner"/> : null, message];
22!
155
    };
156

157
    handleSaveGroup = () =>{
16✔
NEW
158
        this.props.onSave(this.props.group);
×
NEW
159
        this.props.onRefresh();
×
160

161
    }
162

163
    renderButtons = () => {
16✔
164
        let CloseBtn = <CloseConfirmButton status={this.props.group && this.props.group.status} onClick={this.props.onClose}/>;
22✔
165
        return [
22✔
166
            CloseBtn,
167
            <Button key="save" bsSize={this.props.buttonSize}
168
                bsStyle={this.isSaved() ? "success" : "primary" }
22!
169
                onClick={this.handleSaveGroup}
170
                disabled={!this.isValid() || this.isSaving()}>
23✔
171
                {this.renderSaveButtonContent()}</Button>
172
        ];
173
    };
174

175
    renderError = () => {
16✔
176
        let error = this.props.group && this.props.group.status === "error";
22✔
177
        if ( error ) {
22!
178
            let lastError = this.props.group && this.props.group.lastError;
×
179
            return <Alert key="error" bsStyle="warning"><Message msgId="usergroups.errorSaving" />{lastError && lastError.statusText}</Alert>;
×
180
        }
181
        return null;
22✔
182
    };
183

184
    renderMembers = () => {
16✔
185
        let members = this.getCurrentGroupMembers();
22✔
186
        if (!members || members.length === 0) {
22!
187
            return (<div style={{
×
188
                width: "100%",
189
                textAlign: "center"
190
            }}><Message msgId="usergroups.noUsers"/></div>);
191
        }
192
        // NOTE: faking group Id
193
        return (<UsersTable users={[...members].sort((u1, u2) => u1.name > u2.name)} onRemove={(user) => {
22✔
194
            let id = user.id;
1✔
195
            let newUsers = this.getCurrentGroupMembers().filter(u => u.id !== id);
2✔
196
            this.props.onChange("newUsers", newUsers);
1✔
197
        }}/>);
198
    };
199

200

201
    renderMembersTab = () => {
16✔
202
        let availableUsers = this.props.availableUsers.filter((user) => findIndex(this.getCurrentGroupMembers(), member => member.id === user.id) < 0).map(u => ({ value: u.id, label: u.name }));
22✔
203
        const pagination = {
22✔
204
            firstPage: this.selectMemberPage === 0,
205
            lastPage: this.isLastPage(),
206
            loadNextPage: this.loadNextPageMembers,
207
            loadPrevPage: this.loadPrevPageMembers,
208
            paginated: true
209
        };
210
        const placeholder = this.context.intl ? this.context.intl.formatMessage({id: 'usergroups.selectMemberPlaceholder'}) : '';
22!
211
        return (<div style={{ marginTop: "10px" }}>
22✔
212
            <label key="member-label" className="control-label"><Message msgId="usergroups.groupMembers" /></label>
213
            <div key="member-list" style={
214
                {
215
                    maxHeight: "200px",
216
                    padding: "5px",
217
                    overflow: "auto",
218
                    boxShadow: "inset 0 2px 5px 0 #AAA"
219
                }} >{this.renderMembers()}</div>
220
            <div key="add-member" style={{ marginTop: "10px" }}>
221
                <label key="add-member-label" className="control-label"><Message msgId="usergroups.addMember" /></label>
222
                <PagedCombobox
223
                    busy={this.props.availableUsersLoading}
224
                    data={availableUsers}
225
                    open={this.state.openSelectMember}
226
                    onToggle={this.handleToggleSelectMember}
227
                    onChange={this.handleSelectMemberOnChange}
228
                    placeholder={placeholder}
229
                    pagination={pagination}
230
                    selectedValue={this.state.selectedMember}
231
                    onSelect={this.handleSelect}
232
                    stopPropagation
233
                />
234
            </div>
235
        </div>);
236
    };
237
    renderAttributes = () => {
16✔
238
        const byName = attrName => ({name}) => attrName === name;
24✔
239
        const group = this.props.group;
22✔
240
        const attributes = group?.attributes ?? [];
22✔
241
        return <>
22✔
242
            {
243
                this.props.attributeFields.map( ({name, title, controlType, ...rest}) => {
244
                    const values = attributes.filter(byName(name)).map(({value}) => value);
24✔
245
                    const value =  values.length === 1 ? values?.[0] : values;
24✔
246
                    const Control = controls[controlType ?? "string"];
24✔
247
                    return (<div style={{ marginTop: "10px" }}>
24✔
248
                        <label key="member-label" className="control-label">{title ?? name}</label>
48✔
249
                        <Control name={name} {...rest} value={value} onChange={(newValue) => {
250
                            // newValue can be an array or a single value to support multiple attributes of the same name.
251
                            const newAttributes = attributes.filter(v => !byName(name)(v)).concat(castArray(newValue).map(vv => ({name, value: vv})));
×
252
                            this.props.onChange("attributes", newAttributes);
×
253
                        }} />
254
                    </div>);
255
                })
256
            }
257
        </>;
258
    };
259

260
    render() {
261
        return (<Dialog
22✔
262
            modal
263
            maskLoading={this.props.group && (this.props.group.status === "loading" || this.props.group.status === "saving")}
65✔
264
            id="mapstore-group-dialog"
265
            className="group-edit-dialog"
266
            style={assign({}, this.props.style, {display: this.props.show ? "block" : "none"})}
22✔
267
            draggable={false}
268
        >
269
            <span role="header">
270
                <button onClick={this.props.onClose} className="login-panel-close close">
271
                    {this.props.closeGlyph ? <Glyphicon glyph={this.props.closeGlyph}/> : <span><Glyphicon glyph="1-close"/></span>}
22!
272
                </button>
273
                <span className="user-panel-title">{(this.props.group && this.props.group.groupName) || <Message msgId="usergroups.newGroup" />}</span>
44!
274
            </span>
275
            <div role="body">
276
                <Tabs justified defaultActiveKey={1} onSelect={ ( key) => { this.setState({key}); }} key="tab-panel">
2✔
277
                    <Tab eventKey={1} title={<Glyphicon glyph="1-group" style={{ display: 'block', padding: 8 }} />} >
278
                        {this.renderGeneral()}
279
                        {this.checkNameLenght()}
280
                        {this.checkDescLenght()}
281
                    </Tab>
282
                    {this.props.showMembersTab ?
22!
283
                        <Tab eventKey={2} title={<Glyphicon glyph="1-group-add" style={{ display: 'block', padding: 8 }} />} >
284
                            {this.renderMembersTab()}
285
                        </Tab>
286
                        : null
287
                    }
288
                    {this.props.showAttributesTab
22!
289
                        ? <Tab eventKey={3} title={<Glyphicon glyph="list-alt" style={{ display: 'block', padding: 8 }} />} >
290
                            {this.renderAttributes()}
291
                        </Tab>
292
                        : null
293
                    }
294
                </Tabs>
295
            </div>
296
            <div role="footer">
297
                {this.renderError()}
298
                {this.renderButtons()}
299
            </div>
300
        </Dialog>);
301
    }
302

303
    // check if pagination last page
304
    isLastPage = () => {
16✔
305
        return (this.selectMemberPage + PAGINATION_LIMIT) > this.props.availableUsersCount;
23✔
306
    }
307

308
    // called before onChange
309
    handleSelect = () => {
16✔
310
        this.selected = true;
1✔
311
    };
312

313
    handleToggleSelectMember = () => {
16✔
314
        this.setState(prevState => ({
2✔
315
            openSelectMember: !prevState.openSelectMember
316
        }));
317
    }
318

319
    handleSelectMemberOnChange = (selected) => {
16✔
320
        if (typeof selected === 'string') {
3✔
321
            this.selectMemberPage = 0;
2✔
322
            this.setState({selectedMember: selected});
2✔
323
            this.searchUsers(selected, true);
2✔
324
            return;
2✔
325
        }
326

327
        if (this.selected) {
1!
328
            this.selected = false;
1✔
329
            let value = selected.value;
1✔
330
            let newMemberIndex = findIndex(this.props.availableUsers, u => u.id === value);
1✔
331
            if (newMemberIndex >= 0) {
1!
332
                let newMember = this.props.availableUsers[newMemberIndex];
1✔
333
                let newUsers = this.getCurrentGroupMembers();
1✔
334
                newUsers = [...newUsers, newMember];
1✔
335
                this.props.onChange("newUsers", newUsers);
1✔
336
                this.setState({selectedMember: '', openSelectMember: false});
1✔
337
                this.searchUsers('*', true);
1✔
338
            }
339
            return;
1✔
340
        }
341
        if (selected.value) {
×
342
            this.setState({selectedMember: selected.label});
×
343
            this.selectMemberPage = 0;
×
344
        }
345
    }
346

347
    loadNextPageMembers = () => {
16✔
348
        if (this.selectMemberPage === 0) {
2✔
349
            this.selectMemberPage = this.selectMemberPage + PAGINATION_LIMIT + 1;
1✔
350
        } else {
351
            this.selectMemberPage = this.selectMemberPage + PAGINATION_LIMIT;
1✔
352
        }
353
        this.searchUsers();
2✔
354
    }
355

356
    loadPrevPageMembers = () => {
16✔
357
        this.selectMemberPage = this.selectMemberPage - PAGINATION_LIMIT;
1✔
358
        if (this.selectMemberPage === 1) {
1!
359
            this.selectMemberPage = 0;
×
360
        }
361
        this.searchUsers();
1✔
362
    }
363

364
    searchUsers = (q, textChanged) => {
16✔
365
        const start = this.selectMemberPage;
22✔
366
        const text = textChanged ? q : (typeof this.state.selectedMember === 'string' && this.state.selectedMember ? this.state.selectedMember : q);
22!
367
        this.props.searchUsers(text, start, PAGINATION_LIMIT);
22✔
368
    }
369

370
    checkNameLenght = () => {
16✔
371
        return this.props.group && this.props.group.groupName && this.props.group.groupName.length === this.props.nameLimit ? <div className="alert alert-warning">
22!
372
            <Message msgId="usergroups.nameLimit"/>
373
        </div> : null;
374
    };
375

376
    checkDescLenght = () => {
16✔
377
        return this.props.group && this.props.group.description && this.props.group.description.length === this.props.descLimit ? <div className="alert alert-warning">
22!
378
            <Message msgId="usergroups.descLimit"/>
379
        </div> : null;
380
    };
381

382
    isSaving = () => {
16✔
383
        return this.props.group && this.props.group.status === "saving";
23✔
384
    };
385

386
    isSaved = () => {
16✔
387
        return this.props.group && (this.props.group.status === "saved" || this.props.group.status === "created");
22✔
388
    };
389

390
    isValid = () => {
16✔
391
        let valid = true;
22✔
392
        let group = this.props.group;
22✔
393
        if (!group) return false;
22!
394
        valid = valid && group.groupName && group.status === "modified";
22✔
395
        return valid;
22✔
396
    };
397

398
    handleChange = (event) => {
16✔
399
        this.props.onChange(event.target.name, event.target.value);
×
400
    };
401
}
402

403
export default GroupDialog;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc