mirror of https://github.com/zulip/zulip.git
slack: Support importing shared channels.
This commit is contained in:
parent
e09a29f4d3
commit
1e5c49ad82
|
@ -187,7 +187,8 @@ def users_to_zerver_userprofile(slack_data_dir: str, users: List[ZerverFieldsT],
|
|||
userprofile = UserProfile(
|
||||
full_name=get_user_full_name(user),
|
||||
short_name=user['name'],
|
||||
is_active=not user['deleted'],
|
||||
is_active=not user.get('deleted', False) and not user["is_mirror_dummy"],
|
||||
is_mirror_dummy=user["is_mirror_dummy"],
|
||||
id=user_id,
|
||||
email=email,
|
||||
delivery_email=email,
|
||||
|
@ -287,6 +288,8 @@ def process_customprofilefields(customprofilefield: List[ZerverFieldsT],
|
|||
def get_user_email(user: ZerverFieldsT, domain_name: str) -> str:
|
||||
if 'email' in user['profile']:
|
||||
return user['profile']['email']
|
||||
if user['is_mirror_dummy']:
|
||||
return "{}@{}.slack.com".format(user["name"], user["team_domain"])
|
||||
if 'bot_id' in user['profile']:
|
||||
if 'real_name_normalized' in user['profile']:
|
||||
slack_bot_name = user['profile']['real_name_normalized']
|
||||
|
@ -609,7 +612,7 @@ def convert_slack_workspace_messages(slack_data_dir: str, users: List[ZerverFiel
|
|||
logging.info('######### IMPORTING MESSAGES FINISHED #########\n')
|
||||
return total_reactions, total_uploads, total_attachments
|
||||
|
||||
def get_messages_iterator(slack_data_dir: str, added_channels: AddedChannelsT,
|
||||
def get_messages_iterator(slack_data_dir: str, added_channels: Dict[str, Any],
|
||||
added_mpims: AddedMPIMsT, dm_members: DMMembersT) -> Iterator[ZerverFieldsT]:
|
||||
"""This function is an iterator that returns all the messages across
|
||||
all Slack channels, in order by timestamp. It's important to
|
||||
|
@ -950,6 +953,45 @@ def get_message_sending_user(message: ZerverFieldsT) -> Optional[str]:
|
|||
return message['file'].get('user')
|
||||
return None
|
||||
|
||||
def fetch_shared_channel_users(user_list: List[ZerverFieldsT], slack_data_dir: str, token: str) -> None:
|
||||
normal_user_ids = set()
|
||||
mirror_dummy_user_ids = set()
|
||||
added_channels = {}
|
||||
team_id_to_domain = {} # type: Dict[str, str]
|
||||
for user in user_list:
|
||||
user["is_mirror_dummy"] = False
|
||||
normal_user_ids.add(user["id"])
|
||||
|
||||
public_channels = get_data_file(slack_data_dir + '/channels.json')
|
||||
try:
|
||||
private_channels = get_data_file(slack_data_dir + '/groups.json')
|
||||
except FileNotFoundError:
|
||||
private_channels = []
|
||||
for channel in public_channels + private_channels:
|
||||
added_channels[channel["name"]] = True
|
||||
for user_id in channel["members"]:
|
||||
if user_id not in normal_user_ids:
|
||||
mirror_dummy_user_ids.add(user_id)
|
||||
|
||||
all_messages = get_messages_iterator(slack_data_dir, added_channels, {}, {})
|
||||
for message in all_messages:
|
||||
user_id = get_message_sending_user(message)
|
||||
if user_id is None or user_id in normal_user_ids:
|
||||
continue
|
||||
mirror_dummy_user_ids.add(user_id)
|
||||
|
||||
# Fetch data on the mirror_dummy_user_ids from the Slack API (it's
|
||||
# not included in the data export file).
|
||||
for user_id in mirror_dummy_user_ids:
|
||||
user = get_slack_api_data("https://slack.com/api/users.info", "user", token=token, user=user_id)
|
||||
team_id = user["team_id"]
|
||||
if team_id not in team_id_to_domain:
|
||||
team = get_slack_api_data("https://slack.com/api/team.info", "team", token=token, team=team_id)
|
||||
team_id_to_domain[team_id] = team["domain"]
|
||||
user["team_domain"] = team_id_to_domain[team_id]
|
||||
user["is_mirror_dummy"] = True
|
||||
user_list.append(user)
|
||||
|
||||
def do_convert_data(slack_zip_file: str, output_dir: str, token: str, threads: int=6) -> None:
|
||||
# Subdomain is set by the user while running the import command
|
||||
realm_subdomain = ""
|
||||
|
@ -972,6 +1014,7 @@ def do_convert_data(slack_zip_file: str, output_dir: str, token: str, threads: i
|
|||
# We get the user data from the legacy token method of slack api, which is depreciated
|
||||
# but we use it as the user email data is provided only in this method
|
||||
user_list = get_slack_api_data("https://slack.com/api/users.list", "members", token=token)
|
||||
fetch_shared_channel_users(user_list, slack_data_dir, token)
|
||||
|
||||
# Get custom emoji from slack api
|
||||
custom_emoji_list = get_slack_api_data("https://slack.com/api/emoji.list", "emoji", token=token)
|
||||
|
|
|
@ -57,11 +57,10 @@ SLACK_BOLD_REGEX = r"""
|
|||
"""
|
||||
|
||||
def get_user_full_name(user: ZerverFieldsT) -> str:
|
||||
if user['deleted'] is False:
|
||||
if user['real_name'] == '':
|
||||
return user['name']
|
||||
else:
|
||||
return user['real_name']
|
||||
if "deleted" in user and user['deleted'] is False:
|
||||
return user['real_name'] or user['name']
|
||||
elif user["is_mirror_dummy"]:
|
||||
return user["profile"].get("real_name", user["name"])
|
||||
else:
|
||||
return user['name']
|
||||
|
||||
|
|
|
@ -43,10 +43,10 @@
|
|||
},
|
||||
{
|
||||
"id": "C061A0HJG",
|
||||
"name": "feedback",
|
||||
"name": "sharedchannel",
|
||||
"created": "1433558359",
|
||||
"is_general": false,
|
||||
"members": ["U061A3E0G"],
|
||||
"members": ["U061A3E0G", "U061A1R2R"],
|
||||
"is_archived": false,
|
||||
"topic": {
|
||||
"value": ""
|
||||
|
|
|
@ -6,6 +6,7 @@ from zerver.data_import.slack import (
|
|||
get_slack_api_data,
|
||||
get_admin,
|
||||
get_user_timezone,
|
||||
fetch_shared_channel_users,
|
||||
users_to_zerver_userprofile,
|
||||
get_subscription,
|
||||
channels_to_zerver_stream,
|
||||
|
@ -51,7 +52,7 @@ import logging
|
|||
import shutil
|
||||
import os
|
||||
import mock
|
||||
from mock import ANY
|
||||
from mock import ANY, call
|
||||
from typing import Any, Dict, List, Set, Tuple, Iterator
|
||||
|
||||
def remove_folder(path: str) -> None:
|
||||
|
@ -91,6 +92,10 @@ class SlackImporter(ZulipTestCase):
|
|||
get_slack_api_data(slack_user_list_url, "members", token=token)
|
||||
self.assertEqual(invalid.exception.args, ('Enter a valid token!',),)
|
||||
|
||||
with self.assertRaises(Exception) as invalid:
|
||||
get_slack_api_data(slack_user_list_url, "members")
|
||||
self.assertEqual(invalid.exception.args, ('Pass slack token in kwargs',),)
|
||||
|
||||
token = 'status404'
|
||||
wrong_url = "https://slack.com/api/wrong"
|
||||
with self.assertRaises(Exception) as invalid:
|
||||
|
@ -128,6 +133,57 @@ class SlackImporter(ZulipTestCase):
|
|||
self.assertEqual(get_user_timezone(user_timezone_none), "America/New_York")
|
||||
self.assertEqual(get_user_timezone(user_no_timezone), "America/New_York")
|
||||
|
||||
@mock.patch("zerver.data_import.slack.get_data_file")
|
||||
@mock.patch("zerver.data_import.slack.get_slack_api_data")
|
||||
@mock.patch("zerver.data_import.slack.get_messages_iterator")
|
||||
def test_fetch_shared_channel_users(self, messages_mock: mock.Mock, api_mock: mock.Mock,
|
||||
data_file_mock: mock.Mock) -> None:
|
||||
users = [{"id": "U061A1R2R"}, {"id": "U061A5N1G"}, {"id": "U064KUGRJ"}]
|
||||
data_file_mock.side_effect = [
|
||||
[
|
||||
{"name": "general", "members": ["U061A1R2R", "U061A5N1G"]},
|
||||
{"name": "sharedchannel", "members": ["U061A1R2R", "U061A3E0G"]}
|
||||
],
|
||||
[]
|
||||
]
|
||||
api_mock.side_effect = [
|
||||
{"id": "U061A3E0G", "team_id": "T6LARQE2Z"},
|
||||
{"domain": "foreignteam1"},
|
||||
{"id": "U061A8H1G", "team_id": "T7KJRQE8Y"},
|
||||
{"domain": "foreignteam2"},
|
||||
]
|
||||
messages_mock.return_value = [
|
||||
{"user": "U061A1R2R"},
|
||||
{"user": "U061A5N1G"},
|
||||
{"user": "U061A8H1G"},
|
||||
]
|
||||
slack_data_dir = self.fixture_file_name('', type='slack_fixtures')
|
||||
fetch_shared_channel_users(users, slack_data_dir, "token")
|
||||
|
||||
# Normal users
|
||||
self.assertEqual(len(users), 5)
|
||||
self.assertEqual(users[0]["id"], "U061A1R2R")
|
||||
self.assertEqual(users[0]["is_mirror_dummy"], False)
|
||||
self.assertFalse("team_domain" in users[0])
|
||||
self.assertEqual(users[1]["id"], "U061A5N1G")
|
||||
self.assertEqual(users[2]["id"], "U064KUGRJ")
|
||||
|
||||
# Shared channel users
|
||||
self.assertEqual(users[3]["id"], "U061A3E0G")
|
||||
self.assertEqual(users[3]["team_domain"], "foreignteam1")
|
||||
self.assertEqual(users[3]["is_mirror_dummy"], True)
|
||||
self.assertEqual(users[4]["id"], "U061A8H1G")
|
||||
self.assertEqual(users[4]["team_domain"], "foreignteam2")
|
||||
self.assertEqual(users[4]["is_mirror_dummy"], True)
|
||||
|
||||
api_calls = [
|
||||
call("https://slack.com/api/users.info", "user", token="token", user="U061A3E0G"),
|
||||
call("https://slack.com/api/team.info", "team", token="token", team="T6LARQE2Z"),
|
||||
call("https://slack.com/api/users.info", "user", token="token", user="U061A8H1G"),
|
||||
call("https://slack.com/api/team.info", "team", token="token", team="T7KJRQE8Y")
|
||||
]
|
||||
api_mock.assert_has_calls(api_calls, any_order=True)
|
||||
|
||||
@mock.patch("zerver.data_import.slack.get_data_file")
|
||||
def test_users_to_zerver_userprofile(self, mock_get_data_file: mock.Mock) -> None:
|
||||
custom_profile_field_user1 = {"Xf06054BBB": {"value": "random1"},
|
||||
|
@ -138,6 +194,7 @@ class SlackImporter(ZulipTestCase):
|
|||
"team_id": "T5YFFM2QY",
|
||||
"name": "john",
|
||||
"deleted": False,
|
||||
"is_mirror_dummy": False,
|
||||
"real_name": "John Doe",
|
||||
"profile": {"image_32": "", "email": "jon@gmail.com", "avatar_hash": "hash",
|
||||
"phone": "+1-123-456-77-868",
|
||||
|
@ -151,6 +208,7 @@ class SlackImporter(ZulipTestCase):
|
|||
'name': 'Jane',
|
||||
"real_name": "Jane Doe",
|
||||
"deleted": False,
|
||||
"is_mirror_dummy": False,
|
||||
"profile": {"image_32": "https://secure.gravatar.com/avatar/random.png",
|
||||
"fields": custom_profile_field_user2,
|
||||
"email": "jane@foo.com", "avatar_hash": "hash"}},
|
||||
|
@ -160,16 +218,26 @@ class SlackImporter(ZulipTestCase):
|
|||
"real_name": "Bot",
|
||||
"is_bot": True,
|
||||
"deleted": False,
|
||||
"is_mirror_dummy": False,
|
||||
"profile": {"image_32": "https://secure.gravatar.com/avatar/random1.png",
|
||||
"skype": "test_skype_name",
|
||||
"email": "bot1@zulipchat.com", "avatar_hash": "hash"}}]
|
||||
"email": "bot1@zulipchat.com", "avatar_hash": "hash"}},
|
||||
{"id": "UHSG7OPQN",
|
||||
"team_id": "T6LARQE2Z",
|
||||
'name': 'matt.perry',
|
||||
"color": '9d8eee',
|
||||
"is_bot": False,
|
||||
"is_app_user": False,
|
||||
"is_mirror_dummy": True,
|
||||
"team_domain": "foreignteam",
|
||||
"profile": {"image_32": "https://secure.gravatar.com/avatar/random6.png",
|
||||
"avatar_hash": "hash", "first_name": "Matt", "last_name": "Perry",
|
||||
"real_name": "Matt Perry", "display_name": "matt.perry", "team": "T6LARQE2Z"}}]
|
||||
|
||||
mock_get_data_file.return_value = user_data
|
||||
# As user with slack_id 'U0CBK5KAT' is the primary owner, that user should be imported first
|
||||
# and hence has zulip_id = 1
|
||||
test_added_users = {'U08RGD1RD': 1,
|
||||
'U0CBK5KAT': 0,
|
||||
'U09TYF5Sk': 2}
|
||||
test_added_users = {'U08RGD1RD': 1, 'U0CBK5KAT': 0, 'U09TYF5Sk': 2, 'UHSG7OPQN': 3}
|
||||
slack_data_dir = './random_path'
|
||||
timestamp = int(timezone_now().timestamp())
|
||||
mock_get_data_file.return_value = user_data
|
||||
|
@ -196,20 +264,44 @@ class SlackImporter(ZulipTestCase):
|
|||
|
||||
# test that the primary owner should always be imported first
|
||||
self.assertDictEqual(added_users, test_added_users)
|
||||
self.assertEqual(len(avatar_list), 3)
|
||||
self.assertEqual(len(avatar_list), 4)
|
||||
|
||||
self.assertEqual(len(zerver_userprofile), 4)
|
||||
|
||||
self.assertEqual(zerver_userprofile[0]['is_staff'], False)
|
||||
self.assertEqual(zerver_userprofile[0]['is_bot'], False)
|
||||
self.assertEqual(zerver_userprofile[0]['is_active'], True)
|
||||
self.assertEqual(zerver_userprofile[0]['is_mirror_dummy'], False)
|
||||
self.assertEqual(zerver_userprofile[0]['is_realm_admin'], False)
|
||||
self.assertEqual(zerver_userprofile[0]['enable_desktop_notifications'], True)
|
||||
self.assertEqual(zerver_userprofile[0]['email'], 'jon@gmail.com')
|
||||
self.assertEqual(zerver_userprofile[0]['full_name'], 'John Doe')
|
||||
|
||||
self.assertEqual(zerver_userprofile[1]['id'], test_added_users['U0CBK5KAT'])
|
||||
self.assertEqual(len(zerver_userprofile), 3)
|
||||
self.assertEqual(zerver_userprofile[1]['id'], 0)
|
||||
self.assertEqual(zerver_userprofile[1]['is_realm_admin'], True)
|
||||
self.assertEqual(zerver_userprofile[1]['is_staff'], False)
|
||||
self.assertEqual(zerver_userprofile[1]['is_active'], True)
|
||||
self.assertEqual(zerver_userprofile[0]['is_staff'], False)
|
||||
self.assertEqual(zerver_userprofile[0]['is_bot'], False)
|
||||
self.assertEqual(zerver_userprofile[0]['enable_desktop_notifications'], True)
|
||||
self.assertEqual(zerver_userprofile[0]['is_mirror_dummy'], False)
|
||||
|
||||
self.assertEqual(zerver_userprofile[2]['id'], test_added_users['U09TYF5Sk'])
|
||||
self.assertEqual(zerver_userprofile[2]['is_bot'], True)
|
||||
self.assertEqual(zerver_userprofile[2]['is_active'], True)
|
||||
self.assertEqual(zerver_userprofile[2]['is_mirror_dummy'], False)
|
||||
self.assertEqual(zerver_userprofile[2]['email'], 'bot1@zulipchat.com')
|
||||
self.assertEqual(zerver_userprofile[2]['bot_type'], 1)
|
||||
self.assertEqual(zerver_userprofile[2]['avatar_source'], 'U')
|
||||
|
||||
self.assertEqual(zerver_userprofile[3]['id'], test_added_users['UHSG7OPQN'])
|
||||
self.assertEqual(zerver_userprofile[3]['is_realm_admin'], False)
|
||||
self.assertEqual(zerver_userprofile[3]['is_staff'], False)
|
||||
self.assertEqual(zerver_userprofile[3]['is_active'], False)
|
||||
self.assertEqual(zerver_userprofile[3]['email'], 'matt.perry@foreignteam.slack.com')
|
||||
self.assertEqual(zerver_userprofile[3]['realm'], 1)
|
||||
self.assertEqual(zerver_userprofile[3]['full_name'], 'Matt Perry')
|
||||
self.assertEqual(zerver_userprofile[3]['short_name'], 'matt.perry')
|
||||
self.assertEqual(zerver_userprofile[3]['is_mirror_dummy'], True)
|
||||
self.assertEqual(zerver_userprofile[3]['is_api_super_user'], False)
|
||||
|
||||
def test_build_defaultstream(self) -> None:
|
||||
realm_id = 1
|
||||
stream_id = 1
|
||||
|
@ -266,7 +358,7 @@ class SlackImporter(ZulipTestCase):
|
|||
added_recipient = channels_to_zerver_stream(self.fixture_file_name("", "slack_fixtures"), realm_id,
|
||||
{"zerver_userpresence": []}, added_users, zerver_userprofile)
|
||||
|
||||
test_added_channels = {'feedback': ("C061A0HJG", 3), 'general': ("C061A0YJG", 1),
|
||||
test_added_channels = {'sharedchannel': ("C061A0HJG", 3), 'general': ("C061A0YJG", 1),
|
||||
'general1': ("C061A0YJP", 2), 'random': ("C061A0WJG", 0)}
|
||||
test_added_mpims = {'mpdm-user9--user2--user10-1': ('G9HBG2A5D', 0),
|
||||
'mpdm-user6--user7--user4-1': ('G6H1Z0ZPS', 1),
|
||||
|
|
|
@ -57,13 +57,16 @@ class SlackMessageConversion(ZulipTestCase):
|
|||
users = [{"id": "U0CBK5KAT",
|
||||
"name": "aaron.anzalone",
|
||||
"deleted": False,
|
||||
"is_mirror_dummy": False,
|
||||
"real_name": ""},
|
||||
{"id": "U08RGD1RD",
|
||||
"name": "john",
|
||||
"deleted": False,
|
||||
"is_mirror_dummy": False,
|
||||
"real_name": "John Doe"},
|
||||
{"id": "U09TYF5Sk",
|
||||
"name": "Jane",
|
||||
"is_mirror_dummy": False,
|
||||
"deleted": True}] # Deleted users don't have 'real_name' key in Slack
|
||||
channel_map = {'general': ('C5Z73A7RA', 137)}
|
||||
message = 'Hi <@U08RGD1RD|john>: How are you? <#C5Z73A7RA|general>'
|
||||
|
|
Loading…
Reference in New Issue