hipchat: Improve import of public room subscribers.

Now, if you pass an api_key, we'll initialize the public room
subscribers to be whatever they were at the time the import happened.

Also, document the situation on the caveats section.
This commit is contained in:
Tim Abbott 2019-01-09 15:42:38 -08:00
parent 035138dd98
commit 53436766c1
6 changed files with 70 additions and 9 deletions

View File

@ -75,6 +75,9 @@ html2text==2018.1.9
httplib2==0.12.0 httplib2==0.12.0
-e git+https://github.com/zulip/talon.git@7d8bdc4dbcfcc5a73298747293b99fe53da55315#egg=talon==1.2.10.zulip1 -e git+https://github.com/zulip/talon.git@7d8bdc4dbcfcc5a73298747293b99fe53da55315#egg=talon==1.2.10.zulip1
# Needed for hipchat import
hypchat==0.21
# Needed for inlining the CSS in emails # Needed for inlining the CSS in emails
premailer==3.2.0 premailer==3.2.0

View File

@ -74,6 +74,7 @@ hpack==3.0.0 # via h2
html2text==2018.1.9 html2text==2018.1.9
httplib2==0.12.0 httplib2==0.12.0
httpretty==0.9.6 httpretty==0.9.6
hypchat==0.21
hyper==0.7.0 # via apns2 hyper==0.7.0 # via apns2
hyperframe==3.2.0 # via h2, hyper hyperframe==3.2.0 # via h2, hyper
hyperlink==18.0.0 # via twisted hyperlink==18.0.0 # via twisted
@ -150,7 +151,7 @@ recommonmark==0.4.0
redis==2.10.6 redis==2.10.6
regex==2018.11.22 regex==2018.11.22
requests-oauthlib==1.0.0 requests-oauthlib==1.0.0
requests[security]==2.21.0 # via aws-xray-sdk, docker, matrix-client, moto, premailer, pyoembed, python-digitalocean, python-gcm, python-twitter, requests-oauthlib, responses, social-auth-core, sphinx, stripe, twilio requests[security]==2.21.0 # via aws-xray-sdk, docker, hypchat, matrix-client, moto, premailer, pyoembed, python-digitalocean, python-gcm, python-twitter, requests-oauthlib, responses, social-auth-core, sphinx, stripe, twilio
responses==0.10.5 # via moto responses==0.10.5 # via moto
rsa==4.0 rsa==4.0
s3transfer==0.1.13 # via boto3 s3transfer==0.1.13 # via boto3

View File

@ -55,6 +55,7 @@ h2==2.6.2 # via hyper
hpack==3.0.0 # via h2 hpack==3.0.0 # via h2
html2text==2018.1.9 html2text==2018.1.9
httplib2==0.12.0 httplib2==0.12.0
hypchat==0.21
hyper==0.7.0 # via apns2 hyper==0.7.0 # via apns2
hyperframe==3.2.0 # via h2, hyper hyperframe==3.2.0 # via h2, hyper
idna==2.8 # via cryptography, requests idna==2.8 # via cryptography, requests
@ -107,7 +108,7 @@ qrcode==6.0 # via django-two-factor-auth
redis==2.10.6 redis==2.10.6
regex==2018.11.22 regex==2018.11.22
requests-oauthlib==1.0.0 requests-oauthlib==1.0.0
requests[security]==2.21.0 # via matrix-client, premailer, pyoembed, python-gcm, python-twitter, requests-oauthlib, social-auth-core, stripe, twilio requests[security]==2.21.0 # via hypchat, matrix-client, premailer, pyoembed, python-gcm, python-twitter, requests-oauthlib, social-auth-core, stripe, twilio
rsa==4.0 rsa==4.0
simplegeneric==0.8.1 # via ipython simplegeneric==0.8.1 # via ipython
six==1.12.0 six==1.12.0

View File

@ -69,6 +69,10 @@ Email support@zulipchat.com with exported HipChat archive and your desired
subdomain. Your imported organization will be hosted at subdomain. Your imported organization will be hosted at
`<subdomain>.zulipchat.com`. `<subdomain>.zulipchat.com`.
Also, see the [caveats section notes on room subscribers](#caveats)
and consider whether you want to also send a HipChat API key to
provide a more faithful import.
If you've already created a test organization at If you've already created a test organization at
`<subdomain>.zulipchat.com`, let us know, and we can rename the old `<subdomain>.zulipchat.com`, let us know, and we can rename the old
organization first. organization first.
@ -113,3 +117,28 @@ root domain. Replace the last line above with the following, after replacing
{!import-login.md!} {!import-login.md!}
[upgrade-zulip-from-git]: https://zulip.readthedocs.io/en/latest/production/maintain-secure-upgrade.html#upgrading-from-a-git-repository [upgrade-zulip-from-git]: https://zulip.readthedocs.io/en/latest/production/maintain-secure-upgrade.html#upgrading-from-a-git-repository
## Caveats
- While the import tool will correctly import the subscribers of
private rooms precisely, HipChat does not store the subscribers of
public rooms when those users don't have an active client. As a
result, HipChat's data exports don't include subscribers for public
rooms. You can pick one of the following options for handling this:
1. Subscribe all users to all public streams (the default, which is
good for small organizations),
2. Subscribe only HipChat room owners to public streams (and plan
for users to subscribe to the imported Zulip streams manually
after the import completes) using the `--slim-mode` option to `manage.py
convert_hipchat_data`, or
3. Use the [HipChat API][hipchat-api-tokens] to fetch each room's
current room subscribers as of the moment the import is run.
Because HipChat doesn't store subscribers to a room when clients
are not connected, these subscriptons will be incomplete for users
who don't have an actively connected client at the time of the
import. You need to pass the token via `--token=abcd1234` in
`manage.py convert_hipchat_data` (or include it in your request,
if importing into Zulip Cloud).
[upgrade-zulip-from-git]: https://zulip.readthedocs.io/en/latest/production/maintain-secure-upgrade.html#upgrading-from-a-git-repository
[hipchat-api-tokens]: https://developer.atlassian.com/server/hipchat/hipchat-rest-api-access-tokens/

View File

@ -1,6 +1,7 @@
import base64 import base64
import dateutil import dateutil
import glob import glob
import hypchat
import logging import logging
import os import os
import re import re
@ -207,7 +208,8 @@ def convert_room_data(raw_data: List[ZerverFieldsT],
subscriber_handler: SubscriberHandler, subscriber_handler: SubscriberHandler,
stream_id_mapper: IdMapper, stream_id_mapper: IdMapper,
user_id_mapper: IdMapper, user_id_mapper: IdMapper,
realm_id: int) -> List[ZerverFieldsT]: realm_id: int,
api_token: Optional[str]=None) -> List[ZerverFieldsT]:
flat_data = [ flat_data = [
d['Room'] d['Room']
for d in raw_data for d in raw_data
@ -249,10 +251,18 @@ def convert_room_data(raw_data: List[ZerverFieldsT],
if user_id_mapper.has(in_dict['owner']): if user_id_mapper.has(in_dict['owner']):
owner = user_id_mapper.get(in_dict['owner']) owner = user_id_mapper.get(in_dict['owner'])
users.add(owner) users.add(owner)
else:
users = set()
if api_token is not None:
hc = hypchat.HypChat(api_token)
room_data = hc.fromurl('{0}/v2/room/{1}/member'.format(hc.endpoint, in_dict['id']))
if not users: for item in room_data['items']:
continue hipchat_user_id = item['id']
zulip_user_id = user_id_mapper.get(hipchat_user_id)
users.add(zulip_user_id)
if users:
subscriber_handler.set_info( subscriber_handler.set_info(
stream_id=stream_id, stream_id=stream_id,
users=users, users=users,
@ -768,7 +778,9 @@ def make_user_messages(zerver_message: List[ZerverFieldsT],
def do_convert_data(input_tar_file: str, def do_convert_data(input_tar_file: str,
output_dir: str, output_dir: str,
masking_content: bool) -> None: masking_content: bool,
api_token: Optional[str]=None,
slim_mode: bool=False) -> None:
input_data_dir = untar_input_file(input_tar_file) input_data_dir = untar_input_file(input_tar_file)
attachment_handler = AttachmentHandler() attachment_handler = AttachmentHandler()
@ -780,8 +792,6 @@ def do_convert_data(input_tar_file: str,
realm_id = 0 realm_id = 0
realm = make_realm(realm_id=realm_id) realm = make_realm(realm_id=realm_id)
slim_mode = False
# users.json -> UserProfile # users.json -> UserProfile
raw_user_data = read_user_data(data_dir=input_data_dir) raw_user_data = read_user_data(data_dir=input_data_dir)
convert_user_data( convert_user_data(
@ -803,6 +813,7 @@ def do_convert_data(input_tar_file: str,
stream_id_mapper=stream_id_mapper, stream_id_mapper=stream_id_mapper,
user_id_mapper=user_id_mapper, user_id_mapper=user_id_mapper,
realm_id=realm_id, realm_id=realm_id,
api_token=api_token,
) )
realm['zerver_stream'] = zerver_stream realm['zerver_stream'] = zerver_stream
@ -812,7 +823,7 @@ def do_convert_data(input_tar_file: str,
) )
realm['zerver_recipient'] = zerver_recipient realm['zerver_recipient'] = zerver_recipient
if True: if api_token is None:
if slim_mode: if slim_mode:
public_stream_subscriptions = [] # type: List[ZerverFieldsT] public_stream_subscriptions = [] # type: List[ZerverFieldsT]
else: else:
@ -829,6 +840,12 @@ def do_convert_data(input_tar_file: str,
if stream_dict['invite_only']], if stream_dict['invite_only']],
) )
stream_subscriptions = public_stream_subscriptions + private_stream_subscriptions stream_subscriptions = public_stream_subscriptions + private_stream_subscriptions
else:
stream_subscriptions = build_stream_subscriptions(
get_users=subscriber_handler.get_users,
zerver_recipient=zerver_recipient,
zerver_stream=zerver_stream,
)
personal_subscriptions = build_personal_subscriptions( personal_subscriptions = build_personal_subscriptions(
zerver_recipient=zerver_recipient, zerver_recipient=zerver_recipient,

View File

@ -44,6 +44,14 @@ class Command(BaseCommand):
action="store_true", action="store_true",
help='Mask the content for privacy during QA.') help='Mask the content for privacy during QA.')
parser.add_argument('--slim-mode', dest='slim_mode',
action="store_true",
help='Mask the content for privacy during QA.')
parser.add_argument('--token', dest='api_token',
action="store",
help='API token for the HipChat API for fetching subscribers.')
parser.formatter_class = argparse.RawTextHelpFormatter parser.formatter_class = argparse.RawTextHelpFormatter
def handle(self, *args: Any, **options: Any) -> None: def handle(self, *args: Any, **options: Any) -> None:
@ -75,4 +83,6 @@ class Command(BaseCommand):
input_tar_file=path, input_tar_file=path,
output_dir=output_dir, output_dir=output_dir,
masking_content=options.get('masking_content', False), masking_content=options.get('masking_content', False),
slim_mode=options['slim_mode'],
api_token=options.get("api_token"),
) )