Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow excluding public channels from a legal hold #59

Merged
merged 9 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/legalhold/legal_hold.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (ex *Execution) GetChannels() error {
return appErr
}

channelIDs, err := ex.store.GetChannelIDsForUserDuring(userID, ex.ExecutionStartTime, ex.ExecutionEndTime)
channelIDs, err := ex.store.GetChannelIDsForUserDuring(userID, ex.ExecutionStartTime, ex.ExecutionEndTime, ex.LegalHold.ExcludePublicChannels)
if err != nil {
return err
}
Expand Down
72 changes: 38 additions & 34 deletions server/model/legal_hold.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ import (

// LegalHold represents one legal hold.
type LegalHold struct {
ID string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
UserIDs []string `json:"user_ids"`
StartsAt int64 `json:"starts_at"`
EndsAt int64 `json:"ends_at"`
LastExecutionEndedAt int64 `json:"last_execution_ended_at"`
ExecutionLength int64 `json:"execution_length"`
Secret string `json:"secret"`
ID string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
UserIDs []string `json:"user_ids"`
StartsAt int64 `json:"starts_at"`
EndsAt int64 `json:"ends_at"`
ExcludePublicChannels bool `json:"exclude_public_channels"`
LastExecutionEndedAt int64 `json:"last_execution_ended_at"`
ExecutionLength int64 `json:"execution_length"`
Secret string `json:"secret"`
}

// DeepCopy creates a deep copy of the LegalHold.
Expand All @@ -31,16 +32,17 @@ func (lh *LegalHold) DeepCopy() LegalHold {
}

newLegalHold := LegalHold{
ID: lh.ID,
Name: lh.Name,
DisplayName: lh.DisplayName,
CreateAt: lh.CreateAt,
UpdateAt: lh.UpdateAt,
StartsAt: lh.StartsAt,
EndsAt: lh.EndsAt,
LastExecutionEndedAt: lh.LastExecutionEndedAt,
ExecutionLength: lh.ExecutionLength,
Secret: lh.Secret,
ID: lh.ID,
Name: lh.Name,
DisplayName: lh.DisplayName,
CreateAt: lh.CreateAt,
UpdateAt: lh.UpdateAt,
StartsAt: lh.StartsAt,
EndsAt: lh.EndsAt,
ExcludePublicChannels: lh.ExcludePublicChannels,
LastExecutionEndedAt: lh.LastExecutionEndedAt,
ExecutionLength: lh.ExecutionLength,
Secret: lh.Secret,
}

if len(lh.UserIDs) > 0 {
Expand Down Expand Up @@ -131,25 +133,27 @@ func (lh *LegalHold) BasePath() string {

// CreateLegalHold holds the data that is specified in the API call to create a LegalHold.
type CreateLegalHold struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
UserIDs []string `json:"user_ids"`
StartsAt int64 `json:"starts_at"`
EndsAt int64 `json:"ends_at"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
UserIDs []string `json:"user_ids"`
StartsAt int64 `json:"starts_at"`
EndsAt int64 `json:"ends_at"`
ExcludePublicChannels bool `json:"exclude_public_channels"`
}

// NewLegalHoldFromCreate creates and populates a new LegalHold instance from
// the provided CreateLegalHold instance.
func NewLegalHoldFromCreate(lhc CreateLegalHold) LegalHold {
return LegalHold{
ID: mattermostModel.NewId(),
Name: lhc.Name,
DisplayName: lhc.DisplayName,
UserIDs: lhc.UserIDs,
StartsAt: lhc.StartsAt,
EndsAt: lhc.EndsAt,
LastExecutionEndedAt: 0,
ExecutionLength: 86400000,
ID: mattermostModel.NewId(),
Name: lhc.Name,
DisplayName: lhc.DisplayName,
UserIDs: lhc.UserIDs,
StartsAt: lhc.StartsAt,
EndsAt: lhc.EndsAt,
ExcludePublicChannels: lhc.ExcludePublicChannels,
LastExecutionEndedAt: 0,
ExecutionLength: 86400000,
}
}

Expand Down
14 changes: 12 additions & 2 deletions server/store/sqlstore/legal_hold.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,24 @@ func (ss SQLStore) GetPostsBatch(channelID string, endTime int64, cursor model.L
// GetChannelIDsForUserDuring gets the channel IDs for all channels that the user indicated by userID is
// a member of during the time period from (and including) the startTime up until (but not including) the
// endTime.
func (ss SQLStore) GetChannelIDsForUserDuring(userID string, startTime int64, endTime int64) ([]string, error) {
func (ss SQLStore) GetChannelIDsForUserDuring(userID string, startTime int64, endTime int64, excludePublic bool) ([]string, error) {
query := ss.replicaBuilder.
Select("distinct(cmh.channelid)").
From("channelmemberhistory as cmh").
From("channelmemberhistory as cmh")

query = query.
fmartingr marked this conversation as resolved.
Show resolved Hide resolved
Where(sq.Lt{"cmh.jointime": endTime}).
Where(sq.Or{sq.Eq{"cmh.leavetime": nil}, sq.GtOrEq{"cmh.leavetime": startTime}}).
Where(sq.Eq{"cmh.userid": userID})

// Exclude all public channels from the results
if excludePublic {
query = query.Join("channels on cmh.channelid = channels.id").
Where(sq.NotEq{"channels.type": mattermostModel.ChannelTypeOpen})
}
fmartingr marked this conversation as resolved.
Show resolved Hide resolved

ss.logger.Error(query.ToSql())

rows, err := query.Query()
if err != nil {
ss.logger.Error("error fetching channels for user during time period", "err", err)
Expand Down
28 changes: 25 additions & 3 deletions server/store/sqlstore/legal_hold_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestSQLStore_GetPostsBatch(t *testing.T) {
// Test with an open channel first

// create an open channel
channel, err := th.CreateChannel("stale-test", th.User1.Id, th.Team1.Id)
channel, err := th.CreateOpenChannel("stale-test", th.User1.Id, th.Team1.Id)
require.NoError(t, err)

var posts []*mattermostModel.Post
Expand Down Expand Up @@ -119,7 +119,7 @@ func TestSQLStore_LegalHold_GetChannelIDsForUserDuring(t *testing.T) {
require.NoError(t, th.mmStore.ChannelMemberHistory().LogLeaveEvent(th.User1.Id, channels[9].Id, endTwo-1000))

// Check channel IDs for first window.
firstWindowChannelIDs, err := th.Store.GetChannelIDsForUserDuring(th.User1.Id, startOne, endOne)
firstWindowChannelIDs, err := th.Store.GetChannelIDsForUserDuring(th.User1.Id, startOne, endOne, false)
expectedOne := []string{
channels[1].Id,
channels[2].Id,
Expand All @@ -133,7 +133,7 @@ func TestSQLStore_LegalHold_GetChannelIDsForUserDuring(t *testing.T) {
require.ElementsMatch(t, firstWindowChannelIDs, expectedOne)

// Check channel IDs for second window.
secondWindowChannelIDs, err := th.Store.GetChannelIDsForUserDuring(th.User1.Id, startTwo, endTwo)
secondWindowChannelIDs, err := th.Store.GetChannelIDsForUserDuring(th.User1.Id, startTwo, endTwo, false)
expectedTwo := []string{
channels[3].Id,
channels[4].Id,
Expand All @@ -145,6 +145,28 @@ func TestSQLStore_LegalHold_GetChannelIDsForUserDuring(t *testing.T) {
require.ElementsMatch(t, secondWindowChannelIDs, expectedTwo)
}

func TestLegalHold_GetChannelIDsForUserDuring_ExcludePublic(t *testing.T) {
th := SetupHelper(t).SetupBasic(t)
defer th.TearDown(t)

timeReference := mattermostModel.GetMillis()
start := timeReference + 1000000
end := start + 10000

openChannel, err := th.CreateChannel("public-channel", th.User1.Id, th.Team1.Id, mattermostModel.ChannelTypeOpen)
require.NoError(t, err)
privateChannel, err := th.CreateChannel("private-channel", th.User1.Id, th.Team1.Id, mattermostModel.ChannelTypePrivate)
require.NoError(t, err)

require.NoError(t, th.mmStore.ChannelMemberHistory().LogJoinEvent(th.User1.Id, openChannel.Id, start+1000))
require.NoError(t, th.mmStore.ChannelMemberHistory().LogJoinEvent(th.User1.Id, privateChannel.Id, start+1000))

// Check channel IDs
channelIDs, err := th.Store.GetChannelIDsForUserDuring(th.User1.Id, start, end, true)
require.NoError(t, err)
require.ElementsMatch(t, channelIDs, []string{privateChannel.Id})
}

func TestSQLStore_LegalHold_GetFileInfosByIDs(t *testing.T) {
// TODO: Implement me!
_ = t
Expand Down
8 changes: 6 additions & 2 deletions server/store/sqlstore/testhelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,15 @@ func (th *TestHelper) CreateTeams(num int, namePrefix string) ([]*model.Team, er
return teams, nil
}

func (th *TestHelper) CreateChannel(name string, userID string, teamID string) (*model.Channel, error) {
func (th *TestHelper) CreateOpenChannel(name string, userID string, teamID string) (*model.Channel, error) {
return th.CreateChannel(name, userID, teamID, model.ChannelTypeOpen)
}

func (th *TestHelper) CreateChannel(name, userID, teamID string, channelType model.ChannelType) (*model.Channel, error) {
channel := &model.Channel{
Name: name,
DisplayName: name,
Type: model.ChannelTypeOpen,
Type: channelType,
CreatorId: userID,
TeamId: teamID,
}
Expand Down
10 changes: 10 additions & 0 deletions webapp/src/components/create_legal_hold_form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,15 @@
border: none !important;
}

.create-legal-hold-checkbox {
float: left;
width: 1em;
height: 1em;
}

.create-legal-hold-checkbox-wrapper, .create-legal-hold-checkbox-wrapper:focus {
border: 0px !important;
}

.create-legal-hold-container {
}
27 changes: 26 additions & 1 deletion webapp/src/components/create_legal_hold_form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
import UsersInput from '@/components/users_input';
import {CreateLegalHold} from '@/types';
import {GenericModal} from '@/components/mattermost-webapp/generic_modal/generic_modal';
import Input from '@/components/mattermost-webapp/input/input';

Check failure on line 8 in webapp/src/components/create_legal_hold_form.tsx

View workflow job for this annotation

GitHub Actions / plugin-ci / lint

There should be at least one empty line between import groups
import Select from 'react-select';

Check failure on line 9 in webapp/src/components/create_legal_hold_form.tsx

View workflow job for this annotation

GitHub Actions / plugin-ci / lint

`react-select` import should occur before import of `@/components/users_input`

import './create_legal_hold_form.scss';

interface CreateLegalHoldFormProps {
createLegalHold: (data: CreateLegalHold) => Promise<any>;
onExited: () => void;
visible: boolean;
excludePublicChannels: boolean;
}

const CreateLegalHoldForm = (props: CreateLegalHoldFormProps) => {
Expand All @@ -21,6 +23,7 @@
const [startsAt, setStartsAt] = useState('');
const [endsAt, setEndsAt] = useState('');
const [saving, setSaving] = useState(false);
const [excludePublicChannels, setExcludePublicChannels] = useState(props.excludePublicChannels);
const [serverError, setServerError] = useState('');

const displayNameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -35,6 +38,10 @@
setEndsAt(e.target.value);
};

const excludePublicChannelsChanged: (e: React.ChangeEvent<HTMLInputElement>) => void = (e) => {
setExcludePublicChannels(e.target.checked);
};

const resetForm = () => {
setDisplayName('');
setStartsAt('');
Expand All @@ -55,6 +62,7 @@
ends_at: (new Date(endsAt)).getTime(),
starts_at: (new Date(startsAt)).getTime(),
display_name: displayName,
exclude_public_channels: excludePublicChannels,
name: slugify(displayName),
};

Expand Down Expand Up @@ -138,6 +146,24 @@
onChange={setUsers}
/>
</div>
<div
style={{
display: 'flex',
columnGap: '20px',
}}
>
<label htmlFor={'legal-hold-exclude-public-channels'}>
{'Exclude public channels'}
<Input
type='checkbox'
id='legal-hold-exclude-public-channels'
checked={excludePublicChannels}
onChange={excludePublicChannelsChanged}
inputClassName={'create-legal-hold-checkbox'}
wrapperClassName={'create-legal-hold-checkbox-wrapper'}
/>
</label>
</div>
<div
style={{
display: 'flex',
Expand Down Expand Up @@ -189,4 +215,3 @@
};

export default CreateLegalHoldForm;

Loading